utils.go 3.7 KB

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