github.com/tilt-dev/tilt@v0.33.15-0.20240515162809-0a22ed45d8a0/internal/sliceutils/sliceutils.go (about)

     1  package sliceutils
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strings"
     7  )
     8  
     9  // De-duplicate strings, maintaining order.
    10  func Dedupe(ids []string) []string {
    11  	seen := map[string]bool{}
    12  	result := make([]string, 0, len(ids))
    13  	for _, s := range ids {
    14  		if !seen[s] {
    15  			seen[s] = true
    16  			result = append(result, s)
    17  		}
    18  	}
    19  	return result
    20  }
    21  
    22  // Deduplicate and sort a slice of strings.
    23  func DedupedAndSorted(slice []string) []string {
    24  	result := Dedupe(slice)
    25  	sort.Strings(result)
    26  	return result
    27  }
    28  
    29  // Quote each string in the list and separate them by commas.
    30  func QuotedStringList(list []string) string {
    31  	result := make([]string, len(list))
    32  	for i, s := range list {
    33  		result[i] = fmt.Sprintf("%q", s)
    34  	}
    35  	return strings.Join(result, ", ")
    36  }
    37  
    38  func BulletedIndentedStringList(list []string) string {
    39  	if len(list) == 0 {
    40  		return ""
    41  	}
    42  
    43  	return "\t- " + strings.Join(list, "\n\t- ")
    44  }
    45  
    46  func StringSliceEquals(a, b []string) bool {
    47  	if len(a) != len(b) {
    48  		return false
    49  	}
    50  
    51  	for i, e1 := range a {
    52  		e2 := b[i]
    53  		if e1 != e2 {
    54  			return false
    55  		}
    56  	}
    57  
    58  	return true
    59  }
    60  
    61  // StringSliceStartsWith returns true if slice A starts with the given elem.
    62  func StringSliceStartsWith(a []string, elem string) bool {
    63  	if len(a) == 0 {
    64  		return false
    65  	}
    66  
    67  	return a[0] == elem
    68  }
    69  
    70  // returns a slice that consists of `a`, in order, followed by elements of `b` that are not in `a`
    71  func AppendWithoutDupes(a []string, b ...string) []string {
    72  	seen := make(map[string]bool)
    73  	for _, s := range a {
    74  		seen[s] = true
    75  	}
    76  
    77  	ret := append([]string{}, a...)
    78  	for _, s := range b {
    79  		if !seen[s] {
    80  			ret = append(ret, s)
    81  		}
    82  	}
    83  
    84  	return ret
    85  }
    86  
    87  type EscapeSplitOptions struct {
    88  	Delimiter  rune
    89  	EscapeChar rune
    90  }
    91  
    92  func NewEscapeSplitOptions() EscapeSplitOptions {
    93  	return EscapeSplitOptions{
    94  		Delimiter:  ':',
    95  		EscapeChar: '\\',
    96  	}
    97  }
    98  
    99  func UnescapeAndSplit(s string, opts EscapeSplitOptions) ([]string, error) {
   100  	var parts []string
   101  	escapeNextChar := false
   102  	cur := ""
   103  	for i, r := range s {
   104  		if escapeNextChar {
   105  			if r == opts.Delimiter || r == opts.EscapeChar {
   106  				cur += string(r)
   107  			} else {
   108  				// grab the 6 chars around the invalid char to make it easier to find the offending string from the error
   109  				snippetStart := i - 3
   110  				if snippetStart < 0 {
   111  					snippetStart = 0
   112  				}
   113  				snippetEnd := i + 3
   114  				if snippetEnd > len(s) {
   115  					snippetEnd = len(s)
   116  				}
   117  
   118  				return nil, fmt.Errorf("invalid escape sequence '%c%c' in '%s'", opts.EscapeChar, r, s[snippetStart:snippetEnd])
   119  			}
   120  			escapeNextChar = false
   121  		} else {
   122  			switch r {
   123  			case opts.Delimiter:
   124  				parts = append(parts, cur)
   125  				cur = ""
   126  			case opts.EscapeChar:
   127  				escapeNextChar = true
   128  			default:
   129  				cur += string(r)
   130  			}
   131  		}
   132  	}
   133  	parts = append(parts, cur)
   134  
   135  	return parts, nil
   136  }
   137  
   138  func quotePart(s string, opts EscapeSplitOptions) string {
   139  	for _, r := range []rune{opts.EscapeChar, opts.Delimiter} {
   140  		s = strings.ReplaceAll(s, string(r), fmt.Sprintf("%c%c", opts.EscapeChar, r))
   141  	}
   142  	return s
   143  }
   144  
   145  func EscapeAndJoin(parts []string, opts EscapeSplitOptions) string {
   146  	ret := strings.Builder{}
   147  	for i, part := range parts {
   148  		if i != 0 {
   149  			ret.WriteRune(opts.Delimiter)
   150  		}
   151  		_, _ = ret.WriteString(quotePart(part, opts))
   152  	}
   153  
   154  	return ret.String()
   155  }