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