2014-08-05 16:17:40 -04:00
|
|
|
package parser
|
|
|
|
|
2014-10-14 23:33:11 -04:00
|
|
|
import (
|
2015-01-27 10:57:34 -05:00
|
|
|
"fmt"
|
2015-01-03 00:36:58 -05:00
|
|
|
"strconv"
|
2014-10-14 23:33:11 -04:00
|
|
|
"strings"
|
2015-01-27 10:57:34 -05:00
|
|
|
"unicode"
|
2014-10-14 23:33:11 -04:00
|
|
|
)
|
2014-08-05 16:17:40 -04:00
|
|
|
|
2015-07-22 01:29:03 -04:00
|
|
|
// Dump dumps the AST defined by `node` as a list of sexps.
|
|
|
|
// Returns a string suitable for printing.
|
2014-08-05 16:17:40 -04:00
|
|
|
func (node *Node) Dump() string {
|
|
|
|
str := ""
|
|
|
|
str += node.Value
|
|
|
|
|
2015-01-27 10:57:34 -05:00
|
|
|
if len(node.Flags) > 0 {
|
|
|
|
str += fmt.Sprintf(" %q", node.Flags)
|
|
|
|
}
|
|
|
|
|
2014-08-05 16:17:40 -04:00
|
|
|
for _, n := range node.Children {
|
|
|
|
str += "(" + n.Dump() + ")\n"
|
|
|
|
}
|
|
|
|
|
|
|
|
if node.Next != nil {
|
|
|
|
for n := node.Next; n != nil; n = n.Next {
|
|
|
|
if len(n.Children) > 0 {
|
|
|
|
str += " " + n.Dump()
|
|
|
|
} else {
|
2015-01-03 00:36:58 -05:00
|
|
|
str += " " + strconv.Quote(n.Value)
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2014-08-13 06:07:41 -04:00
|
|
|
func fullDispatch(cmd, args string) (*Node, map[string]bool, error) {
|
2014-10-06 23:24:43 -04:00
|
|
|
fn := dispatch[cmd]
|
|
|
|
|
|
|
|
// Ignore invalid Dockerfile instructions
|
|
|
|
if fn == nil {
|
|
|
|
fn = parseIgnore
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
|
|
|
|
2014-10-06 23:24:43 -04:00
|
|
|
sexp, attrs, err := fn(args)
|
2014-08-05 16:17:40 -04:00
|
|
|
if err != nil {
|
2014-08-13 06:07:41 -04:00
|
|
|
return nil, nil, err
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
|
|
|
|
2014-08-13 06:07:41 -04:00
|
|
|
return sexp, attrs, nil
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// splitCommand takes a single line of text and parses out the cmd and args,
|
|
|
|
// which are used for dispatching to more exact parsing functions.
|
2015-01-27 10:57:34 -05:00
|
|
|
func splitCommand(line string) (string, []string, string, error) {
|
2015-02-04 12:34:25 -05:00
|
|
|
var args string
|
2015-01-27 10:57:34 -05:00
|
|
|
var flags []string
|
2015-02-04 12:34:25 -05:00
|
|
|
|
2015-01-15 09:49:48 -05:00
|
|
|
// Make sure we get the same results irrespective of leading/trailing spaces
|
2015-07-22 01:29:03 -04:00
|
|
|
cmdline := tokenWhitespace.Split(strings.TrimSpace(line), 2)
|
2015-02-04 12:34:25 -05:00
|
|
|
cmd := strings.ToLower(cmdline[0])
|
2014-10-14 23:33:11 -04:00
|
|
|
|
2015-02-04 12:34:25 -05:00
|
|
|
if len(cmdline) == 2 {
|
2015-01-27 10:57:34 -05:00
|
|
|
var err error
|
|
|
|
args, flags, err = extractBuilderFlags(cmdline[1])
|
|
|
|
if err != nil {
|
|
|
|
return "", nil, "", err
|
|
|
|
}
|
2014-10-14 23:33:11 -04:00
|
|
|
}
|
|
|
|
|
2015-01-27 10:57:34 -05:00
|
|
|
return cmd, flags, strings.TrimSpace(args), nil
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2015-07-22 01:29:03 -04:00
|
|
|
if tokenComment.MatchString(line) {
|
|
|
|
return tokenComment.ReplaceAllString(line, "")
|
2014-08-05 16:17:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return line
|
|
|
|
}
|
2015-01-27 10:57:34 -05:00
|
|
|
|
|
|
|
func extractBuilderFlags(line string) (string, []string, error) {
|
|
|
|
// Parses the BuilderFlags and returns the remaining part of the line
|
|
|
|
|
|
|
|
const (
|
|
|
|
inSpaces = iota // looking for start of a word
|
|
|
|
inWord
|
|
|
|
inQuote
|
|
|
|
)
|
|
|
|
|
|
|
|
words := []string{}
|
|
|
|
phase := inSpaces
|
|
|
|
word := ""
|
|
|
|
quote := '\000'
|
|
|
|
blankOK := false
|
|
|
|
var ch rune
|
|
|
|
|
|
|
|
for pos := 0; pos <= len(line); pos++ {
|
|
|
|
if pos != len(line) {
|
|
|
|
ch = rune(line[pos])
|
|
|
|
}
|
|
|
|
|
|
|
|
if phase == inSpaces { // Looking for start of word
|
|
|
|
if pos == len(line) { // end of input
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if unicode.IsSpace(ch) { // skip spaces
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only keep going if the next word starts with --
|
|
|
|
if ch != '-' || pos+1 == len(line) || rune(line[pos+1]) != '-' {
|
|
|
|
return line[pos:], words, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
phase = inWord // found someting with "--", fall thru
|
|
|
|
}
|
|
|
|
if (phase == inWord || phase == inQuote) && (pos == len(line)) {
|
|
|
|
if word != "--" && (blankOK || len(word) > 0) {
|
|
|
|
words = append(words, word)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if phase == inWord {
|
|
|
|
if unicode.IsSpace(ch) {
|
|
|
|
phase = inSpaces
|
|
|
|
if word == "--" {
|
|
|
|
return line[pos:], words, nil
|
|
|
|
}
|
|
|
|
if blankOK || len(word) > 0 {
|
|
|
|
words = append(words, word)
|
|
|
|
}
|
|
|
|
word = ""
|
|
|
|
blankOK = false
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if ch == '\'' || ch == '"' {
|
|
|
|
quote = ch
|
|
|
|
blankOK = true
|
|
|
|
phase = inQuote
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if ch == '\\' {
|
|
|
|
if pos+1 == len(line) {
|
|
|
|
continue // just skip \ at end
|
|
|
|
}
|
|
|
|
pos++
|
|
|
|
ch = rune(line[pos])
|
|
|
|
}
|
|
|
|
word += string(ch)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if phase == inQuote {
|
|
|
|
if ch == quote {
|
|
|
|
phase = inWord
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if ch == '\\' {
|
|
|
|
if pos+1 == len(line) {
|
|
|
|
phase = inWord
|
|
|
|
continue // just skip \ at end
|
|
|
|
}
|
|
|
|
pos++
|
|
|
|
ch = rune(line[pos])
|
|
|
|
}
|
|
|
|
word += string(ch)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", words, nil
|
|
|
|
}
|