Monorepo for Tangled
at d82f05f871f30d065c707ec443ec2e6782eebfec 204 lines 3.7 kB view raw
1package searchquery 2 3import ( 4 "strings" 5 "unicode" 6) 7 8type ItemKind int 9 10const ( 11 KindKeyword ItemKind = iota 12 KindQuoted 13 KindTagValue 14) 15 16type Item struct { 17 Kind ItemKind 18 Negated bool 19 Raw string 20 Key string 21 Value string 22} 23 24type Query struct { 25 items []Item 26} 27 28func Parse(input string) *Query { 29 q := &Query{} 30 runes := []rune(strings.TrimSpace(input)) 31 if len(runes) == 0 { 32 return q 33 } 34 35 i := 0 36 for i < len(runes) { 37 for i < len(runes) && unicode.IsSpace(runes[i]) { 38 i++ 39 } 40 if i >= len(runes) { 41 break 42 } 43 44 negated := false 45 if runes[i] == '-' && i+1 < len(runes) && runes[i+1] == '"' { 46 negated = true 47 i++ // skip '-' 48 } 49 50 if runes[i] == '"' { 51 start := i 52 if negated { 53 start-- // include the '-' in Raw 54 } 55 i++ // skip opening quote 56 inner := i 57 for i < len(runes) && runes[i] != '"' { 58 if runes[i] == '\\' && i+1 < len(runes) { 59 i++ 60 } 61 i++ 62 } 63 value := unescapeQuoted(runes[inner:i]) 64 if i < len(runes) { 65 i++ // skip closing quote 66 } 67 q.items = append(q.items, Item{ 68 Kind: KindQuoted, 69 Negated: negated, 70 Raw: string(runes[start:i]), 71 Value: value, 72 }) 73 continue 74 } 75 76 start := i 77 for i < len(runes) && !unicode.IsSpace(runes[i]) && runes[i] != '"' { 78 i++ 79 } 80 token := string(runes[start:i]) 81 82 negated = false 83 subject := token 84 if len(subject) > 1 && subject[0] == '-' { 85 negated = true 86 subject = subject[1:] 87 } 88 89 colonIdx := strings.Index(subject, ":") 90 if colonIdx > 0 { 91 key := subject[:colonIdx] 92 value := subject[colonIdx+1:] 93 q.items = append(q.items, Item{ 94 Kind: KindTagValue, 95 Negated: negated, 96 Raw: token, 97 Key: key, 98 Value: value, 99 }) 100 } else { 101 q.items = append(q.items, Item{ 102 Kind: KindKeyword, 103 Negated: negated, 104 Raw: token, 105 Value: subject, 106 }) 107 } 108 } 109 110 return q 111} 112 113func unescapeQuoted(runes []rune) string { 114 var b strings.Builder 115 for i := 0; i < len(runes); i++ { 116 if runes[i] == '\\' && i+1 < len(runes) { 117 i++ 118 } 119 b.WriteRune(runes[i]) 120 } 121 return b.String() 122} 123 124func (q *Query) Items() []Item { 125 return q.items 126} 127 128func (q *Query) Get(key string) *string { 129 for i, item := range q.items { 130 if item.Kind == KindTagValue && !item.Negated && item.Key == key { 131 return &q.items[i].Value 132 } 133 } 134 return nil 135} 136 137func (q *Query) GetAll(key string) []string { 138 var result []string 139 for _, item := range q.items { 140 if item.Kind == KindTagValue && !item.Negated && item.Key == key { 141 result = append(result, item.Value) 142 } 143 } 144 return result 145} 146 147func (q *Query) GetAllNegated(key string) []string { 148 var result []string 149 for _, item := range q.items { 150 if item.Kind == KindTagValue && item.Negated && item.Key == key { 151 result = append(result, item.Value) 152 } 153 } 154 return result 155} 156 157func (q *Query) Has(key string) bool { 158 return q.Get(key) != nil 159} 160 161func (q *Query) Set(key, value string) { 162 raw := key + ":" + value 163 found := false 164 newItems := make([]Item, 0, len(q.items)) 165 166 for _, item := range q.items { 167 if item.Kind == KindTagValue && !item.Negated && item.Key == key { 168 if !found { 169 newItems = append(newItems, Item{ 170 Kind: KindTagValue, 171 Raw: raw, 172 Key: key, 173 Value: value, 174 }) 175 found = true 176 } 177 } else { 178 newItems = append(newItems, item) 179 } 180 } 181 182 if !found { 183 newItems = append(newItems, Item{ 184 Kind: KindTagValue, 185 Raw: raw, 186 Key: key, 187 Value: value, 188 }) 189 } 190 191 q.items = newItems 192} 193 194func (q *Query) String() string { 195 if len(q.items) == 0 { 196 return "" 197 } 198 199 parts := make([]string, len(q.items)) 200 for i, item := range q.items { 201 parts[i] = item.Raw 202 } 203 return strings.Join(parts, " ") 204}