line_parsers.go 3.3 KB

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