github.com/vieux/docker@v0.6.3-0.20161004191708-e097c2a938c7/builder/dockerfile/parser/line_parsers.go (about) 1 package parser 2 3 // line parsers are dispatch calls that parse a single unit of text into a 4 // Node object which contains the whole statement. Dockerfiles have varied 5 // (but not usually unique, see ONBUILD for a unique example) parsing rules 6 // per-command, and these unify the processing in a way that makes it 7 // manageable. 8 9 import ( 10 "encoding/json" 11 "errors" 12 "fmt" 13 "strings" 14 "unicode" 15 "unicode/utf8" 16 ) 17 18 var ( 19 errDockerfileNotStringArray = errors.New("When using JSON array syntax, arrays must be comprised of strings only.") 20 ) 21 22 // ignore the current argument. This will still leave a command parsed, but 23 // will not incorporate the arguments into the ast. 24 func parseIgnore(rest string, d *Directive) (*Node, map[string]bool, error) { 25 return &Node{}, nil, nil 26 } 27 28 // used for onbuild. Could potentially be used for anything that represents a 29 // statement with sub-statements. 30 // 31 // ONBUILD RUN foo bar -> (onbuild (run foo bar)) 32 // 33 func parseSubCommand(rest string, d *Directive) (*Node, map[string]bool, error) { 34 if rest == "" { 35 return nil, nil, nil 36 } 37 38 _, child, err := ParseLine(rest, d) 39 if err != nil { 40 return nil, nil, err 41 } 42 43 return &Node{Children: []*Node{child}}, nil, nil 44 } 45 46 // helper to parse words (i.e space delimited or quoted strings) in a statement. 47 // The quotes are preserved as part of this function and they are stripped later 48 // as part of processWords(). 49 func parseWords(rest string, d *Directive) []string { 50 const ( 51 inSpaces = iota // looking for start of a word 52 inWord 53 inQuote 54 ) 55 56 words := []string{} 57 phase := inSpaces 58 word := "" 59 quote := '\000' 60 blankOK := false 61 var ch rune 62 var chWidth int 63 64 for pos := 0; pos <= len(rest); pos += chWidth { 65 if pos != len(rest) { 66 ch, chWidth = utf8.DecodeRuneInString(rest[pos:]) 67 } 68 69 if phase == inSpaces { // Looking for start of word 70 if pos == len(rest) { // end of input 71 break 72 } 73 if unicode.IsSpace(ch) { // skip spaces 74 continue 75 } 76 phase = inWord // found it, fall through 77 } 78 if (phase == inWord || phase == inQuote) && (pos == len(rest)) { 79 if blankOK || len(word) > 0 { 80 words = append(words, word) 81 } 82 break 83 } 84 if phase == inWord { 85 if unicode.IsSpace(ch) { 86 phase = inSpaces 87 if blankOK || len(word) > 0 { 88 words = append(words, word) 89 } 90 word = "" 91 blankOK = false 92 continue 93 } 94 if ch == '\'' || ch == '"' { 95 quote = ch 96 blankOK = true 97 phase = inQuote 98 } 99 if ch == d.EscapeToken { 100 if pos+chWidth == len(rest) { 101 continue // just skip an escape token at end of line 102 } 103 // If we're not quoted and we see an escape token, then always just 104 // add the escape token plus the char to the word, even if the char 105 // is a quote. 106 word += string(ch) 107 pos += chWidth 108 ch, chWidth = utf8.DecodeRuneInString(rest[pos:]) 109 } 110 word += string(ch) 111 continue 112 } 113 if phase == inQuote { 114 if ch == quote { 115 phase = inWord 116 } 117 // The escape token is special except for ' quotes - can't escape anything for ' 118 if ch == d.EscapeToken && quote != '\'' { 119 if pos+chWidth == len(rest) { 120 phase = inWord 121 continue // just skip the escape token at end 122 } 123 pos += chWidth 124 word += string(ch) 125 ch, chWidth = utf8.DecodeRuneInString(rest[pos:]) 126 } 127 word += string(ch) 128 } 129 } 130 131 return words 132 } 133 134 // parse environment like statements. Note that this does *not* handle 135 // variable interpolation, which will be handled in the evaluator. 136 func parseNameVal(rest string, key string, d *Directive) (*Node, map[string]bool, error) { 137 // This is kind of tricky because we need to support the old 138 // variant: KEY name value 139 // as well as the new one: KEY name=value ... 140 // The trigger to know which one is being used will be whether we hit 141 // a space or = first. space ==> old, "=" ==> new 142 143 words := parseWords(rest, d) 144 if len(words) == 0 { 145 return nil, nil, nil 146 } 147 148 var rootnode *Node 149 150 // Old format (KEY name value) 151 if !strings.Contains(words[0], "=") { 152 node := &Node{} 153 rootnode = node 154 strs := tokenWhitespace.Split(rest, 2) 155 156 if len(strs) < 2 { 157 return nil, nil, fmt.Errorf(key + " must have two arguments") 158 } 159 160 node.Value = strs[0] 161 node.Next = &Node{} 162 node.Next.Value = strs[1] 163 } else { 164 var prevNode *Node 165 for i, word := range words { 166 if !strings.Contains(word, "=") { 167 return nil, nil, fmt.Errorf("Syntax error - can't find = in %q. Must be of the form: name=value", word) 168 } 169 parts := strings.SplitN(word, "=", 2) 170 171 name := &Node{} 172 value := &Node{} 173 174 name.Next = value 175 name.Value = parts[0] 176 value.Value = parts[1] 177 178 if i == 0 { 179 rootnode = name 180 } else { 181 prevNode.Next = name 182 } 183 prevNode = value 184 } 185 } 186 187 return rootnode, nil, nil 188 } 189 190 func parseEnv(rest string, d *Directive) (*Node, map[string]bool, error) { 191 return parseNameVal(rest, "ENV", d) 192 } 193 194 func parseLabel(rest string, d *Directive) (*Node, map[string]bool, error) { 195 return parseNameVal(rest, "LABEL", d) 196 } 197 198 // parses a statement containing one or more keyword definition(s) and/or 199 // value assignments, like `name1 name2= name3="" name4=value`. 200 // Note that this is a stricter format than the old format of assignment, 201 // allowed by parseNameVal(), in a way that this only allows assignment of the 202 // form `keyword=[<value>]` like `name2=`, `name3=""`, and `name4=value` above. 203 // In addition, a keyword definition alone is of the form `keyword` like `name1` 204 // above. And the assignments `name2=` and `name3=""` are equivalent and 205 // assign an empty value to the respective keywords. 206 func parseNameOrNameVal(rest string, d *Directive) (*Node, map[string]bool, error) { 207 words := parseWords(rest, d) 208 if len(words) == 0 { 209 return nil, nil, nil 210 } 211 212 var ( 213 rootnode *Node 214 prevNode *Node 215 ) 216 for i, word := range words { 217 node := &Node{} 218 node.Value = word 219 if i == 0 { 220 rootnode = node 221 } else { 222 prevNode.Next = node 223 } 224 prevNode = node 225 } 226 227 return rootnode, nil, nil 228 } 229 230 // parses a whitespace-delimited set of arguments. The result is effectively a 231 // linked list of string arguments. 232 func parseStringsWhitespaceDelimited(rest string, d *Directive) (*Node, map[string]bool, error) { 233 if rest == "" { 234 return nil, nil, nil 235 } 236 237 node := &Node{} 238 rootnode := node 239 prevnode := node 240 for _, str := range tokenWhitespace.Split(rest, -1) { // use regexp 241 prevnode = node 242 node.Value = str 243 node.Next = &Node{} 244 node = node.Next 245 } 246 247 // XXX to get around regexp.Split *always* providing an empty string at the 248 // end due to how our loop is constructed, nil out the last node in the 249 // chain. 250 prevnode.Next = nil 251 252 return rootnode, nil, nil 253 } 254 255 // parsestring just wraps the string in quotes and returns a working node. 256 func parseString(rest string, d *Directive) (*Node, map[string]bool, error) { 257 if rest == "" { 258 return nil, nil, nil 259 } 260 n := &Node{} 261 n.Value = rest 262 return n, nil, nil 263 } 264 265 // parseJSON converts JSON arrays to an AST. 266 func parseJSON(rest string, d *Directive) (*Node, map[string]bool, error) { 267 rest = strings.TrimLeftFunc(rest, unicode.IsSpace) 268 if !strings.HasPrefix(rest, "[") { 269 return nil, nil, fmt.Errorf(`Error parsing "%s" as a JSON array`, rest) 270 } 271 272 var myJSON []interface{} 273 if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJSON); err != nil { 274 return nil, nil, err 275 } 276 277 var top, prev *Node 278 for _, str := range myJSON { 279 s, ok := str.(string) 280 if !ok { 281 return nil, nil, errDockerfileNotStringArray 282 } 283 284 node := &Node{Value: s} 285 if prev == nil { 286 top = node 287 } else { 288 prev.Next = node 289 } 290 prev = node 291 } 292 293 return top, map[string]bool{"json": true}, nil 294 } 295 296 // parseMaybeJSON determines if the argument appears to be a JSON array. If 297 // so, passes to parseJSON; if not, quotes the result and returns a single 298 // node. 299 func parseMaybeJSON(rest string, d *Directive) (*Node, map[string]bool, error) { 300 if rest == "" { 301 return nil, nil, nil 302 } 303 304 node, attrs, err := parseJSON(rest, d) 305 306 if err == nil { 307 return node, attrs, nil 308 } 309 if err == errDockerfileNotStringArray { 310 return nil, nil, err 311 } 312 313 node = &Node{} 314 node.Value = rest 315 return node, nil, nil 316 } 317 318 // parseMaybeJSONToList determines if the argument appears to be a JSON array. If 319 // so, passes to parseJSON; if not, attempts to parse it as a whitespace 320 // delimited string. 321 func parseMaybeJSONToList(rest string, d *Directive) (*Node, map[string]bool, error) { 322 node, attrs, err := parseJSON(rest, d) 323 324 if err == nil { 325 return node, attrs, nil 326 } 327 if err == errDockerfileNotStringArray { 328 return nil, nil, err 329 } 330 331 return parseStringsWhitespaceDelimited(rest, d) 332 } 333 334 // The HEALTHCHECK command is like parseMaybeJSON, but has an extra type argument. 335 func parseHealthConfig(rest string, d *Directive) (*Node, map[string]bool, error) { 336 // Find end of first argument 337 var sep int 338 for ; sep < len(rest); sep++ { 339 if unicode.IsSpace(rune(rest[sep])) { 340 break 341 } 342 } 343 next := sep 344 for ; next < len(rest); next++ { 345 if !unicode.IsSpace(rune(rest[next])) { 346 break 347 } 348 } 349 350 if sep == 0 { 351 return nil, nil, nil 352 } 353 354 typ := rest[:sep] 355 cmd, attrs, err := parseMaybeJSON(rest[next:], d) 356 if err != nil { 357 return nil, nil, err 358 } 359 360 return &Node{Value: typ, Next: cmd}, attrs, err 361 }