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

Accept the multiple --type options in order to output the generated code of the multiple types into a single file #14

Merged
merged 1 commit into from
Aug 21, 2022
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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ build4test: clean
-o $(RELEASE_DIR)/gonstructor_test cmd/gonstructor/gonstructor.go

gen4test: build4test
rm -f internal/test/*_gen.go
rm -f internal/test/*_gen.go internal/test/**/*_gen.go
go generate $(PKGS)

test: gen4test
Expand Down
150 changes: 93 additions & 57 deletions cmd/gonstructor/gonstructor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
Expand All @@ -20,29 +19,44 @@ const (
builderConstructorType = "builder"
)

var (
typeName = flag.String("type", "", "[mandatory] a type name")
output = flag.String("output", "", `[optional] output file name (default "srcdir/<type>_gen.go")`)
constructorTypes = flag.String("constructorTypes", allArgsConstructorType, fmt.Sprintf(`[optional] comma-separated list of constructor types; it expects "%s" and "%s"`, allArgsConstructorType, builderConstructorType))
shouldShowVersion = flag.Bool("version", false, "[optional] show the version information")
withGetter = flag.Bool("withGetter", false, "[optional] generate a constructor along with getter functions for each field")
initFunc = flag.String("init", "", "[optional] name of function to call on object after creating it")
)
type stringArrayFlag []string

func (a *stringArrayFlag) String() string {
return strings.Join(*a, ", ")
}

func (a *stringArrayFlag) Set(item string) error {
*a = append(*a, item)
return nil
}

func main() {
var typeNames stringArrayFlag
flag.Var(&typeNames, "type", `[mandatory] A type name. It accepts this option occurs multiple times to output the generated code of the multi types into a single file. If this option is given multiple times, the "-output" option becomes mandatory.`)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@moznion maybe do types=A,B,C,D,E,F?

For backward compatibility, you can keep type, if u want.

Copy link
Owner Author

@moznion moznion Aug 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, I intend to keep the backward compatibility. Also I think this way to specify the multiple options is suitable rather than comma-separated.
So if I have to have the consistency of the way to give this command's options, it should change -constructorTypes To -constructorType as well as this change.

Copy link

@Antonboom Antonboom Aug 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As u wish :)

output := flag.String("output", "", `[optional] Output file name (default "srcdir/<type>_gen.go"). See also "-type" option's description.'`)
constructorTypesStr := flag.String("constructorTypes", allArgsConstructorType, fmt.Sprintf(`[optional] comma-separated list of constructor types; it expects "%s" and "%s"`, allArgsConstructorType, builderConstructorType))
shouldShowVersion := flag.Bool("version", false, "[optional] show the version information")
withGetter := flag.Bool("withGetter", false, "[optional] generate a constructor along with getter functions for each field")
initFunc := flag.String("init", "", "[optional] name of function to call on object after creating it")

flag.Parse()

if *shouldShowVersion {
internal.ShowVersion()
return
}

if *typeName == "" {
if len(typeNames) <= 0 {
flag.Usage()
os.Exit(2)
}
if len(typeNames) >= 2 && *output == "" {
_, _ = fmt.Fprintln(os.Stderr, `[error] if "-type" option appears two or more times, the "-output" option must be specified.`)
flag.Usage()
os.Exit(2)
}

constructorTypes, err := getConstructorTypes()
constructorTypes, err := getConstructorTypes(*constructorTypesStr)
if err != nil {
log.Printf("[error] %s", err)
flag.Usage()
Expand All @@ -64,60 +78,82 @@ func main() {
log.Fatal(fmt.Errorf("[error] failed to parse a file: %w", err))
}

fields, err := constructor.CollectConstructorFieldsFromAST(*typeName, astFiles)
if err != nil {
log.Fatal(fmt.Errorf("[error] failed to collect fields from files: %w", err))
}
fileHeaderGenerated := false
for _, typeName := range typeNames {
fields, err := constructor.CollectConstructorFieldsFromAST(typeName, astFiles)
if err != nil {
log.Fatal(fmt.Errorf("[error] failed to collect fields from files: %w", err))
}

rootStmt := g.NewRoot(
g.NewComment(fmt.Sprintf(" Code generated by gonstructor %s; DO NOT EDIT.", strings.Join(os.Args[1:], " "))),
g.NewNewline(),
g.NewPackage(pkg.Name),
g.NewNewline(),
)

for _, constructorType := range constructorTypes {
var constructorGenerator constructor.Generator
switch constructorType {
case allArgsConstructorType:
constructorGenerator = &constructor.AllArgsConstructorGenerator{
TypeName: *typeName,
Fields: fields,
InitFunc: *initFunc,
}
rootStmt := g.NewRoot()

case builderConstructorType:
constructorGenerator = &constructor.BuilderGenerator{
TypeName: *typeName,
Fields: fields,
InitFunc: *initFunc,
if !fileHeaderGenerated {
rootStmt = rootStmt.AddStatements(
g.NewComment(fmt.Sprintf(" Code generated by gonstructor %s; DO NOT EDIT.", strings.Join(os.Args[1:], " "))),
g.NewNewline(),
g.NewPackage(pkg.Name),
g.NewNewline(),
)
}
fileHeaderGenerated = true

for _, constructorType := range constructorTypes {
var constructorGenerator constructor.Generator
switch constructorType {
case allArgsConstructorType:
constructorGenerator = &constructor.AllArgsConstructorGenerator{
TypeName: typeName,
Fields: fields,
InitFunc: *initFunc,
}

case builderConstructorType:
constructorGenerator = &constructor.BuilderGenerator{
TypeName: typeName,
Fields: fields,
InitFunc: *initFunc,
}

default:
// unreachable, just in case
log.Fatalf("[error] unexpected constructor type has come [given=%s]", constructorType)
}

default:
// unreachable, just in case
log.Fatalf("[error] unexpected constructor type has come [given=%s]", constructorType)
rootStmt = rootStmt.AddStatements(constructorGenerator.Generate(0))
}

rootStmt = rootStmt.AddStatements(constructorGenerator.Generate(0))
}
if *withGetter {
rootStmt = rootStmt.AddStatements(internal.GenerateGetters(typeName, fields))
}

if *withGetter {
rootStmt = rootStmt.AddStatements(internal.GenerateGetters(*typeName, fields))
}
code, err := rootStmt.Goimports().EnableSyntaxChecking().Generate(0)
if err != nil {
log.Fatal(fmt.Errorf("[error] failed to generate code: %w", err))
}

code, err := rootStmt.Goimports().EnableSyntaxChecking().Generate(0)
if err != nil {
log.Fatal(fmt.Errorf("[error] failed to generate code: %w", err))
}
err = func() error {
f, err := os.OpenFile(getFilenameToGenerate(typeName, *output, args), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return fmt.Errorf("[error] failed open a file to output the generated code: %w", err)
}

err = ioutil.WriteFile(getFilenameToGenerate(args), []byte(code), 0644)
if err != nil {
log.Fatal(fmt.Errorf("[error] failed output generated code to a file: %w", err))
defer f.Close()

_, err = f.WriteString(code)
if err != nil {
return fmt.Errorf("[error] failed output generated code to a file: %w", err)
}

return nil
}()
if err != nil {
log.Fatal(err)
}
}
}

func getConstructorTypes() ([]string, error) {
typs := strings.Split(*constructorTypes, ",")
func getConstructorTypes(constructorTypes string) ([]string, error) {
typs := strings.Split(constructorTypes, ",")
for _, typ := range typs {
if typ != allArgsConstructorType && typ != builderConstructorType {
return nil, fmt.Errorf("unexpected constructor type has come [given=%s]", typ)
Expand All @@ -134,9 +170,9 @@ func isDirectory(name string) bool {
return info.IsDir()
}

func getFilenameToGenerate(args []string) string {
if *output != "" {
return *output
func getFilenameToGenerate(typeName string, output string, args []string) string {
if output != "" {
return output
}

var dir string
Expand All @@ -145,5 +181,5 @@ func getFilenameToGenerate(args []string) string {
} else {
dir = filepath.Dir(args[0])
}
return fmt.Sprintf("%s/%s_gen.go", dir, strcase.ToSnake(*typeName))
return fmt.Sprintf("%s/%s_gen.go", dir, strcase.ToSnake(typeName))
}
86 changes: 86 additions & 0 deletions internal/test/multitypes/alpha_and_bravo_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions internal/test/multitypes/structure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package multitypes

//go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=AlphaStructure --type=BravoStructure --constructorTypes=allArgs,builder --withGetter --output=./alpha_and_bravo_gen.go"

type AlphaStructure struct {
foo string
bar int
}

type BravoStructure struct {
buz string
qux int
}
58 changes: 58 additions & 0 deletions internal/test/multitypes/structure_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package multitypes

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestFooAndBarStructureAllArgsConstructor(t *testing.T) {
givenFooString := "foo"
givenBarInt := 12345
givenBuzString := "buz"
givenQuxInt := 54321

gotAlpha := NewAlphaStructure(givenFooString, givenBarInt)
gotBravo := NewBravoStructure(givenBuzString, givenQuxInt)

assert.IsType(t, &AlphaStructure{}, gotAlpha)
assert.IsType(t, &BravoStructure{}, gotBravo)

assert.EqualValues(t, givenFooString, gotAlpha.foo)
assert.EqualValues(t, givenBarInt, gotAlpha.bar)

assert.EqualValues(t, givenBuzString, gotBravo.buz)
assert.EqualValues(t, givenQuxInt, gotBravo.qux)

// test for getters
assert.EqualValues(t, givenFooString, gotAlpha.GetFoo())
assert.EqualValues(t, givenBarInt, gotAlpha.GetBar())

assert.EqualValues(t, givenBuzString, gotBravo.GetBuz())
assert.EqualValues(t, givenQuxInt, gotBravo.GetQux())
}

func TestStructureBuilder(t *testing.T) {
givenFooString := "foo"
givenBarInt := 12345
givenBuzString := "buz"
givenQuxInt := 54321

alphaBuilder := NewAlphaStructureBuilder()
gotAlpha := alphaBuilder.Foo(givenFooString).
Bar(givenBarInt).
Build()
assert.IsType(t, &AlphaStructure{}, gotAlpha)

assert.EqualValues(t, givenFooString, gotAlpha.foo)
assert.EqualValues(t, givenBarInt, gotAlpha.bar)

bravoBuilder := NewBravoStructureBuilder()
gotBravo := bravoBuilder.Buz(givenBuzString).
Qux(givenQuxInt).
Build()
assert.IsType(t, &BravoStructure{}, gotBravo)

assert.EqualValues(t, givenBuzString, gotBravo.buz)
assert.EqualValues(t, givenQuxInt, gotBravo.qux)
}