diff --git a/components/engine/builder/dockerfile/parser/parser.go b/components/engine/builder/dockerfile/parser/parser.go index 1b2dc8f3a0..2a884ceff6 100644 --- a/components/engine/builder/dockerfile/parser/parser.go +++ b/components/engine/builder/dockerfile/parser/parser.go @@ -7,6 +7,7 @@ import ( "fmt" "io" "regexp" + "strconv" "strings" "unicode" @@ -36,6 +37,31 @@ type Node struct { EndLine int // the line in the original dockerfile where the node ends } +// Dump dumps the AST defined by `node` as a list of sexps. +// Returns a string suitable for printing. +func (node *Node) Dump() string { + str := "" + str += node.Value + + if len(node.Flags) > 0 { + str += fmt.Sprintf(" %q", node.Flags) + } + + for _, n := range node.Children { + str += "(" + n.Dump() + ")\n" + } + + for n := node.Next; n != nil; n = n.Next { + if len(n.Children) > 0 { + str += " " + n.Dump() + } else { + str += " " + strconv.Quote(n.Value) + } + } + + return strings.TrimSpace(str) +} + // Directive is the structure used during a build run to hold the state of // parsing directives. type Directive struct { @@ -96,24 +122,9 @@ func init() { // ParseLine parses a line and returns the remainder. func ParseLine(line string, d *Directive, ignoreCont bool) (string, *Node, error) { - // Handle the parser directive '# escape=. Parser directives must precede - // any builder instruction or other comments, and cannot be repeated. - if d.LookingForDirectives { - tecMatch := tokenEscapeCommand.FindStringSubmatch(strings.ToLower(line)) - if len(tecMatch) > 0 { - if d.EscapeSeen == true { - return "", nil, fmt.Errorf("only one escape parser directive can be used") - } - for i, n := range tokenEscapeCommand.SubexpNames() { - if n == "escapechar" { - if err := SetEscapeToken(tecMatch[i], d); err != nil { - return "", nil, err - } - d.EscapeSeen = true - return "", nil, nil - } - } - } + if escapeFound, err := handleParserDirective(line, d); err != nil || escapeFound { + d.EscapeSeen = escapeFound + return "", nil, err } d.LookingForDirectives = false @@ -127,25 +138,60 @@ func ParseLine(line string, d *Directive, ignoreCont bool) (string, *Node, error return line, nil, nil } + node, err := newNodeFromLine(line, d) + return "", node, err +} + +// newNodeFromLine splits the line into parts, and dispatches to a function +// based on the command and command arguments. A Node is created from the +// result of the dispatch. +func newNodeFromLine(line string, directive *Directive) (*Node, error) { cmd, flags, args, err := splitCommand(line) if err != nil { - return "", nil, err + return nil, err } - node := &Node{} - node.Value = cmd - - sexp, attrs, err := fullDispatch(cmd, args, d) + fn := dispatch[cmd] + // Ignore invalid Dockerfile instructions + if fn == nil { + fn = parseIgnore + } + next, attrs, err := fn(args, directive) if err != nil { - return "", nil, err + return nil, err } - node.Next = sexp - node.Attributes = attrs - node.Original = line - node.Flags = flags + return &Node{ + Value: cmd, + Original: line, + Flags: flags, + Next: next, + Attributes: attrs, + }, nil +} - return "", node, nil +// Handle the parser directive '# escape=. Parser directives must precede +// any builder instruction or other comments, and cannot be repeated. +func handleParserDirective(line string, d *Directive) (bool, error) { + if !d.LookingForDirectives { + return false, nil + } + tecMatch := tokenEscapeCommand.FindStringSubmatch(strings.ToLower(line)) + if len(tecMatch) == 0 { + return false, nil + } + if d.EscapeSeen == true { + return false, fmt.Errorf("only one escape parser directive can be used") + } + for i, n := range tokenEscapeCommand.SubexpNames() { + if n == "escapechar" { + if err := SetEscapeToken(tecMatch[i], d); err != nil { + return false, err + } + return true, nil + } + } + return false, nil } // Parse is the main parse routine. @@ -219,3 +265,14 @@ func Parse(rwc io.Reader, d *Directive) (*Node, error) { return root, nil } + +// covers comments and empty lines. Lines should be trimmed before passing to +// this function. +func stripComments(line string) string { + // string is already trimmed at this point + if tokenComment.MatchString(line) { + return tokenComment.ReplaceAllString(line, "") + } + + return line +} diff --git a/components/engine/builder/dockerfile/parser/utils.go b/components/engine/builder/dockerfile/parser/split_command.go similarity index 66% rename from components/engine/builder/dockerfile/parser/utils.go rename to components/engine/builder/dockerfile/parser/split_command.go index 1f77f057ce..171f454f6d 100644 --- a/components/engine/builder/dockerfile/parser/utils.go +++ b/components/engine/builder/dockerfile/parser/split_command.go @@ -1,55 +1,10 @@ package parser import ( - "fmt" - "strconv" "strings" "unicode" ) -// Dump dumps the AST defined by `node` as a list of sexps. -// Returns a string suitable for printing. -func (node *Node) Dump() string { - str := "" - str += node.Value - - if len(node.Flags) > 0 { - str += fmt.Sprintf(" %q", node.Flags) - } - - for _, n := range node.Children { - str += "(" + n.Dump() + ")\n" - } - - for n := node.Next; n != nil; n = n.Next { - if len(n.Children) > 0 { - str += " " + n.Dump() - } else { - str += " " + strconv.Quote(n.Value) - } - } - - return strings.TrimSpace(str) -} - -// performs the dispatch based on the two primal strings, cmd and args. Please -// look at the dispatch table in parser.go to see how these dispatchers work. -func fullDispatch(cmd, args string, d *Directive) (*Node, map[string]bool, error) { - fn := dispatch[cmd] - - // Ignore invalid Dockerfile instructions - if fn == nil { - fn = parseIgnore - } - - sexp, attrs, err := fn(args, d) - if err != nil { - return nil, nil, err - } - - return sexp, attrs, nil -} - // splitCommand takes a single line of text and parses out the cmd and args, // which are used for dispatching to more exact parsing functions. func splitCommand(line string) (string, []string, string, error) { @@ -71,17 +26,6 @@ func splitCommand(line string) (string, []string, string, error) { return cmd, flags, strings.TrimSpace(args), nil } -// covers comments and empty lines. Lines should be trimmed before passing to -// this function. -func stripComments(line string) string { - // string is already trimmed at this point - if tokenComment.MatchString(line) { - return tokenComment.ReplaceAllString(line, "") - } - - return line -} - func extractBuilderFlags(line string) (string, []string, error) { // Parses the BuilderFlags and returns the remaining part of the line