utils.go 3.6 KB

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