Skip to content

Commit

Permalink
Localisation support (#665)
Browse files Browse the repository at this point in the history
* Add dialect options to support localisation

* Add test for ParseFeatures support for localisation
  • Loading branch information
MegaGrindStone authored Nov 14, 2024
1 parent c5a88f6 commit da4633a
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 23 deletions.
3 changes: 3 additions & 0 deletions internal/flags/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ type Options struct {
// from feature files
Tags string

// Dialect to be used to parse feature files. If not set, default to "en".
Dialect string

// The formatter name
Format string

Expand Down
34 changes: 21 additions & 13 deletions internal/parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func ExtractFeaturePathLine(p string) (string, int) {
return retPath, line
}

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

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

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

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

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

feat, err := parseFeatureFile(fsys, p, newIDFunc)
feat, err := parseFeatureFile(fsys, p, dialect, newIDFunc)
if err != nil {
return err
}
Expand All @@ -95,7 +95,7 @@ func parseFeatureDir(fsys fs.FS, dir string, newIDFunc func() string) ([]*models
})
}

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

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

if fi.IsDir() {
return parseFeatureDir(fsys, path, newIDFunc)
return parseFeatureDir(fsys, path, dialect, newIDFunc)
}

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

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

if dialect == "" {
dialect = gherkin.DefaultDialect
}

featureIdxs := make(map[string]int)
uniqueFeatureURI := make(map[string]*models.Feature)
newIDFunc := (&messages.Incrementing{}).NewId
for _, path := range paths {
feats, err := parsePath(fsys, path, newIDFunc)
feats, err := parsePath(fsys, path, dialect, newIDFunc)

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

type FeatureContent = flags.Feature

func ParseFromBytes(filter string, featuresInputs []FeatureContent) ([]*models.Feature, error) {
func ParseFromBytes(filter, dialect string, featuresInputs []FeatureContent) ([]*models.Feature, error) {
var order int

if dialect == "" {
dialect = gherkin.DefaultDialect
}

featureIdxs := make(map[string]int)
uniqueFeatureURI := make(map[string]*models.Feature)
newIDFunc := (&messages.Incrementing{}).NewId
for _, f := range featuresInputs {
ft, err := parseBytes(f.Name, f.Contents, newIDFunc)
ft, err := parseBytes(f.Name, f.Contents, dialect, newIDFunc)
if err != nil {
return nil, err
}
Expand Down
137 changes: 133 additions & 4 deletions internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ Feature: eat godogs
{Name: "MyCoolDuplicatedFeature", Contents: []byte(eatGodogContents)},
}

featureFromBytes, err := parser.ParseFromBytes("", input)
featureFromBytes, err := parser.ParseFromBytes("", "", input)
require.NoError(t, err)
require.Len(t, featureFromBytes, 1)
}
Expand All @@ -80,15 +80,15 @@ Feature: eat godogs
},
}

featureFromFile, err := parser.ParseFeatures(fsys, "", []string{baseDir})
featureFromFile, err := parser.ParseFeatures(fsys, "", "", []string{baseDir})
require.NoError(t, err)
require.Len(t, featureFromFile, 1)

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

featureFromBytes, err := parser.ParseFromBytes("", input)
featureFromBytes, err := parser.ParseFromBytes("", "", input)
require.NoError(t, err)
require.Len(t, featureFromBytes, 1)

Expand Down Expand Up @@ -155,7 +155,7 @@ func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
t.Run(name, func(t *testing.T) {
t.Parallel()

features, err := parser.ParseFeatures(test.fsys, "", test.paths)
features, err := parser.ParseFeatures(test.fsys, "", "", test.paths)
if test.expError != nil {
require.Error(t, err)
require.EqualError(t, err, test.expError.Error())
Expand All @@ -178,3 +178,132 @@ func Test_ParseFeatures_FromMultiplePaths(t *testing.T) {
})
}
}

func Test_ParseFeatures_Localisation(t *testing.T) {
tests := map[string]struct {
dialect string
contents string
}{
"english": {
dialect: "en",
contents: `
Feature: dummy
Rule: dummy
Background: dummy
Given dummy
When dummy
Then dummy
Scenario: dummy
Given dummy
When dummy
Then dummy
And dummy
But dummy
Example: dummy
Given dummy
When dummy
Then dummy
Scenario Outline: dummy
Given dummy
When dummy
Then dummy
`,
},
"afrikaans": {
dialect: "af",
contents: `
Funksie: dummy
Regel: dummy
Agtergrond: dummy
Gegewe dummy
Wanneer dummy
Dan dummy
Voorbeeld: dummy
Gegewe dummy
Wanneer dummy
Dan dummy
En dummy
Maar dummy
Voorbeelde: dummy
Gegewe dummy
Wanneer dummy
Dan dummy
Situasie Uiteensetting: dummy
Gegewe dummy
Wanneer dummy
Dan dummy
`,
},
"arabic": {
dialect: "ar",
contents: `
خاصية: dummy
Rule: dummy
الخلفية: dummy
بفرض dummy
متى dummy
اذاً dummy
مثال: dummy
بفرض dummy
متى dummy
اذاً dummy
و dummy
لكن dummy
امثلة: dummy
بفرض dummy
متى dummy
اذاً dummy
سيناريو مخطط: dummy
بفرض dummy
متى dummy
اذاً dummy
`,
},
"chinese simplified": {
dialect: "zh-CN",
contents: `
功能: dummy
规则: dummy
背景: dummy
假如 dummy
当 dummy
那么 dummy
场景: dummy
假如 dummy
当 dummy
那么 dummy
而且 dummy
但是 dummy
例子: dummy
假如 dummy
当 dummy
那么 dummy
场景大纲: dummy
假如 dummy
当 dummy
那么 dummy
`,
},
}

featureFileName := "godogs.feature"
baseDir := "base"

for name, test := range tests {
test := test
t.Run(name, func(t *testing.T) {
t.Parallel()

fsys := fstest.MapFS{
filepath.Join(baseDir, featureFileName): {
Data: []byte(test.contents),
Mode: fs.FileMode(0o644),
},
}

featureTestDialect, err := parser.ParseFeatures(fsys, "", test.dialect, []string{baseDir})
require.NoError(t, err)
require.Len(t, featureTestDialect, 1)
})
}
}
6 changes: 3 additions & 3 deletions run.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ func runWithOptions(suiteName string, runner runner, opt Options) int {
opt.FS = storage.FS{FS: opt.FS}

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

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

return parser.ParseFeatures(opt.FS, opt.Tags, opt.Paths)
return parser.ParseFeatures(opt.FS, opt.Tags, opt.Dialect, opt.Paths)
}

func getDefaultOptions() (*Options, error) {
Expand Down
5 changes: 2 additions & 3 deletions suite_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ func (tc *godogFeaturesScenario) theLoggedMessagesShouldInclude(ctx context.Cont
}

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

storage := tc.testedSuite.storage
Expand Down Expand Up @@ -673,7 +673,7 @@ func (tc *godogFeaturesScenario) featurePath(path string) {
}

func (tc *godogFeaturesScenario) parseFeatures() error {
fts, err := parser.ParseFeatures(storage.FS{}, "", tc.paths)
fts, err := parser.ParseFeatures(storage.FS{}, "", "", tc.paths)
if err != nil {
return err
}
Expand Down Expand Up @@ -1226,7 +1226,6 @@ func TestTestSuite_Run(t *testing.T) {
s.Step("^multistep has ambiguous$", func() Steps {
return Steps{"step is ambiguous"}
})

},
Options: &Options{
Format: "pretty",
Expand Down

0 comments on commit da4633a

Please sign in to comment.