Browse code

Hide builder.parser.Directive internals

Signed-off-by: Daniel Nephin <dnephin@docker.com>

Daniel Nephin authored on 2017/04/12 04:07:02
Showing 12 changed files
... ...
@@ -62,7 +62,7 @@ type Builder struct {
62 62
 	disableCommit bool
63 63
 	cacheBusted   bool
64 64
 	buildArgs     *buildArgs
65
-	directive     parser.Directive
65
+	directive     *parser.Directive
66 66
 
67 67
 	imageCache builder.ImageCache
68 68
 	from       builder.Image
... ...
@@ -122,14 +122,9 @@ func NewBuilder(clientCtx context.Context, config *types.ImageBuildOptions, back
122 122
 		runConfig:     new(container.Config),
123 123
 		tmpContainers: map[string]struct{}{},
124 124
 		buildArgs:     newBuildArgs(config.BuildArgs),
125
-		directive: parser.Directive{
126
-			EscapeSeen:           false,
127
-			LookingForDirectives: true,
128
-		},
125
+		directive:     parser.NewDefaultDirective(),
129 126
 	}
130 127
 	b.imageContexts = &imageContexts{b: b}
131
-
132
-	parser.SetEscapeToken(parser.DefaultEscapeToken, &b.directive) // Assume the default token for escape
133 128
 	return b, nil
134 129
 }
135 130
 
... ...
@@ -325,7 +320,7 @@ func BuildFromConfig(config *container.Config, changes []string) (*container.Con
325 325
 		return nil, err
326 326
 	}
327 327
 
328
-	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")), &b.directive)
328
+	ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n")), b.directive)
329 329
 	if err != nil {
330 330
 		return nil, err
331 331
 	}
... ...
@@ -10,9 +10,8 @@ import (
10 10
 
11 11
 func TestAddNodesForLabelOption(t *testing.T) {
12 12
 	dockerfile := "FROM scratch"
13
-	d := parser.Directive{}
14
-	parser.SetEscapeToken(parser.DefaultEscapeToken, &d)
15
-	nodes, err := parser.Parse(strings.NewReader(dockerfile), &d)
13
+	d := parser.NewDefaultDirective()
14
+	nodes, err := parser.Parse(strings.NewReader(dockerfile), d)
16 15
 	assert.NilError(t, err)
17 16
 
18 17
 	labels := map[string]string{
... ...
@@ -200,7 +200,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string
200 200
 		substituionArgs = append(substituionArgs, key+"="+value)
201 201
 	}
202 202
 
203
-	name, err := ProcessWord(args[0], substituionArgs, b.directive.EscapeToken)
203
+	name, err := ProcessWord(args[0], substituionArgs, b.directive.EscapeToken())
204 204
 	if err != nil {
205 205
 		return err
206 206
 	}
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/docker/docker/api/types/container"
11 11
 	"github.com/docker/docker/api/types/strslice"
12 12
 	"github.com/docker/docker/builder"
13
+	"github.com/docker/docker/builder/dockerfile/parser"
13 14
 	"github.com/docker/docker/pkg/testutil/assert"
14 15
 	"github.com/docker/go-connections/nat"
15 16
 )
... ...
@@ -204,6 +205,7 @@ func newBuilderWithMockBackend() *Builder {
204 204
 		options:   &types.ImageBuildOptions{},
205 205
 		docker:    &MockBackend{},
206 206
 		buildArgs: newBuildArgs(make(map[string]*string)),
207
+		directive: parser.NewDefaultDirective(),
207 208
 	}
208 209
 	b.imageContexts = &imageContexts{b: b}
209 210
 	return b
... ...
@@ -180,7 +180,7 @@ func (b *Builder) evaluateEnv(cmd string, str string, envs []string) ([]string,
180 180
 			return []string{word}, err
181 181
 		}
182 182
 	}
183
-	return processFunc(str, envs, b.directive.EscapeToken)
183
+	return processFunc(str, envs, b.directive.EscapeToken())
184 184
 }
185 185
 
186 186
 // buildArgsWithoutConfigEnv returns a list of key=value pairs for all the build
... ...
@@ -171,9 +171,7 @@ func executeTestCase(t *testing.T, testCase dispatchTestCase) {
171 171
 	}()
172 172
 
173 173
 	r := strings.NewReader(testCase.dockerfile)
174
-	d := parser.Directive{}
175
-	parser.SetEscapeToken(parser.DefaultEscapeToken, &d)
176
-	n, err := parser.Parse(r, &d)
174
+	n, err := parser.Parse(r, parser.NewDefaultDirective())
177 175
 
178 176
 	if err != nil {
179 177
 		t.Fatalf("Error when parsing Dockerfile: %s", err)
... ...
@@ -190,6 +188,7 @@ func executeTestCase(t *testing.T, testCase dispatchTestCase) {
190 190
 		Stdout:    ioutil.Discard,
191 191
 		context:   context,
192 192
 		buildArgs: newBuildArgs(options.BuildArgs),
193
+		directive: parser.NewDefaultDirective(),
193 194
 	}
194 195
 
195 196
 	err = b.dispatch(0, len(n.Children), n.Children[0])
... ...
@@ -462,7 +462,7 @@ func (b *Builder) processImageFrom(img builder.Image) error {
462 462
 
463 463
 	// parse the ONBUILD triggers by invoking the parser
464 464
 	for _, step := range onBuildTriggers {
465
-		ast, err := parser.Parse(strings.NewReader(step), &b.directive)
465
+		ast, err := parser.Parse(strings.NewReader(step), b.directive)
466 466
 		if err != nil {
467 467
 			return err
468 468
 		}
... ...
@@ -702,5 +702,5 @@ func (b *Builder) parseDockerfile() (*parser.Node, error) {
702 702
 			return nil, fmt.Errorf("The Dockerfile (%s) cannot be empty", b.options.Dockerfile)
703 703
 		}
704 704
 	}
705
-	return parser.Parse(f, &b.directive)
705
+	return parser.Parse(f, b.directive)
706 706
 }
... ...
@@ -23,10 +23,8 @@ func main() {
23 23
 		}
24 24
 		defer f.Close()
25 25
 
26
-		d := parser.Directive{LookingForDirectives: true}
27
-		parser.SetEscapeToken(parser.DefaultEscapeToken, &d)
28
-
29
-		ast, err := parser.Parse(f, &d)
26
+		d := parser.NewDefaultDirective()
27
+		ast, err := parser.Parse(f, d)
30 28
 		if err != nil {
31 29
 			panic(err)
32 30
 		} else {
... ...
@@ -29,7 +29,7 @@ var validJSONArraysOfStrings = map[string][]string{
29 29
 func TestJSONArraysOfStrings(t *testing.T) {
30 30
 	for json, expected := range validJSONArraysOfStrings {
31 31
 		d := Directive{}
32
-		SetEscapeToken(DefaultEscapeToken, &d)
32
+		d.SetEscapeToken(DefaultEscapeToken)
33 33
 
34 34
 		if node, _, err := parseJSON(json, &d); err != nil {
35 35
 			t.Fatalf("%q should be a valid JSON array of strings, but wasn't! (err: %q)", json, err)
... ...
@@ -52,7 +52,7 @@ func TestJSONArraysOfStrings(t *testing.T) {
52 52
 	}
53 53
 	for _, json := range invalidJSONArraysOfStrings {
54 54
 		d := Directive{}
55
-		SetEscapeToken(DefaultEscapeToken, &d)
55
+		d.SetEscapeToken(DefaultEscapeToken)
56 56
 
57 57
 		if _, _, err := parseJSON(json, &d); err != errDockerfileNotStringArray {
58 58
 			t.Fatalf("%q should be an invalid JSON array of strings, but wasn't!", json)
... ...
@@ -103,7 +103,7 @@ func parseWords(rest string, d *Directive) []string {
103 103
 				blankOK = true
104 104
 				phase = inQuote
105 105
 			}
106
-			if ch == d.EscapeToken {
106
+			if ch == d.escapeToken {
107 107
 				if pos+chWidth == len(rest) {
108 108
 					continue // just skip an escape token at end of line
109 109
 				}
... ...
@@ -122,7 +122,7 @@ func parseWords(rest string, d *Directive) []string {
122 122
 				phase = inWord
123 123
 			}
124 124
 			// The escape token is special except for ' quotes - can't escape anything for '
125
-			if ch == d.EscapeToken && quote != '\'' {
125
+			if ch == d.escapeToken && quote != '\'' {
126 126
 				if pos+chWidth == len(rest) {
127 127
 					phase = inWord
128 128
 					continue // just skip the escape token at end
... ...
@@ -62,15 +62,6 @@ func (node *Node) Dump() string {
62 62
 	return strings.TrimSpace(str)
63 63
 }
64 64
 
65
-// Directive is the structure used during a build run to hold the state of
66
-// parsing directives.
67
-type Directive struct {
68
-	EscapeToken           rune           // Current escape token
69
-	LineContinuationRegex *regexp.Regexp // Current line continuation regex
70
-	LookingForDirectives  bool           // Whether we are currently looking for directives
71
-	EscapeSeen            bool           // Whether the escape directive has been seen
72
-}
73
-
74 65
 var (
75 66
 	dispatch           map[string]func(string, *Directive) (*Node, map[string]bool, error)
76 67
 	tokenWhitespace    = regexp.MustCompile(`[\t\v\f\r ]+`)
... ...
@@ -81,16 +72,40 @@ var (
81 81
 // DefaultEscapeToken is the default escape token
82 82
 const DefaultEscapeToken = "\\"
83 83
 
84
+// Directive is the structure used during a build run to hold the state of
85
+// parsing directives.
86
+type Directive struct {
87
+	escapeToken           rune           // Current escape token
88
+	lineContinuationRegex *regexp.Regexp // Current line continuation regex
89
+	lookingForDirectives  bool           // Whether we are currently looking for directives
90
+	escapeSeen            bool           // Whether the escape directive has been seen
91
+}
92
+
84 93
 // SetEscapeToken sets the default token for escaping characters in a Dockerfile.
85
-func SetEscapeToken(s string, d *Directive) error {
94
+func (d *Directive) SetEscapeToken(s string) error {
86 95
 	if s != "`" && s != "\\" {
87 96
 		return fmt.Errorf("invalid ESCAPE '%s'. Must be ` or \\", s)
88 97
 	}
89
-	d.EscapeToken = rune(s[0])
90
-	d.LineContinuationRegex = regexp.MustCompile(`\` + s + `[ \t]*$`)
98
+	d.escapeToken = rune(s[0])
99
+	d.lineContinuationRegex = regexp.MustCompile(`\` + s + `[ \t]*$`)
91 100
 	return nil
92 101
 }
93 102
 
103
+// EscapeToken returns the escape token
104
+func (d *Directive) EscapeToken() rune {
105
+	return d.escapeToken
106
+}
107
+
108
+// NewDefaultDirective returns a new Directive with the default escapeToken token
109
+func NewDefaultDirective() *Directive {
110
+	directive := Directive{
111
+		escapeSeen:           false,
112
+		lookingForDirectives: true,
113
+	}
114
+	directive.SetEscapeToken(DefaultEscapeToken)
115
+	return &directive
116
+}
117
+
94 118
 func init() {
95 119
 	// Dispatch Table. see line_parsers.go for the parse functions.
96 120
 	// The command is parsed and mapped to the line parser. The line parser
... ...
@@ -123,18 +138,18 @@ func init() {
123 123
 // ParseLine parses a line and returns the remainder.
124 124
 func ParseLine(line string, d *Directive, ignoreCont bool) (string, *Node, error) {
125 125
 	if escapeFound, err := handleParserDirective(line, d); err != nil || escapeFound {
126
-		d.EscapeSeen = escapeFound
126
+		d.escapeSeen = escapeFound
127 127
 		return "", nil, err
128 128
 	}
129 129
 
130
-	d.LookingForDirectives = false
130
+	d.lookingForDirectives = false
131 131
 
132 132
 	if line = stripComments(line); line == "" {
133 133
 		return "", nil, nil
134 134
 	}
135 135
 
136
-	if !ignoreCont && d.LineContinuationRegex.MatchString(line) {
137
-		line = d.LineContinuationRegex.ReplaceAllString(line, "")
136
+	if !ignoreCont && d.lineContinuationRegex.MatchString(line) {
137
+		line = d.lineContinuationRegex.ReplaceAllString(line, "")
138 138
 		return line, nil, nil
139 139
 	}
140 140
 
... ...
@@ -170,22 +185,22 @@ func newNodeFromLine(line string, directive *Directive) (*Node, error) {
170 170
 	}, nil
171 171
 }
172 172
 
173
-// Handle the parser directive '# escape=<char>. Parser directives must precede
173
+// Handle the parser directive '# escapeToken=<char>. Parser directives must precede
174 174
 // any builder instruction or other comments, and cannot be repeated.
175 175
 func handleParserDirective(line string, d *Directive) (bool, error) {
176
-	if !d.LookingForDirectives {
176
+	if !d.lookingForDirectives {
177 177
 		return false, nil
178 178
 	}
179 179
 	tecMatch := tokenEscapeCommand.FindStringSubmatch(strings.ToLower(line))
180 180
 	if len(tecMatch) == 0 {
181 181
 		return false, nil
182 182
 	}
183
-	if d.EscapeSeen == true {
183
+	if d.escapeSeen == true {
184 184
 		return false, fmt.Errorf("only one escape parser directive can be used")
185 185
 	}
186 186
 	for i, n := range tokenEscapeCommand.SubexpNames() {
187 187
 		if n == "escapechar" {
188
-			if err := SetEscapeToken(tecMatch[i], d); err != nil {
188
+			if err := d.SetEscapeToken(tecMatch[i]); err != nil {
189 189
 				return false, err
190 190
 			}
191 191
 			return true, nil
... ...
@@ -40,9 +40,7 @@ func TestTestNegative(t *testing.T) {
40 40
 		}
41 41
 		defer df.Close()
42 42
 
43
-		d := Directive{LookingForDirectives: true}
44
-		SetEscapeToken(DefaultEscapeToken, &d)
45
-		_, err = Parse(df, &d)
43
+		_, err = Parse(df, NewDefaultDirective())
46 44
 		if err == nil {
47 45
 			t.Fatalf("No error parsing broken dockerfile for %s", dir)
48 46
 		}
... ...
@@ -60,9 +58,7 @@ func TestTestData(t *testing.T) {
60 60
 		}
61 61
 		defer df.Close()
62 62
 
63
-		d := Directive{LookingForDirectives: true}
64
-		SetEscapeToken(DefaultEscapeToken, &d)
65
-		ast, err := Parse(df, &d)
63
+		ast, err := Parse(df, NewDefaultDirective())
66 64
 		if err != nil {
67 65
 			t.Fatalf("Error parsing %s's dockerfile: %v", dir, err)
68 66
 		}
... ...
@@ -122,9 +118,7 @@ func TestParseWords(t *testing.T) {
122 122
 	}
123 123
 
124 124
 	for _, test := range tests {
125
-		d := Directive{LookingForDirectives: true}
126
-		SetEscapeToken(DefaultEscapeToken, &d)
127
-		words := parseWords(test["input"][0], &d)
125
+		words := parseWords(test["input"][0], NewDefaultDirective())
128 126
 		if len(words) != len(test["expect"]) {
129 127
 			t.Fatalf("length check failed. input: %v, expect: %q, output: %q", test["input"][0], test["expect"], words)
130 128
 		}
... ...
@@ -143,9 +137,7 @@ func TestLineInformation(t *testing.T) {
143 143
 	}
144 144
 	defer df.Close()
145 145
 
146
-	d := Directive{LookingForDirectives: true}
147
-	SetEscapeToken(DefaultEscapeToken, &d)
148
-	ast, err := Parse(df, &d)
146
+	ast, err := Parse(df, NewDefaultDirective())
149 147
 	if err != nil {
150 148
 		t.Fatalf("Error parsing dockerfile %s: %v", testFileLineInfo, err)
151 149
 	}