Skip to content

Commit da4633a

Browse files
Localisation support (#665)
* Add dialect options to support localisation * Add test for ParseFeatures support for localisation
1 parent c5a88f6 commit da4633a

File tree

5 files changed

+162
-23
lines changed

5 files changed

+162
-23
lines changed

internal/flags/options.go

+3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ type Options struct {
4848
// from feature files
4949
Tags string
5050

51+
// Dialect to be used to parse feature files. If not set, default to "en".
52+
Dialect string
53+
5154
// The formatter name
5255
Format string
5356

internal/parser/parser.go

+21-13
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func ExtractFeaturePathLine(p string) (string, int) {
3333
return retPath, line
3434
}
3535

36-
func parseFeatureFile(fsys fs.FS, path string, newIDFunc func() string) (*models.Feature, error) {
36+
func parseFeatureFile(fsys fs.FS, path, dialect string, newIDFunc func() string) (*models.Feature, error) {
3737
reader, err := fsys.Open(path)
3838
if err != nil {
3939
return nil, err
@@ -42,7 +42,7 @@ func parseFeatureFile(fsys fs.FS, path string, newIDFunc func() string) (*models
4242
defer reader.Close()
4343

4444
var buf bytes.Buffer
45-
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc)
45+
gherkinDocument, err := gherkin.ParseGherkinDocumentForLanguage(io.TeeReader(reader, &buf), dialect, newIDFunc)
4646
if err != nil {
4747
return nil, fmt.Errorf("%s - %v", path, err)
4848
}
@@ -54,11 +54,11 @@ func parseFeatureFile(fsys fs.FS, path string, newIDFunc func() string) (*models
5454
return &f, nil
5555
}
5656

57-
func parseBytes(path string, feature []byte, newIDFunc func() string) (*models.Feature, error) {
57+
func parseBytes(path string, feature []byte, dialect string, newIDFunc func() string) (*models.Feature, error) {
5858
reader := bytes.NewReader(feature)
5959

6060
var buf bytes.Buffer
61-
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc)
61+
gherkinDocument, err := gherkin.ParseGherkinDocumentForLanguage(io.TeeReader(reader, &buf), dialect, newIDFunc)
6262
if err != nil {
6363
return nil, fmt.Errorf("%s - %v", path, err)
6464
}
@@ -70,7 +70,7 @@ func parseBytes(path string, feature []byte, newIDFunc func() string) (*models.F
7070
return &f, nil
7171
}
7272

73-
func parseFeatureDir(fsys fs.FS, dir string, newIDFunc func() string) ([]*models.Feature, error) {
73+
func parseFeatureDir(fsys fs.FS, dir, dialect string, newIDFunc func() string) ([]*models.Feature, error) {
7474
var features []*models.Feature
7575
return features, fs.WalkDir(fsys, dir, func(p string, f fs.DirEntry, err error) error {
7676
if err != nil {
@@ -85,7 +85,7 @@ func parseFeatureDir(fsys fs.FS, dir string, newIDFunc func() string) ([]*models
8585
return nil
8686
}
8787

88-
feat, err := parseFeatureFile(fsys, p, newIDFunc)
88+
feat, err := parseFeatureFile(fsys, p, dialect, newIDFunc)
8989
if err != nil {
9090
return err
9191
}
@@ -95,7 +95,7 @@ func parseFeatureDir(fsys fs.FS, dir string, newIDFunc func() string) ([]*models
9595
})
9696
}
9797

98-
func parsePath(fsys fs.FS, path string, newIDFunc func() string) ([]*models.Feature, error) {
98+
func parsePath(fsys fs.FS, path, dialect string, newIDFunc func() string) ([]*models.Feature, error) {
9999
var features []*models.Feature
100100

101101
path, line := ExtractFeaturePathLine(path)
@@ -114,10 +114,10 @@ func parsePath(fsys fs.FS, path string, newIDFunc func() string) ([]*models.Feat
114114
}
115115

116116
if fi.IsDir() {
117-
return parseFeatureDir(fsys, path, newIDFunc)
117+
return parseFeatureDir(fsys, path, dialect, newIDFunc)
118118
}
119119

120-
ft, err := parseFeatureFile(fsys, path, newIDFunc)
120+
ft, err := parseFeatureFile(fsys, path, dialect, newIDFunc)
121121
if err != nil {
122122
return features, err
123123
}
@@ -146,14 +146,18 @@ func parsePath(fsys fs.FS, path string, newIDFunc func() string) ([]*models.Feat
146146
}
147147

148148
// ParseFeatures ...
149-
func ParseFeatures(fsys fs.FS, filter string, paths []string) ([]*models.Feature, error) {
149+
func ParseFeatures(fsys fs.FS, filter, dialect string, paths []string) ([]*models.Feature, error) {
150150
var order int
151151

152+
if dialect == "" {
153+
dialect = gherkin.DefaultDialect
154+
}
155+
152156
featureIdxs := make(map[string]int)
153157
uniqueFeatureURI := make(map[string]*models.Feature)
154158
newIDFunc := (&messages.Incrementing{}).NewId
155159
for _, path := range paths {
156-
feats, err := parsePath(fsys, path, newIDFunc)
160+
feats, err := parsePath(fsys, path, dialect, newIDFunc)
157161

158162
switch {
159163
case os.IsNotExist(err):
@@ -189,14 +193,18 @@ func ParseFeatures(fsys fs.FS, filter string, paths []string) ([]*models.Feature
189193

190194
type FeatureContent = flags.Feature
191195

192-
func ParseFromBytes(filter string, featuresInputs []FeatureContent) ([]*models.Feature, error) {
196+
func ParseFromBytes(filter, dialect string, featuresInputs []FeatureContent) ([]*models.Feature, error) {
193197
var order int
194198

199+
if dialect == "" {
200+
dialect = gherkin.DefaultDialect
201+
}
202+
195203
featureIdxs := make(map[string]int)
196204
uniqueFeatureURI := make(map[string]*models.Feature)
197205
newIDFunc := (&messages.Incrementing{}).NewId
198206
for _, f := range featuresInputs {
199-
ft, err := parseBytes(f.Name, f.Contents, newIDFunc)
207+
ft, err := parseBytes(f.Name, f.Contents, dialect, newIDFunc)
200208
if err != nil {
201209
return nil, err
202210
}

internal/parser/parser_test.go

+133-4
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ Feature: eat godogs
5454
{Name: "MyCoolDuplicatedFeature", Contents: []byte(eatGodogContents)},
5555
}
5656

57-
featureFromBytes, err := parser.ParseFromBytes("", input)
57+
featureFromBytes, err := parser.ParseFromBytes("", "", input)
5858
require.NoError(t, err)
5959
require.Len(t, featureFromBytes, 1)
6060
}
@@ -80,15 +80,15 @@ Feature: eat godogs
8080
},
8181
}
8282

83-
featureFromFile, err := parser.ParseFeatures(fsys, "", []string{baseDir})
83+
featureFromFile, err := parser.ParseFeatures(fsys, "", "", []string{baseDir})
8484
require.NoError(t, err)
8585
require.Len(t, featureFromFile, 1)
8686

8787
input := []parser.FeatureContent{
8888
{Name: filepath.Join(baseDir, featureFileName), Contents: []byte(eatGodogContents)},
8989
}
9090

91-
featureFromBytes, err := parser.ParseFromBytes("", input)
91+
featureFromBytes, err := parser.ParseFromBytes("", "", input)
9292
require.NoError(t, err)
9393
require.Len(t, featureFromBytes, 1)
9494

@@ -155,7 +155,7 @@ func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
155155
t.Run(name, func(t *testing.T) {
156156
t.Parallel()
157157

158-
features, err := parser.ParseFeatures(test.fsys, "", test.paths)
158+
features, err := parser.ParseFeatures(test.fsys, "", "", test.paths)
159159
if test.expError != nil {
160160
require.Error(t, err)
161161
require.EqualError(t, err, test.expError.Error())
@@ -178,3 +178,132 @@ func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
178178
})
179179
}
180180
}
181+
182+
func Test_ParseFeatures_Localisation(t *testing.T) {
183+
tests := map[string]struct {
184+
dialect string
185+
contents string
186+
}{
187+
"english": {
188+
dialect: "en",
189+
contents: `
190+
Feature: dummy
191+
Rule: dummy
192+
Background: dummy
193+
Given dummy
194+
When dummy
195+
Then dummy
196+
Scenario: dummy
197+
Given dummy
198+
When dummy
199+
Then dummy
200+
And dummy
201+
But dummy
202+
Example: dummy
203+
Given dummy
204+
When dummy
205+
Then dummy
206+
Scenario Outline: dummy
207+
Given dummy
208+
When dummy
209+
Then dummy
210+
`,
211+
},
212+
"afrikaans": {
213+
dialect: "af",
214+
contents: `
215+
Funksie: dummy
216+
Regel: dummy
217+
Agtergrond: dummy
218+
Gegewe dummy
219+
Wanneer dummy
220+
Dan dummy
221+
Voorbeeld: dummy
222+
Gegewe dummy
223+
Wanneer dummy
224+
Dan dummy
225+
En dummy
226+
Maar dummy
227+
Voorbeelde: dummy
228+
Gegewe dummy
229+
Wanneer dummy
230+
Dan dummy
231+
Situasie Uiteensetting: dummy
232+
Gegewe dummy
233+
Wanneer dummy
234+
Dan dummy
235+
`,
236+
},
237+
"arabic": {
238+
dialect: "ar",
239+
contents: `
240+
خاصية: dummy
241+
Rule: dummy
242+
الخلفية: dummy
243+
بفرض dummy
244+
متى dummy
245+
اذاً dummy
246+
مثال: dummy
247+
بفرض dummy
248+
متى dummy
249+
اذاً dummy
250+
و dummy
251+
لكن dummy
252+
امثلة: dummy
253+
بفرض dummy
254+
متى dummy
255+
اذاً dummy
256+
سيناريو مخطط: dummy
257+
بفرض dummy
258+
متى dummy
259+
اذاً dummy
260+
`,
261+
},
262+
"chinese simplified": {
263+
dialect: "zh-CN",
264+
contents: `
265+
功能: dummy
266+
规则: dummy
267+
背景: dummy
268+
假如 dummy
269+
当 dummy
270+
那么 dummy
271+
场景: dummy
272+
假如 dummy
273+
当 dummy
274+
那么 dummy
275+
而且 dummy
276+
但是 dummy
277+
例子: dummy
278+
假如 dummy
279+
当 dummy
280+
那么 dummy
281+
场景大纲: dummy
282+
假如 dummy
283+
当 dummy
284+
那么 dummy
285+
`,
286+
},
287+
}
288+
289+
featureFileName := "godogs.feature"
290+
baseDir := "base"
291+
292+
for name, test := range tests {
293+
test := test
294+
t.Run(name, func(t *testing.T) {
295+
t.Parallel()
296+
297+
fsys := fstest.MapFS{
298+
filepath.Join(baseDir, featureFileName): {
299+
Data: []byte(test.contents),
300+
Mode: fs.FileMode(0o644),
301+
},
302+
}
303+
304+
featureTestDialect, err := parser.ParseFeatures(fsys, "", test.dialect, []string{baseDir})
305+
require.NoError(t, err)
306+
require.Len(t, featureTestDialect, 1)
307+
})
308+
}
309+
}

run.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
247247
opt.FS = storage.FS{FS: opt.FS}
248248

249249
if len(opt.FeatureContents) > 0 {
250-
features, err := parser.ParseFromBytes(opt.Tags, opt.FeatureContents)
250+
features, err := parser.ParseFromBytes(opt.Tags, opt.Dialect, opt.FeatureContents)
251251
if err != nil {
252252
fmt.Fprintln(os.Stderr, err)
253253
return exitOptionError
@@ -256,7 +256,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
256256
}
257257

258258
if len(opt.Paths) > 0 {
259-
features, err := parser.ParseFeatures(opt.FS, opt.Tags, opt.Paths)
259+
features, err := parser.ParseFeatures(opt.FS, opt.Tags, opt.Dialect, opt.Paths)
260260
if err != nil {
261261
fmt.Fprintln(os.Stderr, err)
262262
return exitOptionError
@@ -389,7 +389,7 @@ func (ts TestSuite) RetrieveFeatures() ([]*models.Feature, error) {
389389
}
390390
}
391391

392-
return parser.ParseFeatures(opt.FS, opt.Tags, opt.Paths)
392+
return parser.ParseFeatures(opt.FS, opt.Tags, opt.Dialect, opt.Paths)
393393
}
394394

395395
func getDefaultOptions() (*Options, error) {

suite_context_test.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -494,7 +494,7 @@ func (tc *godogFeaturesScenario) theLoggedMessagesShouldInclude(ctx context.Cont
494494
}
495495

496496
func (tc *godogFeaturesScenario) followingStepsShouldHave(status string, steps *DocString) error {
497-
var expected = strings.Split(steps.Content, "\n")
497+
expected := strings.Split(steps.Content, "\n")
498498
var actual, unmatched, matched []string
499499

500500
storage := tc.testedSuite.storage
@@ -673,7 +673,7 @@ func (tc *godogFeaturesScenario) featurePath(path string) {
673673
}
674674

675675
func (tc *godogFeaturesScenario) parseFeatures() error {
676-
fts, err := parser.ParseFeatures(storage.FS{}, "", tc.paths)
676+
fts, err := parser.ParseFeatures(storage.FS{}, "", "", tc.paths)
677677
if err != nil {
678678
return err
679679
}
@@ -1226,7 +1226,6 @@ func TestTestSuite_Run(t *testing.T) {
12261226
s.Step("^multistep has ambiguous$", func() Steps {
12271227
return Steps{"step is ambiguous"}
12281228
})
1229-
12301229
},
12311230
Options: &Options{
12321231
Format: "pretty",

0 commit comments

Comments
 (0)