Refactor some builder code

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2017-03-10 16:23:46 -05:00
parent b86efd2707
commit d7807c7316
2 changed files with 86 additions and 85 deletions

View File

@ -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=<char>. 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=<char>. 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
}

View File

@ -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