2014-08-05 16:17:40 -04:00
|
|
|
package parser
|
|
|
|
|
2014-08-07 01:56:44 -04:00
|
|
|
// line parsers are dispatch calls that parse a single unit of text into a
|
|
|
|
// Node object which contains the whole statement. Dockerfiles have varied
|
|
|
|
// (but not usually unique, see ONBUILD for a unique example) parsing rules
|
|
|
|
// per-command, and these unify the processing in a way that makes it
|
|
|
|
// manageable.
|
|
|
|
|
2014-08-05 16:17:40 -04:00
|
|
|
import (
|
|
|
|
"encoding/json"
|
2014-08-07 03:42:10 -04:00
|
|
|
"errors"
|
2014-08-05 16:17:40 -04:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
2014-08-07 03:42:10 -04:00
|
|
|
var (
|
|
|
|
dockerFileErrJSONNesting = errors.New("You may not nest arrays in Dockerfile statements.")
|
|
|
|
)
|
|
|
|
|
2014-08-05 16:17:40 -04:00
|
|
|
// ignore the current argument. This will still leave a command parsed, but
|
|
|
|
// will not incorporate the arguments into the ast.
|
|
|
|
func parseIgnore(rest string) (*Node, error) {
|
|
|
|
return blankNode(), nil
|
|
|
|
}
|
|
|
|
|
2014-08-07 01:56:44 -04:00
|
|
|
// used for onbuild. Could potentially be used for anything that represents a
|
|
|
|
// statement with sub-statements.
|
|
|
|
//
|
|
|
|
// ONBUILD RUN foo bar -> (onbuild (run foo bar))
|
|
|
|
//
|
2014-08-05 16:17:40 -04:00
|
|
|
func parseSubCommand(rest string) (*Node, error) {
|
|
|
|
_, child, err := parseLine(rest)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Node{Children: []*Node{child}}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse environment like statements. Note that this does *not* handle
|
|
|
|
// variable interpolation, which will be handled in the evaluator.
|
|
|
|
func parseEnv(rest string) (*Node, error) {
|
|
|
|
node := blankNode()
|
|
|
|
rootnode := node
|
|
|
|
strs := TOKEN_WHITESPACE.Split(rest, 2)
|
2014-08-05 18:41:09 -04:00
|
|
|
node.Value = strs[0]
|
2014-08-05 16:17:40 -04:00
|
|
|
node.Next = blankNode()
|
2014-08-05 18:41:09 -04:00
|
|
|
node.Next.Value = strs[1]
|
2014-08-05 16:17:40 -04:00
|
|
|
|
|
|
|
return rootnode, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parses a whitespace-delimited set of arguments. The result is effectively a
|
|
|
|
// linked list of string arguments.
|
|
|
|
func parseStringsWhitespaceDelimited(rest string) (*Node, error) {
|
|
|
|
node := blankNode()
|
|
|
|
rootnode := node
|
2014-08-05 18:41:09 -04:00
|
|
|
prevnode := node
|
2014-08-05 16:17:40 -04:00
|
|
|
for _, str := range TOKEN_WHITESPACE.Split(rest, -1) { // use regexp
|
2014-08-05 18:41:09 -04:00
|
|
|
prevnode = node
|
|
|
|
node.Value = str
|
2014-08-05 16:17:40 -04:00
|
|
|
node.Next = blankNode()
|
|
|
|
node = node.Next
|
|
|
|
}
|
|
|
|
|
2014-08-05 18:41:09 -04:00
|
|
|
// XXX to get around regexp.Split *always* providing an empty string at the
|
|
|
|
// end due to how our loop is constructed, nil out the last node in the
|
|
|
|
// chain.
|
|
|
|
prevnode.Next = nil
|
|
|
|
|
2014-08-05 16:17:40 -04:00
|
|
|
return rootnode, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parsestring just wraps the string in quotes and returns a working node.
|
|
|
|
func parseString(rest string) (*Node, error) {
|
2014-08-05 18:41:09 -04:00
|
|
|
return &Node{rest, nil, nil}, nil
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// parseJSON converts JSON arrays to an AST.
|
|
|
|
func parseJSON(rest string) (*Node, error) {
|
|
|
|
var (
|
|
|
|
myJson []interface{}
|
|
|
|
next = blankNode()
|
|
|
|
orignext = next
|
2014-08-05 18:41:09 -04:00
|
|
|
prevnode = next
|
2014-08-05 16:17:40 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
if err := json.Unmarshal([]byte(rest), &myJson); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, str := range myJson {
|
|
|
|
switch str.(type) {
|
2014-08-07 03:42:10 -04:00
|
|
|
case []interface{}:
|
|
|
|
return nil, dockerFileErrJSONNesting
|
2014-08-05 16:17:40 -04:00
|
|
|
case float64:
|
|
|
|
str = strconv.FormatFloat(str.(float64), 'G', -1, 64)
|
|
|
|
}
|
2014-08-05 18:41:09 -04:00
|
|
|
next.Value = str.(string)
|
2014-08-05 16:17:40 -04:00
|
|
|
next.Next = blankNode()
|
2014-08-05 18:41:09 -04:00
|
|
|
prevnode = next
|
2014-08-05 16:17:40 -04:00
|
|
|
next = next.Next
|
|
|
|
}
|
|
|
|
|
2014-08-05 18:41:09 -04:00
|
|
|
prevnode.Next = nil
|
|
|
|
|
2014-08-05 16:17:40 -04:00
|
|
|
return orignext, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// parseMaybeJSON determines if the argument appears to be a JSON array. If
|
|
|
|
// so, passes to parseJSON; if not, quotes the result and returns a single
|
|
|
|
// node.
|
|
|
|
func parseMaybeJSON(rest string) (*Node, error) {
|
|
|
|
rest = strings.TrimSpace(rest)
|
|
|
|
|
|
|
|
if strings.HasPrefix(rest, "[") {
|
|
|
|
node, err := parseJSON(rest)
|
|
|
|
if err == nil {
|
|
|
|
return node, nil
|
2014-08-07 03:42:10 -04:00
|
|
|
} else if err == dockerFileErrJSONNesting {
|
|
|
|
return nil, err
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
node := blankNode()
|
2014-08-05 18:41:09 -04:00
|
|
|
node.Value = rest
|
2014-08-05 16:17:40 -04:00
|
|
|
return node, nil
|
|
|
|
}
|