github.com/stchris/docker@v1.4.2-0.20150106053530-1510a324dbd5/builder/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 "strconv" 14 "strings" 15 "unicode" 16 ) 17 18 var ( 19 errDockerfileJSONNesting = errors.New("You may not nest arrays in Dockerfile statements.") 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) (*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) (*Node, map[string]bool, error) { 34 _, child, err := parseLine(rest) 35 if err != nil { 36 return nil, nil, err 37 } 38 39 return &Node{Children: []*Node{child}}, nil, nil 40 } 41 42 // parse environment like statements. Note that this does *not* handle 43 // variable interpolation, which will be handled in the evaluator. 44 func parseEnv(rest string) (*Node, map[string]bool, error) { 45 // This is kind of tricky because we need to support the old 46 // variant: ENV name value 47 // as well as the new one: ENV name=value ... 48 // The trigger to know which one is being used will be whether we hit 49 // a space or = first. space ==> old, "=" ==> new 50 51 const ( 52 inSpaces = iota // looking for start of a word 53 inWord 54 inQuote 55 ) 56 57 words := []string{} 58 phase := inSpaces 59 word := "" 60 quote := '\000' 61 blankOK := false 62 var ch rune 63 64 for pos := 0; pos <= len(rest); pos++ { 65 if pos != len(rest) { 66 ch = rune(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 thru 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 // Look for = and if no there assume 91 // we're doing the old stuff and 92 // just read the rest of the line 93 if !strings.Contains(word, "=") { 94 word = strings.TrimSpace(rest[pos:]) 95 words = append(words, word) 96 break 97 } 98 } 99 word = "" 100 blankOK = false 101 continue 102 } 103 if ch == '\'' || ch == '"' { 104 quote = ch 105 blankOK = true 106 phase = inQuote 107 continue 108 } 109 if ch == '\\' { 110 if pos+1 == len(rest) { 111 continue // just skip \ at end 112 } 113 pos++ 114 ch = rune(rest[pos]) 115 } 116 word += string(ch) 117 continue 118 } 119 if phase == inQuote { 120 if ch == quote { 121 phase = inWord 122 continue 123 } 124 if ch == '\\' { 125 if pos+1 == len(rest) { 126 phase = inWord 127 continue // just skip \ at end 128 } 129 pos++ 130 ch = rune(rest[pos]) 131 } 132 word += string(ch) 133 } 134 } 135 136 if len(words) == 0 { 137 return nil, nil, fmt.Errorf("ENV must have some arguments") 138 } 139 140 // Old format (ENV name value) 141 var rootnode *Node 142 143 if !strings.Contains(words[0], "=") { 144 node := &Node{} 145 rootnode = node 146 strs := TOKEN_WHITESPACE.Split(rest, 2) 147 148 if len(strs) < 2 { 149 return nil, nil, fmt.Errorf("ENV must have two arguments") 150 } 151 152 node.Value = strs[0] 153 node.Next = &Node{} 154 node.Next.Value = strs[1] 155 } else { 156 var prevNode *Node 157 for i, word := range words { 158 if !strings.Contains(word, "=") { 159 return nil, nil, fmt.Errorf("Syntax error - can't find = in %q. Must be of the form: name=value", word) 160 } 161 parts := strings.SplitN(word, "=", 2) 162 163 name := &Node{} 164 value := &Node{} 165 166 name.Next = value 167 name.Value = parts[0] 168 value.Value = parts[1] 169 170 if i == 0 { 171 rootnode = name 172 } else { 173 prevNode.Next = name 174 } 175 prevNode = value 176 } 177 } 178 179 return rootnode, nil, nil 180 } 181 182 // parses a whitespace-delimited set of arguments. The result is effectively a 183 // linked list of string arguments. 184 func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) { 185 node := &Node{} 186 rootnode := node 187 prevnode := node 188 for _, str := range TOKEN_WHITESPACE.Split(rest, -1) { // use regexp 189 prevnode = node 190 node.Value = str 191 node.Next = &Node{} 192 node = node.Next 193 } 194 195 // XXX to get around regexp.Split *always* providing an empty string at the 196 // end due to how our loop is constructed, nil out the last node in the 197 // chain. 198 prevnode.Next = nil 199 200 return rootnode, nil, nil 201 } 202 203 // parsestring just wraps the string in quotes and returns a working node. 204 func parseString(rest string) (*Node, map[string]bool, error) { 205 n := &Node{} 206 n.Value = rest 207 return n, nil, nil 208 } 209 210 // parseJSON converts JSON arrays to an AST. 211 func parseJSON(rest string) (*Node, map[string]bool, error) { 212 var ( 213 myJson []interface{} 214 next = &Node{} 215 orignext = next 216 prevnode = next 217 ) 218 219 if err := json.Unmarshal([]byte(rest), &myJson); err != nil { 220 return nil, nil, err 221 } 222 223 for _, str := range myJson { 224 switch str.(type) { 225 case string: 226 case float64: 227 str = strconv.FormatFloat(str.(float64), 'G', -1, 64) 228 default: 229 return nil, nil, errDockerfileJSONNesting 230 } 231 next.Value = str.(string) 232 next.Next = &Node{} 233 prevnode = next 234 next = next.Next 235 } 236 237 prevnode.Next = nil 238 239 return orignext, map[string]bool{"json": true}, nil 240 } 241 242 // parseMaybeJSON determines if the argument appears to be a JSON array. If 243 // so, passes to parseJSON; if not, quotes the result and returns a single 244 // node. 245 func parseMaybeJSON(rest string) (*Node, map[string]bool, error) { 246 rest = strings.TrimSpace(rest) 247 248 node, attrs, err := parseJSON(rest) 249 250 if err == nil { 251 return node, attrs, nil 252 } 253 if err == errDockerfileJSONNesting { 254 return nil, nil, err 255 } 256 257 node = &Node{} 258 node.Value = rest 259 return node, nil, nil 260 } 261 262 // parseMaybeJSONToList determines if the argument appears to be a JSON array. If 263 // so, passes to parseJSON; if not, attmpts to parse it as a whitespace 264 // delimited string. 265 func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) { 266 rest = strings.TrimSpace(rest) 267 268 node, attrs, err := parseJSON(rest) 269 270 if err == nil { 271 return node, attrs, nil 272 } 273 if err == errDockerfileJSONNesting { 274 return nil, nil, err 275 } 276 277 return parseStringsWhitespaceDelimited(rest) 278 }