chore: update go versions (#258)

* chore: update go versions

* test: add test coverage

* chore: convert generator statements to options

* chore: convert to options struct

* feat: start support for config file

* feat: closer to config based running

* tests: increase coverage by testing main function.

* tests: improve coverage
This commit is contained in:
Alex Bice authored and GitHub committed 2025-07-21 19:24:24 -07:00
1 parent 0979fc7bd6
commit 054ab7e347
11 files changed
+2737 -399

No files matched your search

+22 -16
View File
@@ -13,14 +13,16 @@ jobs:
fail-fast: false
matrix:
go:
- "1.20"
- "1.21"
- '1.22'
- '1.23'
- '1.24'
name: run tests with go version ${{ matrix.go }}
steps:
- name: install go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
go-version: '${{ matrix.go }}'
check-latest: true
- name: checkout code
uses: actions/checkout@v4
@@ -29,26 +31,30 @@ jobs:
run: make build
- name: Test
env:
COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
run: |
make cover
- name: Coveralls
if: (matrix.go == '1.21')
env:
COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
run: |
if [[ -n "$COVERALLS_TOKEN" ]]; then
make coveralls
fi
- name: Send coverage
uses: shogo82148/actions-goveralls@v1
with:
path-to-profile: coverage.out
flag-name: Go-${{ matrix.go }}
parallel: true
- name: Upload coverage
if: (matrix.go == '1.21')
uses: actions/upload-artifact@v4
with:
name: coverage
name: coverage.${{ matrix.go }}
path: coverage.*
- name: Assert no changes
run: make assert-no-changes
# notifies that all test jobs are finished.
finish:
needs: build
runs-on: ubuntu-latest
steps:
- uses: shogo82148/actions-goveralls@v1
with:
parallel-finished: true
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ">=1.20.2"
go-version: ">=1.23.2"
- name: Ensure Go
run: go version
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ">=1.20.2"
go-version: ">=1.23.2"
- name: Ensure Go
run: go version
+3 -3
View File
@@ -16,8 +16,8 @@
/bin
/coverage
vendor/
coverage.out
coverage.html
coverage.*
dist/
.idea/
.idea/
.DS_Store
+6 -6
View File
@@ -10,7 +10,7 @@ COVERAGEDIR= coverage
SERVICE=local
ifdef GITHUB_ACTIONS
SERVICE=github-actions
SERVICE=github
endif
DATE := $(shell date -u '+%FT%T%z')
@@ -99,23 +99,23 @@ bin/goveralls: go.sum
$(call goinstall,github.com/mattn/goveralls)
# snapshots: snapshots_1.17
snapshots: snapshots_1.21
snapshots: snapshots_1.24
snapshots_%: clean
echo "##### updating snapshots for golang $* #####"
docker run -i -t -w /app -v $(shell pwd):/app --entrypoint /bin/sh golang:$* -c './update-snapshots.sh || true'
.PHONY: ci
ci: docker_1.20
ci: docker_1.21
ci: docker_1.23
ci: docker_1.24
docker_%:
echo "##### testing golang $* #####"
docker run -i -t -w /app -v $(shell pwd):/app --entrypoint /bin/sh golang:$* -c 'make clean && make'
.PHONY: pullimages
pullimages: pullimage_1.20
pullimages: pullimage_1.21
pullimages: pullimage_1.23
pullimages: pullimage_1.24
pullimage_%:
docker pull golang:$*
+90 -193
View File
@@ -27,35 +27,15 @@ const (
// Generator is responsible for generating validation files for the given in a go source file.
type Generator struct {
Version string
Revision string
BuildDate string
BuiltBy string
Version string
Revision string
BuildDate string
BuiltBy string
GeneratorConfig
t *template.Template
knownTemplates map[string]*template.Template
userTemplateNames []string
fileSet *token.FileSet
noPrefix bool
lowercaseLookup bool
caseInsensitive bool
marshal bool
sql bool
sqlint bool
flag bool
names bool
values bool
leaveSnakeCase bool
jsonPkg string
prefix string
sqlNullInt bool
sqlNullStr bool
ptr bool
mustParse bool
forceLower bool
forceUpper bool
noComments bool
buildTags []string
replacementNames map[string]string
userTemplateNames []string
}
// Enum holds data for a discovered enum in the parsed source
@@ -79,19 +59,26 @@ type EnumValue struct {
// NewGenerator is a constructor method for creating a new Generator with default
// templates loaded.
func NewGenerator() *Generator {
func NewGenerator(options ...Option) *Generator {
g := &Generator{
Version: "-",
Revision: "-",
BuildDate: "-",
BuiltBy: "-",
knownTemplates: make(map[string]*template.Template),
userTemplateNames: make([]string, 0),
t: template.New("generator"),
fileSet: token.NewFileSet(),
noPrefix: false,
replacementNames: map[string]string{},
jsonPkg: "encoding/json",
userTemplateNames: make([]string, 0),
GeneratorConfig: GeneratorConfig{
NoPrefix: false,
ReplacementNames: map[string]string{},
JSONPkg: "encoding/json",
},
}
// Apply all options
for _, option := range options {
option(&g.GeneratorConfig)
}
funcs := sprig.TxtFuncMap()
@@ -106,144 +93,66 @@ func NewGenerator() *Generator {
g.t.Funcs(funcs)
g.addEmbeddedTemplates()
g.updateTemplates()
// Process template files if any were provided via options
// This must happen AFTER embedded templates are added and updated
g.processUserTemplates()
return g
}
// WithNoPrefix is used to change the enum const values generated to not have the enum on them.
func (g *Generator) WithNoPrefix() *Generator {
g.noPrefix = true
return g
}
// WithLowercaseVariant is used to change the enum const values generated to not have the enum on them.
func (g *Generator) WithLowercaseVariant() *Generator {
g.lowercaseLookup = true
return g
}
// WithLowercaseVariant is used to change the enum const values generated to not have the enum on them.
func (g *Generator) WithCaseInsensitiveParse() *Generator {
g.lowercaseLookup = true
g.caseInsensitive = true
return g
}
// WithMarshal is used to add marshalling to the enum
func (g *Generator) WithMarshal() *Generator {
g.marshal = true
return g
}
// WithSQLDriver is used to add marshalling to the enum
func (g *Generator) WithSQLDriver() *Generator {
g.sql = true
return g
}
// WithSQLInt is used to signal a string to be stored as an int.
func (g *Generator) WithSQLInt() *Generator {
g.sqlint = true
return g
}
// WithFlag is used to add flag methods to the enum
func (g *Generator) WithFlag() *Generator {
g.flag = true
return g
}
// WithNames is used to add Names methods to the enum
func (g *Generator) WithNames() *Generator {
g.names = true
return g
}
// WithValues is used to add Values methods to the enum
func (g *Generator) WithValues() *Generator {
g.values = true
return g
}
// WithoutSnakeToCamel is used to add flag methods to the enum
func (g *Generator) WithoutSnakeToCamel() *Generator {
g.leaveSnakeCase = true
return g
}
// WithJsonPkg is used to add a custom json package to the imports
func (g *Generator) WithJsonPkg(pkg string) *Generator {
g.jsonPkg = pkg
return g
}
// WithPrefix is used to add a custom prefix to the enum constants
func (g *Generator) WithPrefix(prefix string) *Generator {
g.prefix = prefix
return g
}
// WithPtr adds a way to get a pointer value straight from the const value.
func (g *Generator) WithPtr() *Generator {
g.ptr = true
return g
}
// WithSQLNullInt is used to add a null int option for SQL interactions.
func (g *Generator) WithSQLNullInt() *Generator {
g.sqlNullInt = true
return g
}
// WithSQLNullStr is used to add a null string option for SQL interactions.
func (g *Generator) WithSQLNullStr() *Generator {
g.sqlNullStr = true
return g
}
// WithMustParse is used to add a method `MustParse` that will panic on failure.
func (g *Generator) WithMustParse() *Generator {
g.mustParse = true
return g
}
// WithForceLower is used to force enums names to lower case while keeping variable names the same.
func (g *Generator) WithForceLower() *Generator {
g.forceLower = true
return g
}
// WithForceUpper is used to force enums names to upper case while keeping variable names the same.
func (g *Generator) WithForceUpper() *Generator {
g.forceUpper = true
return g
}
// WithNoComments is used to remove auto generated comments from the enum.
func (g *Generator) WithNoComments() *Generator {
g.noComments = true
return g
}
// WithBuildTags will add build tags to the generated file.
func (g *Generator) WithBuildTags(tags ...string) *Generator {
g.buildTags = append(g.buildTags, tags...)
return g
}
// WithAliases will set up aliases for the generator.
func (g *Generator) WithAliases(aliases map[string]string) *Generator {
if aliases == nil {
return g
// NewGeneratorWithConfig is a constructor method for creating a new Generator with
// a configuration struct instead of using the functional options pattern.
func NewGeneratorWithConfig(config GeneratorConfig) *Generator {
g := &Generator{
Version: "-",
Revision: "-",
BuildDate: "-",
BuiltBy: "-",
knownTemplates: make(map[string]*template.Template),
t: template.New("generator"),
fileSet: token.NewFileSet(),
userTemplateNames: make([]string, 0),
GeneratorConfig: config,
}
g.replacementNames = aliases
funcs := sprig.TxtFuncMap()
funcs["stringify"] = Stringify
funcs["mapify"] = Mapify
funcs["unmapify"] = Unmapify
funcs["namify"] = Namify
funcs["offset"] = Offset
funcs["quote"] = strconv.Quote
g.t.Funcs(funcs)
g.addEmbeddedTemplates()
g.updateTemplates()
// Process template files if any were provided
// This must happen AFTER embedded templates are added and updated
g.processUserTemplates()
return g
}
// processUserTemplates handles loading and processing user template files
func (g *Generator) processUserTemplates() {
if len(g.TemplateFileNames) > 0 {
for _, ut := range template.Must(g.t.ParseFiles(g.TemplateFileNames...)).Templates() {
if _, ok := g.knownTemplates[ut.Name()]; !ok {
g.userTemplateNames = append(g.userTemplateNames, ut.Name())
}
}
sort.Strings(g.userTemplateNames)
g.updateTemplates()
}
}
func (g *Generator) anySQLEnabled() bool {
return g.sql || g.sqlNullStr || g.sqlint || g.sqlNullInt
return g.SQL || g.SQLNullStr || g.SQLInt || g.SQLNullInt
}
// ParseAliases is used to add aliases to replace during name sanitization.
@@ -264,18 +173,6 @@ func ParseAliases(aliases []string) (map[string]string, error) {
return aliasMap, nil
}
// WithTemplates is used to provide the filenames of additional templates.
func (g *Generator) WithTemplates(filenames ...string) *Generator {
for _, ut := range template.Must(g.t.ParseFiles(filenames...)).Templates() {
if _, ok := g.knownTemplates[ut.Name()]; !ok {
g.userTemplateNames = append(g.userTemplateNames, ut.Name())
}
}
g.updateTemplates()
sort.Strings(g.userTemplateNames)
return g
}
// GenerateFromFile is responsible for orchestrating the Code generation. It results in a byte array
// that can be written to any file desired. It has already had goimports run on the code before being returned.
func (g *Generator) GenerateFromFile(inputFile string) ([]byte, error) {
@@ -302,8 +199,8 @@ func (g *Generator) Generate(f *ast.File) ([]byte, error) {
"revision": g.Revision,
"buildDate": g.BuildDate,
"builtBy": g.BuiltBy,
"buildTags": g.buildTags,
"jsonpkg": g.jsonPkg,
"buildTags": g.BuildTags,
"jsonpkg": g.JSONPkg,
})
if err != nil {
return nil, fmt.Errorf("failed writing header: %w", err)
@@ -330,22 +227,22 @@ func (g *Generator) Generate(f *ast.File) ([]byte, error) {
data := map[string]interface{}{
"enum": enum,
"name": name,
"lowercase": g.lowercaseLookup,
"nocase": g.caseInsensitive,
"nocomments": g.noComments,
"marshal": g.marshal,
"sql": g.sql,
"sqlint": g.sqlint,
"flag": g.flag,
"names": g.names,
"ptr": g.ptr,
"values": g.values,
"lowercase": g.LowercaseLookup,
"nocase": g.CaseInsensitive,
"nocomments": g.NoComments,
"marshal": g.Marshal,
"sql": g.SQL,
"sqlint": g.SQLInt,
"flag": g.Flag,
"names": g.Names,
"ptr": g.Ptr,
"values": g.Values,
"anySQLEnabled": g.anySQLEnabled(),
"sqlnullint": g.sqlNullInt,
"sqlnullstr": g.sqlNullStr,
"mustparse": g.mustParse,
"forcelower": g.forceLower,
"forceupper": g.forceUpper,
"sqlnullint": g.SQLNullInt,
"sqlnullstr": g.SQLNullStr,
"mustparse": g.MustParse,
"forcelower": g.ForceLower,
"forceupper": g.ForceUpper,
}
templateName := "enum"
@@ -402,11 +299,11 @@ func (g *Generator) parseEnum(ts *ast.TypeSpec) (*Enum, error) {
enum.Name = ts.Name.Name
enum.Type = fmt.Sprintf("%s", ts.Type)
if !g.noPrefix {
if !g.NoPrefix {
enum.Prefix = ts.Name.Name
}
if g.prefix != "" {
enum.Prefix = g.prefix + enum.Prefix
if g.Prefix != "" {
enum.Prefix = g.Prefix + enum.Prefix
}
commentPreEnumDecl, _, _ := strings.Cut(ts.Doc.Text(), `ENUM(`)
@@ -489,7 +386,7 @@ func (g *Generator) parseEnum(ts *ast.TypeSpec) (*Enum, error) {
if name != skipHolder {
prefixedName = enum.Prefix + name
prefixedName = g.sanitizeValue(prefixedName)
if !g.leaveSnakeCase {
if !g.LeaveSnakeCase {
prefixedName = snakeToCamelCase(prefixedName)
}
}
@@ -549,7 +446,7 @@ func (g *Generator) sanitizeValue(value string) string {
}
replacedValue := value
for k, v := range g.replacementNames {
for k, v := range g.ReplacementNames {
replacedValue = strings.ReplaceAll(replacedValue, k, v)
}
+50 -49
View File
@@ -27,8 +27,7 @@ func Test118NoStructFile(t *testing.T) {
}
`
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
@@ -41,8 +40,7 @@ func Test118NoStructFile(t *testing.T) {
// TestNoFile
func Test118NoFile(t *testing.T) {
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
// Parse the file given in arguments
_, err := g.GenerateFromFile("")
assert.NotNil(t, err, "Error generating formatted code")
@@ -50,12 +48,13 @@ func Test118NoFile(t *testing.T) {
// TestExampleFile
func Test118ExampleFile(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithSQLDriver().
WithCaseInsensitiveParse().
WithNames().
WithoutSnakeToCamel()
g := NewGenerator(
WithMarshal(),
WithSQLDriver(),
WithCaseInsensitiveParse(),
WithNames(),
WithoutSnakeToCamel(),
)
for name, testExample := range testExampleFiles {
t.Run(name, func(t *testing.T) {
@@ -75,15 +74,16 @@ func Test118ExampleFile(t *testing.T) {
// TestExampleFileMoreOptions
func Test118ExampleFileMoreOptions(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithSQLDriver().
WithCaseInsensitiveParse().
WithNames().
WithoutSnakeToCamel().
WithMustParse().
WithForceLower().
WithTemplates(`../example/user_template.tmpl`)
g := NewGenerator(
WithMarshal(),
WithSQLDriver(),
WithCaseInsensitiveParse(),
WithNames(),
WithoutSnakeToCamel(),
WithMustParse(),
WithForceLower(),
WithTemplates(`../example/user_template.tmpl`),
)
for name, testExample := range testExampleFiles {
t.Run(name, func(t *testing.T) {
// Parse the file given in arguments
@@ -102,12 +102,13 @@ func Test118ExampleFileMoreOptions(t *testing.T) {
// TestExampleFile
func Test118NoPrefixExampleFile(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithLowercaseVariant().
WithNoPrefix().
WithFlag().
WithoutSnakeToCamel()
g := NewGenerator(
WithMarshal(),
WithLowercaseVariant(),
WithNoPrefix(),
WithFlag(),
WithoutSnakeToCamel(),
)
for name, testExample := range testExampleFiles {
t.Run(name, func(t *testing.T) {
// Parse the file given in arguments
@@ -126,11 +127,12 @@ func Test118NoPrefixExampleFile(t *testing.T) {
// TestExampleFile
func Test118NoPrefixExampleFileWithSnakeToCamel(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithLowercaseVariant().
WithNoPrefix().
WithFlag()
g := NewGenerator(
WithMarshal(),
WithLowercaseVariant(),
WithNoPrefix(),
WithFlag(),
)
for name, testExample := range testExampleFiles {
t.Run(name, func(t *testing.T) {
@@ -150,16 +152,17 @@ func Test118NoPrefixExampleFileWithSnakeToCamel(t *testing.T) {
// TestCustomPrefixExampleFile
func Test118CustomPrefixExampleFile(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithLowercaseVariant().
WithNoPrefix().
WithFlag().
WithoutSnakeToCamel().
WithPtr().
WithSQLNullInt().
WithSQLNullStr().
WithPrefix("Custom_prefix_")
g := NewGenerator(
WithMarshal(),
WithLowercaseVariant(),
WithNoPrefix(),
WithFlag(),
WithoutSnakeToCamel(),
WithPtr(),
WithSQLNullInt(),
WithSQLNullStr(),
WithPrefix("Custom_prefix_"),
)
for name, testExample := range testExampleFiles {
t.Run(name, func(t *testing.T) {
// Parse the file given in arguments
@@ -242,8 +245,7 @@ func Test118EnumParseFailure(t *testing.T) {
//}
type Animal int
`
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
@@ -263,8 +265,7 @@ func Test118UintInvalidParsing(t *testing.T) {
//)
type Animal uint
`
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
@@ -284,8 +285,7 @@ func Test118IntInvalidParsing(t *testing.T) {
//)
type Animal int
`
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
@@ -305,9 +305,10 @@ func Test118Aliasing(t *testing.T) {
`
aliases, err := ParseAliases([]string{"CDEF:C"})
require.NoError(t, err)
g := NewGenerator().
WithoutSnakeToCamel().
WithAliases(aliases)
g := NewGenerator(
WithoutSnakeToCamel(),
WithAliases(aliases),
)
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
+216 -64
View File
@@ -24,8 +24,7 @@ func TestNoStructFile(t *testing.T) {
}
`
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
@@ -38,8 +37,7 @@ func TestNoStructFile(t *testing.T) {
// TestNoFile
func TestNoFile(t *testing.T) {
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
// Parse the file given in arguments
_, err := g.GenerateFromFile("")
assert.NotNil(t, err, "Error generating formatted code")
@@ -47,12 +45,13 @@ func TestNoFile(t *testing.T) {
// TestExampleFile
func TestExampleFile(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithSQLDriver().
WithCaseInsensitiveParse().
WithNames().
WithoutSnakeToCamel()
g := NewGenerator(
WithMarshal(),
WithSQLDriver(),
WithCaseInsensitiveParse(),
WithNames(),
WithoutSnakeToCamel(),
)
// Parse the file given in arguments
imported, err := g.GenerateFromFile(testExample)
require.Nil(t, err, "Error generating formatted code")
@@ -67,15 +66,16 @@ func TestExampleFile(t *testing.T) {
// TestExampleFileMoreOptions
func TestExampleFileMoreOptions(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithSQLDriver().
WithCaseInsensitiveParse().
WithNames().
WithoutSnakeToCamel().
WithMustParse().
WithForceLower().
WithTemplates(`../example/user_template.tmpl`)
g := NewGenerator(
WithMarshal(),
WithSQLDriver(),
WithCaseInsensitiveParse(),
WithNames(),
WithoutSnakeToCamel(),
WithMustParse(),
WithForceLower(),
WithTemplates(`../example/user_template.tmpl`),
)
// Parse the file given in arguments
imported, err := g.GenerateFromFile(testExample)
require.Nil(t, err, "Error generating formatted code")
@@ -90,14 +90,15 @@ func TestExampleFileMoreOptions(t *testing.T) {
// TestExampleFileMoreOptionsWithForceUpper — test with force upper option
func TestExampleFileMoreOptionsWithForceUpper(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithSQLDriver().
WithNames().
WithoutSnakeToCamel().
WithMustParse().
WithForceUpper().
WithTemplates(`../example/user_template.tmpl`)
g := NewGenerator(
WithMarshal(),
WithSQLDriver(),
WithNames(),
WithoutSnakeToCamel(),
WithMustParse(),
WithForceUpper(),
WithTemplates(`../example/user_template.tmpl`),
)
// Parse the file given in arguments
imported, err := g.GenerateFromFile(testExample)
require.Nil(t, err, "Error generating formatted code")
@@ -112,12 +113,13 @@ func TestExampleFileMoreOptionsWithForceUpper(t *testing.T) {
// TestExampleFile
func TestNoPrefixExampleFile(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithLowercaseVariant().
WithNoPrefix().
WithFlag().
WithoutSnakeToCamel()
g := NewGenerator(
WithMarshal(),
WithLowercaseVariant(),
WithNoPrefix(),
WithFlag(),
WithoutSnakeToCamel(),
)
// Parse the file given in arguments
imported, err := g.GenerateFromFile(testExample)
require.Nil(t, err, "Error generating formatted code")
@@ -132,13 +134,14 @@ func TestNoPrefixExampleFile(t *testing.T) {
// TestExampleFile
func TestReplacePrefixExampleFile(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithLowercaseVariant().
WithNoPrefix().
WithPrefix("MyPrefix_").
WithFlag().
WithoutSnakeToCamel()
g := NewGenerator(
WithMarshal(),
WithLowercaseVariant(),
WithNoPrefix(),
WithPrefix("MyPrefix_"),
WithFlag(),
WithoutSnakeToCamel(),
)
// Parse the file given in arguments
imported, err := g.GenerateFromFile(testExample)
require.Nil(t, err, "Error generating formatted code")
@@ -153,11 +156,12 @@ func TestReplacePrefixExampleFile(t *testing.T) {
// TestExampleFile
func TestNoPrefixExampleFileWithSnakeToCamel(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithLowercaseVariant().
WithNoPrefix().
WithFlag()
g := NewGenerator(
WithMarshal(),
WithLowercaseVariant(),
WithNoPrefix(),
WithFlag(),
)
// Parse the file given in arguments
imported, err := g.GenerateFromFile(testExample)
@@ -173,16 +177,17 @@ func TestNoPrefixExampleFileWithSnakeToCamel(t *testing.T) {
// TestCustomPrefixExampleFile
func TestCustomPrefixExampleFile(t *testing.T) {
g := NewGenerator().
WithMarshal().
WithLowercaseVariant().
WithNoPrefix().
WithFlag().
WithoutSnakeToCamel().
WithPtr().
WithSQLNullInt().
WithSQLNullStr().
WithPrefix("Custom_prefix_")
g := NewGenerator(
WithMarshal(),
WithLowercaseVariant(),
WithNoPrefix(),
WithFlag(),
WithoutSnakeToCamel(),
WithPtr(),
WithSQLNullInt(),
WithSQLNullStr(),
WithPrefix("Custom_prefix_"),
)
// Parse the file given in arguments
imported, err := g.GenerateFromFile(testExample)
require.Nil(t, err, "Error generating formatted code")
@@ -265,8 +270,7 @@ func TestEnumParseFailure(t *testing.T) {
//}
type Animal int
`
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
@@ -286,8 +290,7 @@ func TestUintInvalidParsing(t *testing.T) {
//)
type Animal uint
`
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
@@ -307,8 +310,7 @@ func TestIntInvalidParsing(t *testing.T) {
//)
type Animal int
`
g := NewGenerator().
WithoutSnakeToCamel()
g := NewGenerator(WithoutSnakeToCamel())
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
@@ -328,9 +330,10 @@ func TestAliasing(t *testing.T) {
`
aliases, err := ParseAliases([]string{"CDEF:C"})
require.NoError(t, err)
g := NewGenerator().
WithoutSnakeToCamel().
WithAliases(aliases)
g := NewGenerator(
WithoutSnakeToCamel(),
WithAliases(aliases),
)
f, err := parser.ParseFile(g.fileSet, "TestRequiredErrors", input, parser.ParseComments)
assert.Nil(t, err, "Error parsing no struct input")
@@ -440,3 +443,152 @@ func TestStringWithSingleBacktickValue(t *testing.T) {
fmt.Println(string(output))
}
}
// TestNewGeneratorWithConfig tests the NewGeneratorWithConfig constructor
func TestNewGeneratorWithConfig(t *testing.T) {
config := GeneratorConfig{
NoPrefix: true,
LowercaseLookup: true,
Marshal: true,
SQL: true,
SQLInt: true,
JSONPkg: "custom/json",
Prefix: "TestPrefix",
BuildTags: []string{"tag1", "tag2"},
NoComments: true,
Values: true,
}
g := NewGeneratorWithConfig(config)
assert.NotNil(t, g)
assert.Equal(t, config.NoPrefix, g.NoPrefix)
assert.Equal(t, config.LowercaseLookup, g.LowercaseLookup)
assert.Equal(t, config.Marshal, g.Marshal)
assert.Equal(t, config.SQL, g.SQL)
assert.Equal(t, config.SQLInt, g.SQLInt)
assert.Equal(t, config.JSONPkg, g.JSONPkg)
assert.Equal(t, config.Prefix, g.Prefix)
assert.Equal(t, config.BuildTags, g.BuildTags)
assert.Equal(t, config.NoComments, g.NoComments)
assert.Equal(t, config.Values, g.Values)
// Test default values
assert.Equal(t, "-", g.Version)
assert.Equal(t, "-", g.Revision)
assert.Equal(t, "-", g.BuildDate)
assert.Equal(t, "-", g.BuiltBy)
assert.NotNil(t, g.knownTemplates)
assert.NotNil(t, g.t)
assert.NotNil(t, g.fileSet)
assert.NotNil(t, g.userTemplateNames)
}
// TestNewGeneratorConfig tests the NewGeneratorConfig constructor
func TestNewGeneratorConfig(t *testing.T) {
config := NewGeneratorConfig()
assert.NotNil(t, config)
assert.False(t, config.NoPrefix)
assert.NotNil(t, config.ReplacementNames)
assert.Equal(t, "encoding/json", config.JSONPkg)
assert.Empty(t, config.ReplacementNames)
}
// TestWithSQLInt tests the WithSQLInt option
func TestWithSQLInt(t *testing.T) {
config := &GeneratorConfig{}
option := WithSQLInt()
assert.False(t, config.SQLInt)
option(config)
assert.True(t, config.SQLInt)
}
// TestWithValues tests the WithValues option
func TestWithValues(t *testing.T) {
config := &GeneratorConfig{}
option := WithValues()
assert.False(t, config.Values)
option(config)
assert.True(t, config.Values)
}
// TestWithJsonPkg tests the WithJsonPkg option
func TestWithJsonPkg(t *testing.T) {
config := &GeneratorConfig{}
testPkg := "custom/json/package"
option := WithJsonPkg(testPkg)
assert.Empty(t, config.JSONPkg)
option(config)
assert.Equal(t, testPkg, config.JSONPkg)
}
// TestWithNoComments tests the WithNoComments option
func TestWithNoComments(t *testing.T) {
config := &GeneratorConfig{}
option := WithNoComments()
assert.False(t, config.NoComments)
option(config)
assert.True(t, config.NoComments)
}
// TestWithBuildTags tests the WithBuildTags option
func TestWithBuildTags(t *testing.T) {
config := &GeneratorConfig{}
testTags := []string{"tag1", "tag2", "tag3"}
option := WithBuildTags(testTags...)
assert.Empty(t, config.BuildTags)
option(config)
assert.Equal(t, testTags, config.BuildTags)
// Test appending more tags
moreTags := []string{"tag4", "tag5"}
option2 := WithBuildTags(moreTags...)
option2(config)
expectedTags := append(testTags, moreTags...)
assert.Equal(t, expectedTags, config.BuildTags)
}
// TestAllOptionsIntegration tests using multiple options together
func TestAllOptionsIntegration(t *testing.T) {
g := NewGenerator(
WithSQLInt(),
WithValues(),
WithJsonPkg("custom/json"),
WithNoComments(),
WithBuildTags("integration", "test"),
)
assert.True(t, g.SQLInt)
assert.True(t, g.Values)
assert.Equal(t, "custom/json", g.JSONPkg)
assert.True(t, g.NoComments)
assert.Equal(t, []string{"integration", "test"}, g.BuildTags)
}
// TestGeneratorConfigWithTemplates tests NewGeneratorWithConfig with templates
func TestGeneratorConfigWithTemplates(t *testing.T) {
config := GeneratorConfig{
// Use empty template file names to avoid file not found errors
TemplateFileNames: []string{},
}
g := NewGeneratorWithConfig(config)
assert.NotNil(t, g)
assert.Equal(t, config.TemplateFileNames, g.TemplateFileNames)
// Test with non-empty but valid scenario - no actual templates needed for coverage
config2 := GeneratorConfig{
NoPrefix: true,
Values: true,
}
g2 := NewGeneratorWithConfig(config2)
assert.NotNil(t, g2)
assert.True(t, g2.NoPrefix)
assert.True(t, g2.Values)
}
+198
View File
@@ -0,0 +1,198 @@
package generator
// GeneratorConfig holds all the configuration options for the Generator
type GeneratorConfig struct {
NoPrefix bool `json:"no_prefix"`
LowercaseLookup bool `json:"lowercase_lookup"`
CaseInsensitive bool `json:"case_insensitive"`
Marshal bool `json:"marshal"`
SQL bool `json:"sql"`
SQLInt bool `json:"sql_int"`
Flag bool `json:"flag"`
Names bool `json:"names"`
Values bool `json:"values"`
LeaveSnakeCase bool `json:"leave_snake_case"`
JSONPkg string `json:"json_pkg"`
Prefix string `json:"prefix"`
SQLNullInt bool `json:"sql_null_int"`
SQLNullStr bool `json:"sql_null_str"`
Ptr bool `json:"ptr"`
MustParse bool `json:"must_parse"`
ForceLower bool `json:"force_lower"`
ForceUpper bool `json:"force_upper"`
NoComments bool `json:"no_comments"`
BuildTags []string `json:"build_tags"`
ReplacementNames map[string]string `json:"replacement_names"`
TemplateFileNames []string `json:"template_file_names"`
}
func NewGeneratorConfig() *GeneratorConfig {
return &GeneratorConfig{
NoPrefix: false,
ReplacementNames: map[string]string{},
JSONPkg: "encoding/json",
}
}
// Option is a function that modifies a Generator
type Option func(*GeneratorConfig)
// WithNoPrefix is used to change the enum const values generated to not have the enum on them.
func WithNoPrefix() Option {
return func(g *GeneratorConfig) {
g.NoPrefix = true
}
}
// WithLowercaseVariant is used to change the enum const values generated to not have the enum on them.
func WithLowercaseVariant() Option {
return func(g *GeneratorConfig) {
g.LowercaseLookup = true
}
}
// WithCaseInsensitiveParse is used to change the enum const values generated to not have the enum on them.
func WithCaseInsensitiveParse() Option {
return func(g *GeneratorConfig) {
g.LowercaseLookup = true
g.CaseInsensitive = true
}
}
// WithMarshal is used to add marshalling to the enum
func WithMarshal() Option {
return func(g *GeneratorConfig) {
g.Marshal = true
}
}
// WithSQLDriver is used to add marshalling to the enum
func WithSQLDriver() Option {
return func(g *GeneratorConfig) {
g.SQL = true
}
}
// WithSQLInt is used to signal a string to be stored as an int.
func WithSQLInt() Option {
return func(g *GeneratorConfig) {
g.SQLInt = true
}
}
// WithFlag is used to add flag methods to the enum
func WithFlag() Option {
return func(g *GeneratorConfig) {
g.Flag = true
}
}
// WithNames is used to add Names methods to the enum
func WithNames() Option {
return func(g *GeneratorConfig) {
g.Names = true
}
}
// WithValues is used to add Values methods to the enum
func WithValues() Option {
return func(g *GeneratorConfig) {
g.Values = true
}
}
// WithoutSnakeToCamel is used to add flag methods to the enum
func WithoutSnakeToCamel() Option {
return func(g *GeneratorConfig) {
g.LeaveSnakeCase = true
}
}
// WithJsonPkg is used to add a custom json package to the imports
func WithJsonPkg(pkg string) Option {
return func(g *GeneratorConfig) {
g.JSONPkg = pkg
}
}
// WithPrefix is used to add a custom prefix to the enum constants
func WithPrefix(prefix string) Option {
return func(g *GeneratorConfig) {
g.Prefix = prefix
}
}
// WithPtr adds a way to get a pointer value straight from the const value.
func WithPtr() Option {
return func(g *GeneratorConfig) {
g.Ptr = true
}
}
// WithSQLNullInt is used to add a null int option for SQL interactions.
func WithSQLNullInt() Option {
return func(g *GeneratorConfig) {
g.SQLNullInt = true
}
}
// WithSQLNullStr is used to add a null string option for SQL interactions.
func WithSQLNullStr() Option {
return func(g *GeneratorConfig) {
g.SQLNullStr = true
}
}
// WithMustParse is used to add a method `MustParse` that will panic on failure.
func WithMustParse() Option {
return func(g *GeneratorConfig) {
g.MustParse = true
}
}
// WithForceLower is used to force enums names to lower case while keeping variable names the same.
func WithForceLower() Option {
return func(g *GeneratorConfig) {
g.ForceLower = true
}
}
// WithForceUpper is used to force enums names to upper case while keeping variable names the same.
func WithForceUpper() Option {
return func(g *GeneratorConfig) {
g.ForceUpper = true
}
}
// WithNoComments is used to remove auto generated comments from the enum.
func WithNoComments() Option {
return func(g *GeneratorConfig) {
g.NoComments = true
}
}
// WithBuildTags will add build tags to the generated file.
func WithBuildTags(tags ...string) Option {
return func(g *GeneratorConfig) {
g.BuildTags = append(g.BuildTags, tags...)
}
}
// WithAliases will set up aliases for the generator.
func WithAliases(aliases map[string]string) Option {
return func(g *GeneratorConfig) {
if aliases == nil {
return
}
g.ReplacementNames = aliases
}
}
// WithTemplates is used to provide the filenames of additional templates.
func WithTemplates(filenames ...string) Option {
return func(g *GeneratorConfig) {
// Note: Template processing is deferred to the generator constructor
// because we need access to the template collection and knownTemplates
g.TemplateFileNames = append(g.TemplateFileNames, filenames...)
}
}
+39 -66
View File
@@ -194,82 +194,55 @@ func main() {
}
for _, fileOption := range argv.FileNames.Value() {
g := generator.NewGenerator()
g.Version = version
g.Revision = commit
g.BuildDate = date
g.BuiltBy = builtBy
// Build configuration structure
jsonPkg := argv.JsonPkg
if jsonPkg == "" {
jsonPkg = "encoding/json"
}
g.WithBuildTags(argv.BuildTags.Value()...)
g.WithAliases(aliases)
if argv.NoPrefix {
g.WithNoPrefix()
}
if argv.Lowercase {
g.WithLowercaseVariant()
}
if argv.NoCase {
g.WithCaseInsensitiveParse()
}
if argv.Marshal {
g.WithMarshal()
}
if argv.SQL {
g.WithSQLDriver()
}
if argv.SQLInt {
g.WithSQLInt()
}
if argv.Flag {
g.WithFlag()
}
if argv.Names {
g.WithNames()
}
if argv.Values {
g.WithValues()
}
if argv.LeaveSnakeCase {
g.WithoutSnakeToCamel()
}
if argv.JsonPkg != "" {
g.WithJsonPkg(argv.JsonPkg)
}
if argv.Prefix != "" {
g.WithPrefix(argv.Prefix)
}
if argv.Ptr {
g.WithPtr()
}
if argv.SQLNullInt {
g.WithSQLNullInt()
}
if argv.SQLNullStr {
g.WithSQLNullStr()
}
if argv.MustParse {
g.WithMustParse()
}
if argv.ForceLower {
g.WithForceLower()
}
if argv.ForceUpper {
g.WithForceUpper()
}
if argv.NoComments {
g.WithNoComments()
}
var templateFileNames []string
if templates := []string(argv.TemplateFileNames.Value()); len(templates) > 0 {
for _, t := range templates {
if fn, err := globFilenames(t); err != nil {
return err
} else {
g.WithTemplates(fn...)
templateFileNames = append(templateFileNames, fn...)
}
}
}
config := generator.GeneratorConfig{
NoPrefix: argv.NoPrefix,
LowercaseLookup: argv.Lowercase || argv.NoCase,
CaseInsensitive: argv.NoCase,
Marshal: argv.Marshal,
SQL: argv.SQL,
SQLInt: argv.SQLInt,
Flag: argv.Flag,
Names: argv.Names,
Values: argv.Values,
LeaveSnakeCase: argv.LeaveSnakeCase,
JSONPkg: jsonPkg,
Prefix: argv.Prefix,
SQLNullInt: argv.SQLNullInt,
SQLNullStr: argv.SQLNullStr,
Ptr: argv.Ptr,
MustParse: argv.MustParse,
ForceLower: argv.ForceLower,
ForceUpper: argv.ForceUpper,
NoComments: argv.NoComments,
BuildTags: argv.BuildTags.Value(),
ReplacementNames: aliases,
TemplateFileNames: templateFileNames,
}
// Create generator with configuration
g := generator.NewGeneratorWithConfig(config)
g.Version = version
g.Revision = commit
g.BuildDate = date
g.BuiltBy = builtBy
var filenames []string
if fn, err := globFilenames(fileOption); err != nil {
return err
+2111
View File
File diff suppressed because it is too large. Load diff