Signed-off-by: Daniel Nephin <dnephin@docker.com>
| ... | ... |
@@ -12,6 +12,7 @@ import ( |
| 12 | 12 |
"unicode" |
| 13 | 13 |
|
| 14 | 14 |
"github.com/docker/docker/builder/dockerfile/command" |
| 15 |
+ "github.com/pkg/errors" |
|
| 15 | 16 |
) |
| 16 | 17 |
|
| 17 | 18 |
// Node is a structure used to represent a parse tree. |
| ... | ... |
@@ -77,7 +78,7 @@ const DefaultEscapeToken = '\\' |
| 77 | 77 |
type Directive struct {
|
| 78 | 78 |
escapeToken rune // Current escape token |
| 79 | 79 |
lineContinuationRegex *regexp.Regexp // Current line continuation regex |
| 80 |
- lookingForDirectives bool // Whether we are currently looking for directives |
|
| 80 |
+ processingComplete bool // Whether we are done looking for directives |
|
| 81 | 81 |
escapeSeen bool // Whether the escape directive has been seen |
| 82 | 82 |
} |
| 83 | 83 |
|
| ... | ... |
@@ -91,12 +92,35 @@ func (d *Directive) setEscapeToken(s string) error {
|
| 91 | 91 |
return nil |
| 92 | 92 |
} |
| 93 | 93 |
|
| 94 |
+// processLine looks for a parser directive '# escapeToken=<char>. Parser |
|
| 95 |
+// directives must precede any builder instruction or other comments, and cannot |
|
| 96 |
+// be repeated. |
|
| 97 |
+func (d *Directive) processLine(line string) error {
|
|
| 98 |
+ if d.processingComplete {
|
|
| 99 |
+ return nil |
|
| 100 |
+ } |
|
| 101 |
+ // Processing is finished after the first call |
|
| 102 |
+ defer func() { d.processingComplete = true }()
|
|
| 103 |
+ |
|
| 104 |
+ tecMatch := tokenEscapeCommand.FindStringSubmatch(strings.ToLower(line)) |
|
| 105 |
+ if len(tecMatch) == 0 {
|
|
| 106 |
+ return nil |
|
| 107 |
+ } |
|
| 108 |
+ if d.escapeSeen == true {
|
|
| 109 |
+ return errors.New("only one escape parser directive can be used")
|
|
| 110 |
+ } |
|
| 111 |
+ for i, n := range tokenEscapeCommand.SubexpNames() {
|
|
| 112 |
+ if n == "escapechar" {
|
|
| 113 |
+ d.escapeSeen = true |
|
| 114 |
+ return d.setEscapeToken(tecMatch[i]) |
|
| 115 |
+ } |
|
| 116 |
+ } |
|
| 117 |
+ return nil |
|
| 118 |
+} |
|
| 119 |
+ |
|
| 94 | 120 |
// NewDefaultDirective returns a new Directive with the default escapeToken token |
| 95 | 121 |
func NewDefaultDirective() *Directive {
|
| 96 |
- directive := Directive{
|
|
| 97 |
- escapeSeen: false, |
|
| 98 |
- lookingForDirectives: true, |
|
| 99 |
- } |
|
| 122 |
+ directive := Directive{}
|
|
| 100 | 123 |
directive.setEscapeToken(string(DefaultEscapeToken)) |
| 101 | 124 |
return &directive |
| 102 | 125 |
} |
| ... | ... |
@@ -132,13 +156,10 @@ func init() {
|
| 132 | 132 |
|
| 133 | 133 |
// ParseLine parses a line and returns the remainder. |
| 134 | 134 |
func ParseLine(line string, d *Directive, ignoreCont bool) (string, *Node, error) {
|
| 135 |
- if escapeFound, err := handleParserDirective(line, d); err != nil || escapeFound {
|
|
| 136 |
- d.escapeSeen = escapeFound |
|
| 135 |
+ if err := d.processLine(line); err != nil {
|
|
| 137 | 136 |
return "", nil, err |
| 138 | 137 |
} |
| 139 | 138 |
|
| 140 |
- d.lookingForDirectives = false |
|
| 141 |
- |
|
| 142 | 139 |
if line = stripComments(line); line == "" {
|
| 143 | 140 |
return "", nil, nil |
| 144 | 141 |
} |
| ... | ... |
@@ -180,44 +201,27 @@ func newNodeFromLine(line string, directive *Directive) (*Node, error) {
|
| 180 | 180 |
}, nil |
| 181 | 181 |
} |
| 182 | 182 |
|
| 183 |
-// Handle the parser directive '# escapeToken=<char>. Parser directives must precede |
|
| 184 |
-// any builder instruction or other comments, and cannot be repeated. |
|
| 185 |
-func handleParserDirective(line string, d *Directive) (bool, error) {
|
|
| 186 |
- if !d.lookingForDirectives {
|
|
| 187 |
- return false, nil |
|
| 188 |
- } |
|
| 189 |
- tecMatch := tokenEscapeCommand.FindStringSubmatch(strings.ToLower(line)) |
|
| 190 |
- if len(tecMatch) == 0 {
|
|
| 191 |
- return false, nil |
|
| 192 |
- } |
|
| 193 |
- if d.escapeSeen == true {
|
|
| 194 |
- return false, fmt.Errorf("only one escape parser directive can be used")
|
|
| 195 |
- } |
|
| 196 |
- for i, n := range tokenEscapeCommand.SubexpNames() {
|
|
| 197 |
- if n == "escapechar" {
|
|
| 198 |
- if err := d.setEscapeToken(tecMatch[i]); err != nil {
|
|
| 199 |
- return false, err |
|
| 200 |
- } |
|
| 201 |
- return true, nil |
|
| 202 |
- } |
|
| 203 |
- } |
|
| 204 |
- return false, nil |
|
| 205 |
-} |
|
| 206 |
- |
|
| 207 | 183 |
// Result is the result of parsing a Dockerfile |
| 208 | 184 |
type Result struct {
|
| 209 | 185 |
AST *Node |
| 210 | 186 |
EscapeToken rune |
| 211 | 187 |
} |
| 212 | 188 |
|
| 189 |
+// scanLines is a split function for bufio.Scanner. that augments the default |
|
| 190 |
+// line scanner by supporting newline escapes. |
|
| 191 |
+func scanLines(data []byte, atEOF bool) (int, []byte, error) {
|
|
| 192 |
+ advance, token, err := bufio.ScanLines(data, atEOF) |
|
| 193 |
+ return advance, token, err |
|
| 194 |
+} |
|
| 195 |
+ |
|
| 213 | 196 |
// Parse reads lines from a Reader, parses the lines into an AST and returns |
| 214 | 197 |
// the AST and escape token |
| 215 | 198 |
func Parse(rwc io.Reader) (*Result, error) {
|
| 216 | 199 |
d := NewDefaultDirective() |
| 217 | 200 |
currentLine := 0 |
| 218 |
- root := &Node{}
|
|
| 219 |
- root.StartLine = -1 |
|
| 201 |
+ root := &Node{StartLine: -1}
|
|
| 220 | 202 |
scanner := bufio.NewScanner(rwc) |
| 203 |
+ scanner.Split(scanLines) |
|
| 221 | 204 |
|
| 222 | 205 |
utf8bom := []byte{0xEF, 0xBB, 0xBF}
|
| 223 | 206 |
for scanner.Scan() {
|
| ... | ... |
@@ -226,6 +230,7 @@ func Parse(rwc io.Reader) (*Result, error) {
|
| 226 | 226 |
if currentLine == 0 {
|
| 227 | 227 |
scannedBytes = bytes.TrimPrefix(scannedBytes, utf8bom) |
| 228 | 228 |
} |
| 229 |
+ |
|
| 229 | 230 |
scannedLine := strings.TrimLeftFunc(string(scannedBytes), unicode.IsSpace) |
| 230 | 231 |
currentLine++ |
| 231 | 232 |
line, child, err := ParseLine(scannedLine, d, false) |
| ... | ... |
@@ -59,7 +59,7 @@ func TestTestData(t *testing.T) {
|
| 59 | 59 |
content = bytes.Replace(content, []byte{'\x0d', '\x0a'}, []byte{'\x0a'}, -1)
|
| 60 | 60 |
} |
| 61 | 61 |
|
| 62 |
- assert.Equal(t, result.AST.Dump()+"\n", string(content)) |
|
| 62 |
+ assert.Equal(t, result.AST.Dump()+"\n", string(content), "In "+dockerfile) |
|
| 63 | 63 |
} |
| 64 | 64 |
} |
| 65 | 65 |
|