github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/builder/dockerfile/parser/utils.go (about) 1 package parser 2 3 import ( 4 "fmt" 5 "strconv" 6 "strings" 7 "unicode" 8 ) 9 10 // Dump dumps the AST defined by `node` as a list of sexps. 11 // Returns a string suitable for printing. 12 func (node *Node) Dump() string { 13 str := "" 14 str += node.Value 15 16 if len(node.Flags) > 0 { 17 str += fmt.Sprintf(" %q", node.Flags) 18 } 19 20 for _, n := range node.Children { 21 str += "(" + n.Dump() + ")\n" 22 } 23 24 for n := node.Next; n != nil; n = n.Next { 25 if len(n.Children) > 0 { 26 str += " " + n.Dump() 27 } else { 28 str += " " + strconv.Quote(n.Value) 29 } 30 } 31 32 return strings.TrimSpace(str) 33 } 34 35 // performs the dispatch based on the two primal strings, cmd and args. Please 36 // look at the dispatch table in parser.go to see how these dispatchers work. 37 func fullDispatch(cmd, args string, d *Directive) (*Node, map[string]bool, error) { 38 fn := dispatch[cmd] 39 40 // Ignore invalid Dockerfile instructions 41 if fn == nil { 42 fn = parseIgnore 43 } 44 45 sexp, attrs, err := fn(args, d) 46 if err != nil { 47 return nil, nil, err 48 } 49 50 return sexp, attrs, nil 51 } 52 53 // splitCommand takes a single line of text and parses out the cmd and args, 54 // which are used for dispatching to more exact parsing functions. 55 func splitCommand(line string) (string, []string, string, error) { 56 var args string 57 var flags []string 58 59 // Make sure we get the same results irrespective of leading/trailing spaces 60 cmdline := tokenWhitespace.Split(strings.TrimSpace(line), 2) 61 cmd := strings.ToLower(cmdline[0]) 62 63 if len(cmdline) == 2 { 64 var err error 65 args, flags, err = extractBuilderFlags(cmdline[1]) 66 if err != nil { 67 return "", nil, "", err 68 } 69 } 70 71 return cmd, flags, strings.TrimSpace(args), nil 72 } 73 74 // covers comments and empty lines. Lines should be trimmed before passing to 75 // this function. 76 func stripComments(line string) string { 77 // string is already trimmed at this point 78 if tokenComment.MatchString(line) { 79 return tokenComment.ReplaceAllString(line, "") 80 } 81 82 return line 83 } 84 85 func extractBuilderFlags(line string) (string, []string, error) { 86 // Parses the BuilderFlags and returns the remaining part of the line 87 88 const ( 89 inSpaces = iota // looking for start of a word 90 inWord 91 inQuote 92 ) 93 94 words := []string{} 95 phase := inSpaces 96 word := "" 97 quote := '\000' 98 blankOK := false 99 var ch rune 100 101 for pos := 0; pos <= len(line); pos++ { 102 if pos != len(line) { 103 ch = rune(line[pos]) 104 } 105 106 if phase == inSpaces { // Looking for start of word 107 if pos == len(line) { // end of input 108 break 109 } 110 if unicode.IsSpace(ch) { // skip spaces 111 continue 112 } 113 114 // Only keep going if the next word starts with -- 115 if ch != '-' || pos+1 == len(line) || rune(line[pos+1]) != '-' { 116 return line[pos:], words, nil 117 } 118 119 phase = inWord // found someting with "--", fall through 120 } 121 if (phase == inWord || phase == inQuote) && (pos == len(line)) { 122 if word != "--" && (blankOK || len(word) > 0) { 123 words = append(words, word) 124 } 125 break 126 } 127 if phase == inWord { 128 if unicode.IsSpace(ch) { 129 phase = inSpaces 130 if word == "--" { 131 return line[pos:], words, nil 132 } 133 if blankOK || len(word) > 0 { 134 words = append(words, word) 135 } 136 word = "" 137 blankOK = false 138 continue 139 } 140 if ch == '\'' || ch == '"' { 141 quote = ch 142 blankOK = true 143 phase = inQuote 144 continue 145 } 146 if ch == '\\' { 147 if pos+1 == len(line) { 148 continue // just skip \ at end 149 } 150 pos++ 151 ch = rune(line[pos]) 152 } 153 word += string(ch) 154 continue 155 } 156 if phase == inQuote { 157 if ch == quote { 158 phase = inWord 159 continue 160 } 161 if ch == '\\' { 162 if pos+1 == len(line) { 163 phase = inWord 164 continue // just skip \ at end 165 } 166 pos++ 167 ch = rune(line[pos]) 168 } 169 word += string(ch) 170 } 171 } 172 173 return "", words, nil 174 }