Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support more natural languages for the features. #120

Merged
merged 10 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
wdipax marked this conversation as resolved.
Show resolved Hide resolved
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
Loading