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 }