github.com/solo-io/cue@v0.4.7/internal/attrs.go (about) 1 // Copyright 2020 CUE Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package internal 16 17 import ( 18 "fmt" 19 "strconv" 20 "strings" 21 "unicode" 22 23 "github.com/solo-io/cue/cue/errors" 24 "github.com/solo-io/cue/cue/literal" 25 "github.com/solo-io/cue/cue/token" 26 ) 27 28 // AttrKind indicates the location of an attribute within CUE source. 29 type AttrKind uint8 30 31 const ( 32 // FieldAttr indicates an attribute is a field attribute. 33 // foo: bar @attr() 34 FieldAttr AttrKind = 1 << iota 35 36 // DeclAttr indicates an attribute was specified at a declaration position. 37 // foo: { 38 // @attr() 39 // } 40 DeclAttr 41 42 // TODO: Possible future attr kinds 43 // ElemAttr 44 // FileAttr 45 // ValueAttr = FieldAttr|DeclAttr|ElemAttr 46 ) 47 48 // Attr holds positional information for a single Attr. 49 type Attr struct { 50 Name string // e.g. "json" or "protobuf" 51 Body string 52 Kind AttrKind 53 Fields []KeyValue 54 Err error 55 } 56 57 // NewNonExisting creates a non-existing attribute. 58 func NewNonExisting(key string) Attr { 59 const msgNotExist = "attribute %q does not exist" 60 return Attr{Err: errors.Newf(token.NoPos, msgNotExist, key)} 61 } 62 63 type KeyValue struct { 64 data string 65 equal int // index of equal sign or 0 if non-existing 66 } 67 68 func (kv *KeyValue) Text() string { return kv.data } 69 func (kv *KeyValue) Key() string { 70 if kv.equal == 0 { 71 return kv.data 72 } 73 s := kv.data[:kv.equal] 74 s = strings.TrimSpace(s) 75 return s 76 } 77 func (kv *KeyValue) Value() string { 78 if kv.equal == 0 { 79 return "" 80 } 81 return strings.TrimSpace(kv.data[kv.equal+1:]) 82 } 83 84 func (a *Attr) hasPos(p int) error { 85 if a.Err != nil { 86 return a.Err 87 } 88 if p >= len(a.Fields) { 89 return fmt.Errorf("field does not exist") 90 } 91 return nil 92 } 93 94 // String reports the possibly empty string value at the given position or 95 // an error the attribute is invalid or if the position does not exist. 96 func (a *Attr) String(pos int) (string, error) { 97 if err := a.hasPos(pos); err != nil { 98 return "", err 99 } 100 return a.Fields[pos].Text(), nil 101 } 102 103 // Int reports the integer at the given position or an error if the attribute is 104 // invalid, the position does not exist, or the value at the given position is 105 // not an integer. 106 func (a *Attr) Int(pos int) (int64, error) { 107 if err := a.hasPos(pos); err != nil { 108 return 0, err 109 } 110 // TODO: use CUE's literal parser once it exists, allowing any of CUE's 111 // number types. 112 return strconv.ParseInt(a.Fields[pos].Text(), 10, 64) 113 } 114 115 // Flag reports whether an entry with the given name exists at position pos or 116 // onwards or an error if the attribute is invalid or if the first pos-1 entries 117 // are not defined. 118 func (a *Attr) Flag(pos int, key string) (bool, error) { 119 if err := a.hasPos(pos - 1); err != nil { 120 return false, err 121 } 122 for _, kv := range a.Fields[pos:] { 123 if kv.Text() == key { 124 return true, nil 125 } 126 } 127 return false, nil 128 } 129 130 // Lookup searches for an entry of the form key=value from position pos onwards 131 // and reports the value if found. It reports an error if the attribute is 132 // invalid or if the first pos-1 entries are not defined. 133 func (a *Attr) Lookup(pos int, key string) (val string, found bool, err error) { 134 if err := a.hasPos(pos - 1); err != nil { 135 return "", false, err 136 } 137 for _, kv := range a.Fields[pos:] { 138 if kv.Key() == key { 139 return kv.Value(), true, nil 140 } 141 } 142 return "", false, nil 143 } 144 145 func ParseAttrBody(pos token.Pos, s string) (a Attr) { 146 a.Body = s 147 i := 0 148 for { 149 i += skipSpace(s[i:]) 150 // always scan at least one, possibly empty element. 151 n, err := scanAttributeElem(pos, s[i:], &a) 152 if err != nil { 153 return Attr{Err: err} 154 } 155 if i += n; i >= len(s) { 156 break 157 } 158 i += skipSpace(s[i:]) 159 if s[i] != ',' { 160 return Attr{Err: errors.Newf(pos, "invalid attribute: expected comma")} 161 } 162 i++ 163 } 164 return a 165 } 166 167 func skipSpace(s string) int { 168 for n, r := range s { 169 if !unicode.IsSpace(r) { 170 return n 171 } 172 } 173 return 0 174 } 175 176 func scanAttributeElem(pos token.Pos, s string, a *Attr) (n int, err errors.Error) { 177 // try CUE string 178 kv := KeyValue{} 179 if n, kv.data, err = scanAttributeString(pos, s); n == 0 { 180 // try key-value pair 181 p := strings.IndexAny(s, ",=") // ) is assumed to be stripped. 182 switch { 183 case p < 0: 184 kv.data = strings.TrimSpace(s) 185 n = len(s) 186 187 default: // ',' 188 n = p 189 kv.data = strings.TrimSpace(s[:n]) 190 191 case s[p] == '=': 192 kv.equal = p 193 offset := p + 1 194 offset += skipSpace(s[offset:]) 195 var str string 196 if p, str, err = scanAttributeString(pos, s[offset:]); p > 0 { 197 n = offset + p 198 kv.data = s[:offset] + str 199 } else { 200 n = len(s) 201 if p = strings.IndexByte(s[offset:], ','); p >= 0 { 202 n = offset + p 203 } 204 kv.data = strings.TrimSpace(s[:n]) 205 } 206 } 207 } 208 if a != nil { 209 a.Fields = append(a.Fields, kv) 210 } 211 return n, err 212 } 213 214 func scanAttributeString(pos token.Pos, s string) (n int, str string, err errors.Error) { 215 if s == "" || (s[0] != '#' && s[0] != '"' && s[0] != '\'') { 216 return 0, "", nil 217 } 218 219 nHash := 0 220 for { 221 if nHash < len(s) { 222 if s[nHash] == '#' { 223 nHash++ 224 continue 225 } 226 if s[nHash] == '\'' || s[nHash] == '"' { 227 break 228 } 229 } 230 return nHash, s[:nHash], errors.Newf(pos, "invalid attribute string") 231 } 232 233 // Determine closing quote. 234 nQuote := 1 235 if c := s[nHash]; nHash+6 < len(s) && s[nHash+1] == c && s[nHash+2] == c { 236 nQuote = 3 237 } 238 close := s[nHash:nHash+nQuote] + s[:nHash] 239 240 // Search for closing quote. 241 index := strings.Index(s[len(close):], close) 242 if index == -1 { 243 return len(s), "", errors.Newf(pos, "attribute string not terminated") 244 } 245 246 index += 2 * len(close) 247 s, err2 := literal.Unquote(s[:index]) 248 if err2 != nil { 249 return index, "", errors.Newf(pos, "invalid attribute string: %v", err2) 250 } 251 return index, s, nil 252 }