line_parsers.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. package parser
  2. // line parsers are dispatch calls that parse a single unit of text into a
  3. // Node object which contains the whole statement. Dockerfiles have varied
  4. // (but not usually unique, see ONBUILD for a unique example) parsing rules
  5. // per-command, and these unify the processing in a way that makes it
  6. // manageable.
  7. import (
  8. "encoding/json"
  9. "errors"
  10. "fmt"
  11. "strconv"
  12. "strings"
  13. )
  14. var (
  15. errDockerfileJSONNesting = errors.New("You may not nest arrays in Dockerfile statements.")
  16. )
  17. // ignore the current argument. This will still leave a command parsed, but
  18. // will not incorporate the arguments into the ast.
  19. func parseIgnore(rest string) (*Node, map[string]bool, error) {
  20. return &Node{}, nil, nil
  21. }
  22. // used for onbuild. Could potentially be used for anything that represents a
  23. // statement with sub-statements.
  24. //
  25. // ONBUILD RUN foo bar -> (onbuild (run foo bar))
  26. //
  27. func parseSubCommand(rest string) (*Node, map[string]bool, error) {
  28. _, child, err := parseLine(rest)
  29. if err != nil {
  30. return nil, nil, err
  31. }
  32. return &Node{Children: []*Node{child}}, nil, nil
  33. }
  34. // parse environment like statements. Note that this does *not* handle
  35. // variable interpolation, which will be handled in the evaluator.
  36. func parseEnv(rest string) (*Node, map[string]bool, error) {
  37. node := &Node{}
  38. rootnode := node
  39. strs := TOKEN_WHITESPACE.Split(rest, 2)
  40. if len(strs) < 2 {
  41. return nil, nil, fmt.Errorf("ENV must have two arguments")
  42. }
  43. node.Value = strs[0]
  44. node.Next = &Node{}
  45. node.Next.Value = strs[1]
  46. return rootnode, nil, nil
  47. }
  48. // parses a whitespace-delimited set of arguments. The result is effectively a
  49. // linked list of string arguments.
  50. func parseStringsWhitespaceDelimited(rest string) (*Node, map[string]bool, error) {
  51. node := &Node{}
  52. rootnode := node
  53. prevnode := node
  54. for _, str := range TOKEN_WHITESPACE.Split(rest, -1) { // use regexp
  55. prevnode = node
  56. node.Value = str
  57. node.Next = &Node{}
  58. node = node.Next
  59. }
  60. // XXX to get around regexp.Split *always* providing an empty string at the
  61. // end due to how our loop is constructed, nil out the last node in the
  62. // chain.
  63. prevnode.Next = nil
  64. return rootnode, nil, nil
  65. }
  66. // parsestring just wraps the string in quotes and returns a working node.
  67. func parseString(rest string) (*Node, map[string]bool, error) {
  68. n := &Node{}
  69. n.Value = rest
  70. return n, nil, nil
  71. }
  72. // parseJSON converts JSON arrays to an AST.
  73. func parseJSON(rest string) (*Node, map[string]bool, error) {
  74. var (
  75. myJson []interface{}
  76. next = &Node{}
  77. orignext = next
  78. prevnode = next
  79. )
  80. if err := json.Unmarshal([]byte(rest), &myJson); err != nil {
  81. return nil, nil, err
  82. }
  83. for _, str := range myJson {
  84. switch str.(type) {
  85. case string:
  86. case float64:
  87. str = strconv.FormatFloat(str.(float64), 'G', -1, 64)
  88. default:
  89. return nil, nil, errDockerfileJSONNesting
  90. }
  91. next.Value = str.(string)
  92. next.Next = &Node{}
  93. prevnode = next
  94. next = next.Next
  95. }
  96. prevnode.Next = nil
  97. return orignext, map[string]bool{"json": true}, nil
  98. }
  99. // parseMaybeJSON determines if the argument appears to be a JSON array. If
  100. // so, passes to parseJSON; if not, quotes the result and returns a single
  101. // node.
  102. func parseMaybeJSON(rest string) (*Node, map[string]bool, error) {
  103. rest = strings.TrimSpace(rest)
  104. node, attrs, err := parseJSON(rest)
  105. if err == nil {
  106. return node, attrs, nil
  107. }
  108. if err == errDockerfileJSONNesting {
  109. return nil, nil, err
  110. }
  111. node = &Node{}
  112. node.Value = rest
  113. return node, nil, nil
  114. }