Skip to content

Commit

Permalink
feat: Support more natural languages for the features. (#120)
Browse files Browse the repository at this point in the history
Co-authored-by: Dmitrii Malinovskii <you@example.com>
  • Loading branch information
wdipax and Dmitrii Malinovskii authored Jun 20, 2024
1 parent 03e6cb4 commit c3229ea
Show file tree
Hide file tree
Showing 22 changed files with 521 additions and 71 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ bin

# IDE
.vscode
.idea

# Test binary, built with `go test -c`
*.test
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ Usage of gherkingen [FEATURE_FILE]:
add parallel mark (deprecated, enabled by default) (default true)
-help
print usage
-language string
Specifies the natural language used to describe the feature.
This flag is optional if language information is included in the feature file name, or if the feature is written in English.
The file name should be formatted as follows: <description>.<language_hint>.feature if language hint is included, or <description>.feature if it is not.
When provided, the 'language' flag takes precedence over the language hint from the file name. (default "en")
-languages
list supported natural feature languages
-list
list internal templates
-package string
Expand Down Expand Up @@ -251,7 +258,7 @@ create a pull request for supporting templates for them. For this:
3. Check: `make lint check.generate test`.
4. Commit&Push, create a PR.

## Language support
## Programming language support

Templates are very customizable, so you can even generate non-golang code. In the command-line tool specify `raw` format using `-format` flag and your template using `-template` flag:
`gherkingen -format raw -template example.tmpl example.feature`.
Expand Down
91 changes: 65 additions & 26 deletions internal/app/app.feature
Original file line number Diff line number Diff line change
@@ -1,55 +1,56 @@
Feature: Application command line tool

Scenario Outline: User wants to generate the output in given format
When <format> is given
And <feature> is provided
Then the output should be generated
Examples:
| <feature> | <format> | <assertion> |
| app.feature | go | does |
| app.feature | json | does |
| app.feature | raw | does |
| app.feature | invalid | does not |
| notfound.feature | raw | does not |
| <feature> | <format> | <assertion> |
| app.feature | go | does |
| app.feature | json | does |
| app.feature | raw | does |
| app.feature | invalid | does not |
| notfound.feature | raw | does not |

Scenario Outline: User wants to see usage information
When <flag> is provided
Then usage should be printed
Examples:
| <flag> |
| --help |
| <flag> |
| --help |

Scenario Outline: User wants to list built-in templates
When <flag> is provided
Then templates should be printed
Examples:
| <flag> |
| --list |
| <flag> |
| --list |

Scenario Outline: User wants to use custom template
When <template> is provided
And <feature> is provided
Then the output should be generated
Examples:
| <feature> | <template> |
| app.feature | ../assets/std.simple.v1.go.tmpl |
| app.feature | @/std.simple.v1.go.tmpl |
| <feature> | <template> |
| app.feature | ../assets/std.simple.v1.go.tmpl |
| app.feature | @/std.simple.v1.go.tmpl |

Scenario Outline: User wants to set custom package
When <package> is provided
Then the output should contain <package>
Examples:
| <package> |
| app_test |
| example_test |
| <package> |
| app_test |
| example_test |

Scenario Outline: User wants to generate a permanent json output
When -format is json
And -permanent-ids is <TheSameIDs>
Then calling generation twice will produce the same output <TheSameIDs>
Examples:
| <TheSameIDs> |
| true |
| false |
| <TheSameIDs> |
| true |
| false |

Scenario: User provides an invalid flag
When flag -invalid is provided
Expand All @@ -59,17 +60,17 @@ Feature: Application command line tool
When <flag> is provided
Then version is printed
Examples:
| <flag> |
| --version |
| -version |
| <flag> |
| --version |
| -version |

Scenario Outline: User specifies a file, but the file is not found
When inexistent <template> is provided
And <feature> is provided
Then the user receives an error
Examples:
| <feature> | <template> |
| app.feature | not_found |
| <feature> | <template> |
| app.feature | not_found |

Scenario: User wants to run tests in parallel
When `scenario.feature` is given
Expand All @@ -79,3 +80,41 @@ Feature: Application command line tool
When `-disable-go-parallel` is provided
And `scenario.feature` is given
Then generated code doesn't contain `t.Parallel()`

Scenario Outline: User wants to generate the output for a feature written in a specific natural language
When the <language> is given
And the <feature> is provided
Then the output should be generated
Examples:
| <language> | <feature> | assertion |
| en | ../generator/examples/simple.feature | does |
| en-pirate | ../generator/examples/simple.en-pirate.feature | does |
| unsupported | app.feature | does not |

Scenario Outline: User wants to see all supported natural languages
When the <flag> is provided
Then the list of supported natural languages should be printed
Examples:
| <flag> |
| -languages |
| --languages |

Scenario: User wants to see consistent supported natural languages output
When the user lists supported natural languages several times
Then the output is the same

Scenario Outline: User wants to be able to specify the natural language of a feature in its file name
When the <language> is specified by the flag
And the <feature> is given
Then the output should be generated
Examples:
| <language> | <feature> | assertion |
| en-pirate | ./testdata/pirate.feature | does |
| | ./testdata/pirate.feature | does not |
| | ./testdata/pirate.sample.en-pirate.feature | does |
| wrong | ./testdata/pirate.sample.en-pirate.feature | does not |
| en | ./testdata/english.feature | does |
| | ./testdata/english.feature | does |
| | ./testdata/english.sample.en.feature | does |
| wrong | ./testdata/english.sample.en.feature | does not |
| | app.feature | does |
32 changes: 30 additions & 2 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@ import (
"flag"
"io"
"math/rand"
"slices"
"strings"
"time"

"github.com/hedhyw/gherkingen/v4/internal/model"

gherkin "github.com/cucumber/gherkin/go/v28"
"github.com/google/uuid"

"github.com/hedhyw/gherkingen/v4/internal/model"
)

const (
internalPathPrefix = "@/"
defaultTemplate = "std.simple.v1.go.tmpl"
defaultDisableGoParallel = false
defaultOutputFormat = model.FormatAutoDetect
defaultLanguage = gherkin.DefaultDialect
)

// Run the application.
//
//nolint:cyclop // Looks fine to me.
func Run(arguments []string, out io.Writer, version string) (err error) {
flagSet := flag.NewFlagSet(flag.CommandLine.Name(), flag.ContinueOnError)
flagSet.SetOutput(out)
Expand Down Expand Up @@ -69,6 +74,20 @@ func Run(arguments []string, out io.Writer, version string) (err error) {
false,
"print version",
)
language := flagSet.String(
"language",
defaultLanguage,
"Specifies the natural language used to describe the feature.\n"+
"This flag is optional if language information is included in the feature file name, or if the feature is written in English.\n"+
"The file name should be formatted as follows: <description>.<language_hint>.feature if language hint is included, "+
"or <description>.feature if it is not.\n"+
"When provided, the 'language' flag takes precedence over the language hint from the file name.",
)
listLanguages := flagSet.Bool(
"languages",
false,
"list supported natural feature languages",
)
if err = flagSet.Parse(arguments); err != nil {
return err
}
Expand All @@ -86,11 +105,19 @@ func Run(arguments []string, out io.Writer, version string) (err error) {
inputFile = flagSet.Args()[0]
}

if !slices.Contains(arguments, "-language") {
if hint := tryFromFileName(inputFile); hint != "" {
*language = hint
}
}

switch {
case *versionCmd:
return runVersion(out, version)
case *listCmd:
return runListTemplates(out)
case *listLanguages:
return runListFeatureLanguages(out)
case *helpCmd, inputFile == "":
return runHelp(flagSet)
default:
Expand All @@ -102,6 +129,7 @@ func Run(arguments []string, out io.Writer, version string) (err error) {
PackageName: *packageName,
GoParallel: !(*disableGoParallel),
GenerateUUID: newUUIDRandomGenerator(seed),
Language: *language,
})
}
}
Expand Down
126 changes: 124 additions & 2 deletions internal/app/app_test.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
//nolint:goconst // These are auto generated tests.
package app_test

import (
"bytes"
"testing"

"github.com/hedhyw/gherkingen/v4/internal/app"

gherkin "github.com/cucumber/gherkin/go/v28"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/hedhyw/gherkingen/v4/internal/app"
)

const testVersion = "0.0.1"
Expand Down Expand Up @@ -121,6 +123,126 @@ func TestApplicationCommandLineTool(t *testing.T) {
})
}
})

t.Run("User wants to generate the output for a feature written in a specific natural language", func(t *testing.T) {
t.Parallel()

type testCase struct {
Language string `field:"<language>"`
Feature string `field:"<feature>"`
Assertion string `field:"assertion"`
}

testCases := map[string]testCase{
"en_../generator/examples/simple.feature_does": {"en", "../generator/examples/simple.feature", "does"},
"en-pirate_../generator/examples/simple.en-pirate.feature_does": {"en-pirate", "../generator/examples/simple.en-pirate.feature", "does"},
"unsupported_app.feature_does_not": {"unsupported", "app.feature", "does not"},
}

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

// When the <language> is given.
arguments := []string{
"-language",
testCase.Language,
}

// And the <feature> is provided.
arguments = append(arguments, testCase.Feature)

// Then the output should be generated.
runApp(t, arguments, testCase.Assertion == "does")
})
}
})

t.Run("User wants to see all supported natural languages", func(t *testing.T) {
t.Parallel()

type testCase struct {
Flag string `field:"<flag>"`
}

testCases := map[string]testCase{
"-languages": {"-languages"},
"--languages": {"--languages"},
}

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

// When the <flag> is provided.
arguments := []string{testCase.Flag}

// Then the list of supported natural languages should be printed.
out := runApp(t, arguments, true)

dialectProvider := gherkin.DialectsBuiltin()
dialect := dialectProvider.GetDialect(gherkin.DefaultDialect)

assert.Contains(t, out, dialect.Name)
assert.Contains(t, out, dialect.Language)
assert.Contains(t, out, dialect.Native)
})
}
})

t.Run("User wants to see consistent supported natural languages output", func(t *testing.T) {
t.Parallel()

// When the user lists supported natural languages several times.
arguments := []string{"-languages"}

out1 := runApp(t, arguments, true)
out2 := runApp(t, arguments, true)

// Then the output is the same.
assert.Equal(t, out1, out2)
})

t.Run("User wants to be able to specify the natural language of a feature in its file name", func(t *testing.T) {
t.Parallel()

type testCase struct {
Language string `field:"<language>"`
Feature string `field:"<feature>"`
Assertion string `field:"assertion"`
}

testCases := map[string]testCase{
"en-pirate_./testdata/pirate.feature_does": {"en-pirate", "./testdata/pirate.feature", "does"},
"_./testdata/pirate.feature_does_not": {"", "./testdata/pirate.feature", "does not"},
"_./testdata/pirate.sample.en-pirate.feature_does": {"", "./testdata/pirate.sample.en-pirate.feature", "does"},
"wrong_./testdata/pirate.sample.en-pirate.feature_does_not": {"wrong", "./testdata/pirate.sample.en-pirate.feature", "does not"},
"en_./testdata/english.feature_does": {"en", "./testdata/english.feature", "does"},
"_./testdata/english.feature_does": {"", "./testdata/english.feature", "does"},
"_./testdata/english.sample.en.feature_does": {"", "./testdata/english.sample.en.feature", "does"},
"wrong_./testdata/english.sample.en.feature_does_not": {"wrong", "./testdata/english.sample.en.feature", "does not"},
"_app.feature_does": {"", "app.feature", "does"},
}

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

var arguments []string

// When the <language> is specified by the flag.
if testCase.Language != "" {
arguments = append(arguments, "-language", testCase.Language)
}

// And the <feature> is given.
arguments = append(arguments, testCase.Feature)

// Then the output should be generated.
runApp(t, arguments, testCase.Assertion == "does")
})
}
})
}

func TestApplicationCommandLineToolCustom(t *testing.T) {
Expand Down
Loading

0 comments on commit c3229ea

Please sign in to comment.