parser.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // This package implements a parser and parse tree dumper for Dockerfiles.
  2. package parser
  3. import (
  4. "bufio"
  5. "io"
  6. "regexp"
  7. "strings"
  8. )
  9. // Node is the building block of the AST this package will create.
  10. //
  11. // Nodes are structured to have a value, next, and child, the latter two of
  12. // which are Nodes themselves.
  13. //
  14. // This terminology is unfortunately rather confusing, so here's a diagram.
  15. // Anything after the ; is a comment.
  16. //
  17. // (
  18. // (run "foo") ; value run, and next is a value foo.
  19. // (run "1" "2" "3") ;
  20. // (something (really cool))
  21. // )
  22. //
  23. // Will give you something like this:
  24. //
  25. // &Node{
  26. // Value:"",
  27. // Child: &Node{Value: "run", Next: &Node{Value: "foo"}, Child: nil},
  28. // Next: &Node{Value:"", Child: &Node{Value:"run", Next: &Node{Value:`"1"`....
  29. //
  30. // ... and so on.
  31. //
  32. // The short and fast rule is that anything that starts with ( is a child of
  33. // something. Anything which follows a previous statement is a next of
  34. // something.
  35. //
  36. type Node struct {
  37. Value string // actual content
  38. Next *Node // the next item in the current sexp
  39. Children []*Node // the children of this sexp
  40. }
  41. var (
  42. dispatch map[string]func(string) (*Node, error)
  43. TOKEN_WHITESPACE = regexp.MustCompile(`\s+`)
  44. TOKEN_LINE_CONTINUATION = regexp.MustCompile(`\\$`)
  45. TOKEN_COMMENT = regexp.MustCompile(`^#.*$`)
  46. )
  47. func init() {
  48. // Dispatch Table. see line_parsers.go for the parse functions.
  49. // The command is parsed and mapped to the line parser. The line parser
  50. // recieves the arguments but not the command, and returns an AST after
  51. // reformulating the arguments according to the rules in the parser
  52. // functions. Errors are propogated up by Parse() and the resulting AST can
  53. // be incorporated directly into the existing AST as a next.
  54. dispatch = map[string]func(string) (*Node, error){
  55. "user": parseString,
  56. "onbuild": parseSubCommand,
  57. "workdir": parseString,
  58. "env": parseEnv,
  59. "maintainer": parseString,
  60. "docker-version": parseString,
  61. "from": parseString,
  62. "add": parseStringsWhitespaceDelimited,
  63. "copy": parseStringsWhitespaceDelimited,
  64. "run": parseMaybeJSON,
  65. "cmd": parseMaybeJSON,
  66. "entrypoint": parseMaybeJSON,
  67. "expose": parseStringsWhitespaceDelimited,
  68. "volume": parseMaybeJSON,
  69. }
  70. }
  71. // empty node. Useful for managing structure.
  72. func blankNode() *Node {
  73. return &Node{"", nil, []*Node{}}
  74. }
  75. func parseLine(line string) (string, *Node, error) {
  76. if line = stripComments(line); line == "" {
  77. return "", nil, nil
  78. }
  79. if TOKEN_LINE_CONTINUATION.MatchString(line) {
  80. line = TOKEN_LINE_CONTINUATION.ReplaceAllString(line, "")
  81. return line, nil, nil
  82. }
  83. cmd, args := splitCommand(line)
  84. node := blankNode()
  85. node.Value = cmd
  86. sexp, err := fullDispatch(cmd, args)
  87. if err != nil {
  88. return "", nil, err
  89. }
  90. node.Next = sexp
  91. return "", node, nil
  92. }
  93. // The main parse routine. Handles an io.ReadWriteCloser and returns the root
  94. // of the AST.
  95. func Parse(rwc io.Reader) (*Node, error) {
  96. var child *Node
  97. var line string
  98. var err error
  99. root := blankNode()
  100. scanner := bufio.NewScanner(rwc)
  101. for scanner.Scan() {
  102. line, child, err = parseLine(strings.TrimSpace(scanner.Text()))
  103. if err != nil {
  104. return nil, err
  105. }
  106. if line != "" && child == nil {
  107. for {
  108. scanner.Scan()
  109. newline := strings.TrimSpace(scanner.Text())
  110. if newline == "" {
  111. continue
  112. }
  113. line, child, err = parseLine(line + newline)
  114. if err != nil {
  115. return nil, err
  116. }
  117. if child != nil {
  118. break
  119. }
  120. }
  121. }
  122. if child != nil {
  123. root.Children = append(root.Children, child)
  124. }
  125. }
  126. return root, nil
  127. }