From dde358be82e83096553822f4ceb2d0ec5e8f00d3 Mon Sep 17 00:00:00 2001 From: Joffref Date: Mon, 8 Jan 2024 12:20:29 +0100 Subject: [PATCH 1/4] WIP: PoC expose a fluent-api to generate code Signed-off-by: Joffref --- cmd/genz/genz.go | 79 ++++++++++---------- examples/1_validator/main.go | 51 +++++++++++++ examples/1_validator/test/expected.go | 6 -- examples/1_validator/test/human.go | 2 +- go.mod | 2 +- go.sum | 5 ++ internal/generator/generator.go | 13 ---- internal/generator/generator_test.go | 15 ---- internal/utils/helper.go | 14 ++++ internal/utils/helper_test.go | 20 ++++++ pkg/generator/code.go | 86 ++++++++++++++++++++++ pkg/generator/declaration.go | 5 ++ pkg/generator/function.go | 100 ++++++++++++++++++++++++++ pkg/generator/interface.go | 54 ++++++++++++++ pkg/generator/struct.go | 93 ++++++++++++++++++++++++ pkg/parser/parser.go | 23 ++++++ 16 files changed, 494 insertions(+), 74 deletions(-) create mode 100644 examples/1_validator/main.go create mode 100644 internal/utils/helper_test.go create mode 100644 pkg/generator/code.go create mode 100644 pkg/generator/declaration.go create mode 100644 pkg/generator/function.go create mode 100644 pkg/generator/interface.go create mode 100644 pkg/generator/struct.go create mode 100644 pkg/parser/parser.go diff --git a/cmd/genz/genz.go b/cmd/genz/genz.go index 8c0d234..5b61323 100644 --- a/cmd/genz/genz.go +++ b/cmd/genz/genz.go @@ -1,19 +1,19 @@ package genz import ( + "bytes" "flag" "fmt" "github.com/Joffref/genz/internal/parser" + "github.com/Joffref/genz/pkg/models" "io" "log" - "net/http" - "net/url" "os" "path/filepath" + "plugin" "strings" "github.com/Joffref/genz/internal/command" - "github.com/Joffref/genz/internal/generator" "github.com/Joffref/genz/internal/utils" ) @@ -22,17 +22,17 @@ type generateCommand struct { const ( generateCommandUsage = `Usage of genz: - genz [flags] -type T -template foo.tmpl [directory] - genz [flags] -type T -template foo.tmpl files... # Must be a single package + genz [flags] -type T -generator path/to/my/custom/generator:entrypoint [directory] + genz [flags] -type T -generator path/to/my/custom/generator:entrypoint files... # Must be a single package Flags:` ) var ( - generateCmd = flag.NewFlagSet("", flag.ExitOnError) - typeName = generateCmd.String("type", "", "name of the type to parse") - templateLocation = generateCmd.String("template", "", "go-template local or remote file") - output = generateCmd.String("output", "", "output file name; default srcdir/.gen.go") - buildTags = generateCmd.String("tags", "", "comma-separated list of build tags to apply") + generateCmd = flag.NewFlagSet("", flag.ExitOnError) + typeName = generateCmd.String("type", "", "name of the type to parse") + generator = generateCmd.String("generator", "", "name of the generator to use (e.g: path/to/my/custom/generator:entrypoint)") + output = generateCmd.String("output", "", "output file name; default srcdir/.gen.go") + buildTags = generateCmd.String("tags", "", "comma-separated list of build tags to apply") ) func init() { @@ -51,9 +51,13 @@ func (c generateCommand) ValidateArgs() error { generateCmd.Usage() return fmt.Errorf("missing 'type' argument") } - if len(*templateLocation) == 0 { + if len(*generator) == 0 { generateCmd.Usage() - return fmt.Errorf("missing 'template' argument") + return fmt.Errorf("missing 'generator' argument") + } + if len(strings.Split(*generator, ":")) != 2 { + generateCmd.Usage() + return fmt.Errorf("invalid 'generator' argument: should be path/to/my/custom/generator:entrypoint") } return nil } @@ -68,23 +72,24 @@ func (c generateCommand) Run() error { tags = strings.Split(*buildTags, ",") } - var template []byte - if url, _ := url.ParseRequestURI(*templateLocation); url != nil { - response, err := http.Get(*templateLocation) - if err != nil { - return fmt.Errorf("failed to make a request to %s: %v", *templateLocation, err) - } - body, err := io.ReadAll(response.Body) - if err != nil { - return fmt.Errorf("could not read body of remote template %s: %v", *templateLocation, err) - } - template = body - } else { - file, err := os.ReadFile(*templateLocation) - if err != nil { - return fmt.Errorf("failed to read template file %s: %v", *templateLocation, err) - } - template = file + pluginPath := strings.Split(*generator, ":")[0] + if _, err := os.Stat(pluginPath); err != nil { + return fmt.Errorf("could not find generator plugin: %s", err) + } + + p, err := plugin.Open(pluginPath) + if err != nil { + return fmt.Errorf("could not open generator plugin: %s", err) + } + + userDefinedGenerator, err := p.Lookup(strings.Split(*generator, ":")[1]) + if err != nil { + return fmt.Errorf("could not find generator entrypoint: %s", err) + } + + userDefinedGeneratorFunc, ok := userDefinedGenerator.(func(writer io.Writer, element models.ParsedElement) error) + if !ok { + return fmt.Errorf("could not find generator entrypoint: %s", err) } // We accept either one directory or a list of files. Which do we have? @@ -93,17 +98,15 @@ func (c generateCommand) Run() error { // Default: process whole package in current directory. args = []string{"."} } - buf, err := generator.Generate( - utils.LoadPackage(args, tags), - string(template), - *typeName, - parser.Parser, - ) + + parsedElement, err := parser.Parser(utils.LoadPackage(args, tags), *typeName) + + var buf bytes.Buffer + err = userDefinedGeneratorFunc(&buf, parsedElement) if err != nil { - return err + return fmt.Errorf("generating code: %s", err) } - - src := generator.Format(buf) + src := utils.Format(buf) var dir string if len(args) == 1 && utils.IsDirectory(args[0]) { diff --git a/examples/1_validator/main.go b/examples/1_validator/main.go new file mode 100644 index 0000000..176f6b2 --- /dev/null +++ b/examples/1_validator/main.go @@ -0,0 +1,51 @@ +//go:generate go build -buildmode=plugin -o main.so main.go + +package main + +import ( + "bytes" + "github.com/Joffref/genz/pkg/generator" + "github.com/Joffref/genz/pkg/models" + "io" +) + +func MyCustomGenerator(file io.Writer, element models.ParsedElement) error { + err := generator. + NewCode(file, element.PackageName). + WithHeaderComments("File generated by GenZ with template validator"). + WithImports("fmt", "unicode"). + WithDeclarations( + generator. + Function("Validate"). + WithReceiver("v", element.Type.InternalName, false). + WithReturns("error"). + WithBody(body(element.Element)), + ). + Generate() + if err != nil { + return err + } + return nil +} + +func body(element models.Element) string { + var buf bytes.Buffer + for _, attribute := range element.Attributes { + if attribute.Type.InternalName == "string" { + for _, comment := range attribute.Comments { + if comment == "+required" { + buf.WriteString("if v." + attribute.Name + " == \"\" {\n") + buf.WriteString("return fmt.Errorf(\"attribute '" + attribute.Name + "' must be set\")\n") + buf.WriteString("}\n") + } + if comment == "+startsWithCapital" { + buf.WriteString("if v." + attribute.Name + " != \"\" && !unicode.IsUpper(rune(v." + attribute.Name + "[0])) {\n") + buf.WriteString("return fmt.Errorf(\"attribute '" + attribute.Name + "' should start with a capital letter\")\n") + buf.WriteString("}\n") + } + } + } + } + buf.WriteString("return nil") + return buf.String() +} diff --git a/examples/1_validator/test/expected.go b/examples/1_validator/test/expected.go index 736e8d2..6aefcf7 100644 --- a/examples/1_validator/test/expected.go +++ b/examples/1_validator/test/expected.go @@ -7,20 +7,14 @@ import ( ) func (v Human) Validate() error { - if v.Firstname != "" && !unicode.IsUpper(rune(v.Firstname[0])) { return fmt.Errorf("attribute 'Firstname' should start with a capital letter") } - if v.Lastname == "" { return fmt.Errorf("attribute 'Lastname' must be set") } - if v.Lastname != "" && !unicode.IsUpper(rune(v.Lastname[0])) { return fmt.Errorf("attribute 'Lastname' should start with a capital letter") } - - // WORK IN PROGRESS - return nil } diff --git a/examples/1_validator/test/human.go b/examples/1_validator/test/human.go index 0f447e1..e1baf2d 100644 --- a/examples/1_validator/test/human.go +++ b/examples/1_validator/test/human.go @@ -1,6 +1,6 @@ package test -//go:generate genz -type Human -template ../main.tmpl -output human_validator.gen.go +//go:generate genz -type Human -generator ../main.so:MyCustomGenerator -output human_validator.gen.go type Human struct { //+startsWithCapital Firstname string diff --git a/go.mod b/go.mod index 4167f5d..fcc7f98 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Joffref/genz -go 1.20 +go 1.21 require ( github.com/Masterminds/sprig/v3 v3.2.3 diff --git a/go.sum b/go.sum index 3094fcb..9c8052d 100644 --- a/go.sum +++ b/go.sum @@ -9,6 +9,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -21,7 +22,9 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= @@ -31,6 +34,7 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -57,6 +61,7 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= +golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/generator/generator.go b/internal/generator/generator.go index d0a0925..ebae861 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -5,7 +5,6 @@ import ( "bytes" "fmt" "github.com/Joffref/genz/pkg/models" - "go/format" "html/template" "log" @@ -41,15 +40,3 @@ func Generate( log.Printf("generated buffer (%d bytes)", buf.Len()) return buf, nil } - -func Format(buf bytes.Buffer) []byte { - log.Print("gofmt-ing buffer") - - src, err := format.Source(buf.Bytes()) - if err != nil { - log.Printf("warning: internal error: invalid Go generated: %s", err) - log.Printf("warning: compile the package to analyze the error") - return buf.Bytes() - } - return src -} diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go index 70c229d..aa5ffb5 100644 --- a/internal/generator/generator_test.go +++ b/internal/generator/generator_test.go @@ -1,7 +1,6 @@ package generator_test import ( - "bytes" "errors" "github.com/Joffref/genz/pkg/models" "strings" @@ -68,17 +67,3 @@ func TestGenerateSuccess(t *testing.T) { t.Fatalf("expected TypeName, got %s", buf.String()) } } - -func TestFormatSuccessInvalidGoCode(t *testing.T) { - src := generator.Format(*bytes.NewBufferString("package[main\n\n\n")) - if string(src) != "package[main\n\n\n" { - t.Fatalf("expected formatted code, got: %q", src) - } -} - -func TestFormatSuccessValidGoCode(t *testing.T) { - src := generator.Format(*bytes.NewBufferString(" package main\n\n\n")) - if string(src) != "package main\n" { - t.Fatalf("expected formatted code, got: %q", src) - } -} diff --git a/internal/utils/helper.go b/internal/utils/helper.go index ab3fae9..c324258 100644 --- a/internal/utils/helper.go +++ b/internal/utils/helper.go @@ -1,7 +1,9 @@ package utils import ( + "bytes" "fmt" + "go/format" "golang.org/x/tools/go/packages" "log" "os" @@ -51,3 +53,15 @@ func RunCommand(command []string, verbose bool) error { } return nil } + +func Format(buf bytes.Buffer) []byte { + log.Print("gofmt-ing buffer") + + src, err := format.Source(buf.Bytes()) + if err != nil { + log.Printf("warning: internal error: invalid Go generated: %s", err) + log.Printf("warning: compile the package to analyze the error") + return buf.Bytes() + } + return src +} diff --git a/internal/utils/helper_test.go b/internal/utils/helper_test.go new file mode 100644 index 0000000..ccd6b7a --- /dev/null +++ b/internal/utils/helper_test.go @@ -0,0 +1,20 @@ +package utils + +import ( + "bytes" + "testing" +) + +func TestFormatSuccessInvalidGoCode(t *testing.T) { + src := Format(*bytes.NewBufferString("package[main\n\n\n")) + if string(src) != "package[main\n\n\n" { + t.Fatalf("expected formatted code, got: %q", src) + } +} + +func TestFormatSuccessValidGoCode(t *testing.T) { + src := Format(*bytes.NewBufferString(" package main\n\n\n")) + if string(src) != "package main\n" { + t.Fatalf("expected formatted code, got: %q", src) + } +} diff --git a/pkg/generator/code.go b/pkg/generator/code.go new file mode 100644 index 0000000..ce4e0b5 --- /dev/null +++ b/pkg/generator/code.go @@ -0,0 +1,86 @@ +package generator + +import ( + "bytes" + "fmt" + "github.com/Joffref/genz/internal/utils" + "io" +) + +type Code struct { + writer io.Writer + headerComments []string + packageName string + imports []string + namedImports map[string]string + declarations []Declaration +} + +func NewCode(writer io.Writer, packageName string) *Code { + return &Code{writer: writer, packageName: packageName} +} + +func (c *Code) WithHeaderComments(comments ...string) *Code { + c.headerComments = append(c.headerComments, comments...) + return c +} +func (c *Code) WithPackageName(packageName string) *Code { + c.packageName = packageName + return c +} + +func (c *Code) WithDeclarations(declarations ...Declaration) *Code { + c.declarations = append(c.declarations, declarations...) + return c +} + +func (c *Code) WithImports(imports ...string) *Code { + c.imports = append(c.imports, imports...) + return c +} + +func (c *Code) WithNamedImports(namedImports map[string]string) *Code { + if c.namedImports == nil { + c.namedImports = make(map[string]string) + } + for name, path := range namedImports { + c.namedImports[name] = path + } + return c +} + +func (c *Code) Generate() error { + var buf bytes.Buffer + if len(c.headerComments) > 0 { + for _, comment := range c.headerComments { + buf.WriteString(fmt.Sprintf("// %s\n", comment)) + } + } + if c.packageName != "" { + buf.WriteString(fmt.Sprintf("package %s\n\n", c.packageName)) + } else { + return fmt.Errorf("package name is required") + } + + if len(c.imports) > 0 || len(c.namedImports) > 0 { + buf.WriteString("import (\n") + for _, importPath := range c.imports { + buf.WriteString(fmt.Sprintf("\t\"%s\"\n", importPath)) + } + for name, importPath := range c.namedImports { + buf.WriteString(fmt.Sprintf("\t%s \"%s\"\n", name, importPath)) + } + buf.WriteString(")\n\n") + } + + for _, declaration := range c.declarations { + buf.WriteString(declaration.Generate()) + buf.WriteString("\n\n") + } + + _, err := c.writer.Write(utils.Format(buf)) + if err != nil { + return fmt.Errorf("failed to write generated code: %v", err) + } + return nil +} diff --git a/pkg/generator/declaration.go b/pkg/generator/declaration.go new file mode 100644 index 0000000..b56f469 --- /dev/null +++ b/pkg/generator/declaration.go @@ -0,0 +1,5 @@ +package generator + +type Declaration interface { + Generate() string +} diff --git a/pkg/generator/function.go b/pkg/generator/function.go new file mode 100644 index 0000000..bf80aee --- /dev/null +++ b/pkg/generator/function.go @@ -0,0 +1,100 @@ +package generator + +import ( + "bytes" +) + +type FuncDecl struct { + name string + inputs map[string]string + returns []string + receiverName string + receiverType string + isPtrReceiver bool + isSignature bool + body string +} + +func Function(name string) *FuncDecl { + return &FuncDecl{ + name: name, + } +} + +func (f *FuncDecl) WithReceiver(receiverName string, receiverType string, isPtrReceiver bool) *FuncDecl { + f.isPtrReceiver = isPtrReceiver + f.receiverType = receiverType + f.receiverName = receiverName + return f +} + +func (f *FuncDecl) WithInputs(inputs map[string]string) *FuncDecl { + if f.inputs == nil { + f.inputs = make(map[string]string) + } + for inputName, inputType := range inputs { + f.inputs[inputName] = inputType + } + return f +} + +func (f *FuncDecl) WithReturns(returns ...string) *FuncDecl { + f.returns = returns + return f +} + +func (f *FuncDecl) WithBody(body string) *FuncDecl { + f.body = body + return f +} + +func (f *FuncDecl) SignatureOnly() *FuncDecl { + f.body = "" + f.isSignature = true + return f +} + +func (f *FuncDecl) Generate() string { + var buf bytes.Buffer + buf.WriteString("func ") + if f.receiverName != "" { + buf.WriteString("(") + buf.WriteString(f.receiverName) + if f.isPtrReceiver { + buf.WriteString("*") + } + buf.WriteString(" ") + buf.WriteString(f.receiverType) + buf.WriteString(")") + } + buf.WriteString(" ") + buf.WriteString(f.name) + buf.WriteString("(") + for inputName, inputType := range f.inputs { + buf.WriteString(inputName) + buf.WriteString(" ") + buf.WriteString(inputType) + buf.WriteString(", ") + } + buf.WriteString(") ") + if len(f.returns) > 0 { + buf.WriteString("(") + for _, returnType := range f.returns { + buf.WriteString(returnType) + buf.WriteString(", ") + } + buf.WriteString(")") + } + if f.isSignature { + buf.WriteString("\n") + } else { + buf.WriteString(" {") + if f.body != "" { + buf.WriteString("\n") + buf.WriteString(f.body) + buf.WriteString("\n") + } + buf.WriteString("}") + } + return buf.String() +} diff --git a/pkg/generator/interface.go b/pkg/generator/interface.go new file mode 100644 index 0000000..020c920 --- /dev/null +++ b/pkg/generator/interface.go @@ -0,0 +1,54 @@ +package generator + +import ( + "bytes" + "fmt" +) + +type InterfaceDecl struct { + name string + comments []string + methods map[string]struct { + methodName string + methodDecl *FuncDecl + } +} + +func Interface(name string) *InterfaceDecl { + return &InterfaceDecl{ + name: name, + methods: make(map[string]struct { + methodName string + methodDecl *FuncDecl + }), + } +} + +func (i *InterfaceDecl) WithComments(comments ...string) *InterfaceDecl { + i.comments = append(i.comments, comments...) + return i +} + +func (i *InterfaceDecl) WithMethod(methodName string, methodDecl *FuncDecl) *InterfaceDecl { + methodDecl.SignatureOnly() + i.methods[methodName] = struct { + methodName string + methodDecl *FuncDecl + }{methodName: methodName, methodDecl: methodDecl} + return i +} + +func (i *InterfaceDecl) Generate() string { + var buf bytes.Buffer + if len(i.comments) > 0 { + for _, comment := range i.comments { + buf.WriteString(fmt.Sprintf("// %s\n", comment)) + } + } + buf.WriteString(fmt.Sprintf("type %s interface {\n", i.name)) + for _, method := range i.methods { + buf.WriteString(fmt.Sprintf("%s\n", method.methodDecl.Generate())) + } + buf.WriteString("}\n") + return buf.String() +} diff --git a/pkg/generator/struct.go b/pkg/generator/struct.go new file mode 100644 index 0000000..a03910e --- /dev/null +++ b/pkg/generator/struct.go @@ -0,0 +1,93 @@ +package generator + +import ( + "bytes" + "fmt" +) + +type StructDecl struct { + name string + fields map[string]struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + } +} + +func Struct(name string) *StructDecl { + return &StructDecl{ + name: name, + fields: make(map[string]struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }), + } +} + +func (s *StructDecl) WithField(fieldName string, fieldType string) *StructDecl { + s.fields[fieldName] = struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }{fieldName: fieldName, fieldType: fieldType} + return s +} + +func (s *StructDecl) WithFieldInlineComment(fieldName string, fieldType string, inlineComment string) *StructDecl { + s.fields[fieldName] = struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }{fieldName: fieldName, fieldType: fieldType, inlineComment: inlineComment} + return s +} + +func (s *StructDecl) WithFieldComments(fieldName string, fieldType string, comments []string) *StructDecl { + s.fields[fieldName] = struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }{fieldName: fieldName, fieldType: fieldType, comments: comments} + return s +} + +func (s *StructDecl) WithFieldTags(fieldName string, fieldType string, tags map[string]string) *StructDecl { + s.fields[fieldName] = struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }{fieldName: fieldName, fieldType: fieldType, tags: tags} + return s +} + +func (s *StructDecl) Generate() string { + var buf bytes.Buffer + buf.WriteString(fmt.Sprintf("type %s struct {\n", s.name)) + for _, field := range s.fields { + if field.inlineComment != "" { + buf.WriteString(fmt.Sprintf("\t%s %s // %s\n", field.fieldName, field.fieldType, field.inlineComment)) + } else if len(field.comments) > 0 { + buf.WriteString(fmt.Sprintf("\t%s %s // %s\n", field.fieldName, field.fieldType, field.comments[0])) + for _, comment := range field.comments[1:] { + buf.WriteString(fmt.Sprintf("\t// %s\n", comment)) + } + } else { + buf.WriteString(fmt.Sprintf("\t%s %s\n", field.fieldName, field.fieldType)) + } + } + buf.WriteString("}\n") + return buf.String() +} diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go new file mode 100644 index 0000000..207c164 --- /dev/null +++ b/pkg/parser/parser.go @@ -0,0 +1,23 @@ +package parser + +import ( + "fmt" + "github.com/Joffref/genz/internal/parser" + "github.com/Joffref/genz/internal/utils" + "github.com/Joffref/genz/pkg/models" +) + +// Parse parses a type from a package +// Note: patterns is a list of patterns to match packages +func Parse(typeName string, patterns []string, buildTags ...string) (models.ParsedElement, error) { + pkg := utils.LoadPackage(patterns, buildTags) + if pkg == nil { + return models.ParsedElement{}, fmt.Errorf("could not load package") + } + fmt.Println("pkg:", pkg.PkgPath) + element, err := parser.Parser(pkg, typeName) + if err != nil { + return models.ParsedElement{}, err + } + return element, err +} From 88029851c675467c0f982700515063c4592b31de Mon Sep 17 00:00:00 2001 From: Joffref Date: Tue, 9 Jan 2024 10:22:00 +0100 Subject: [PATCH 2/4] WIP: PoC expose a fluent-api to generate code Signed-off-by: Joffref --- .github/workflows/ci.yml | 15 +- .go-version | 1 - ADOPTERS.md | 0 CHANGELOG.md | 0 Makefile | 33 --- cmd/genz/genz.go | 134 ---------- cmd/genz/root.go | 37 --- cmd/genz/test.go | 46 ---- cmd/genz/version.go | 38 --- examples/1_validator/main.go | 24 +- examples/1_validator/main.tmpl | 33 --- examples/1_validator/main_test.go | 21 ++ examples/1_validator/test/human.go | 2 +- examples/2_getters/getters.tmpl | 9 - examples/2_getters/main.go | 29 +++ examples/2_getters/main_test.go | 20 ++ examples/2_getters/test/car.go | 8 +- examples/2_getters/test/expected.go | 8 +- examples/3_mock/main.go | 42 +++ examples/{4_mock => 3_mock}/mock.tmpl | 0 examples/{4_mock => 3_mock}/test/expected.go | 0 examples/{4_mock => 3_mock}/test/hello.go | 0 .../{4_mock => 3_mock}/test/hello_test.go | 0 examples/3_tests/getters.tmpl | 9 - examples/3_tests/tests/0_no_getter/car.go | 6 - .../3_tests/tests/0_no_getter/expected.go | 1 - examples/3_tests/tests/1_1_getter/car.go | 7 - examples/3_tests/tests/1_1_getter/car_test.go | 18 -- examples/3_tests/tests/1_1_getter/expected.go | 5 - genz/cli/command.go | 101 ++++++++ {pkg/generator => genz}/code.go | 2 +- genz/declaration.go | 240 ++++++++++++++++++ genz/generator.go | 10 + pkg/models/types.go => genz/models/element.go | 0 {pkg/parser => genz}/parser.go | 7 +- genz/testutils/helper.go | 34 +++ internal/command/command.go | 30 --- internal/command/command_test.go | 59 ----- internal/generator/generator.go | 42 --- internal/generator/generator_test.go | 69 ----- internal/parser/helper.go | 2 +- internal/parser/interface.go | 2 +- internal/parser/interface_test.go | 2 +- internal/parser/methods.go | 3 +- internal/parser/package.go | 2 +- internal/parser/package_test.go | 2 +- internal/parser/parser.go | 2 +- internal/parser/struct.go | 3 +- internal/parser/struct_test.go | 3 +- internal/testing/test.go | 100 -------- internal/testing/test_test.go | 82 ------ .../testing/testdata/accurate_expected/car.go | 6 - .../testdata/accurate_expected/expected.go | 1 - .../testdata/difference_with_expected/car.go | 6 - .../difference_with_expected/expected.go | 3 - .../testing/testdata/empty_directory/.KEEP | 0 .../testing/testdata/failing_unit_test/car.go | 7 - .../testdata/failing_unit_test/car_test.go | 7 - .../testdata/failing_unit_test/expected.go | 5 - internal/testing/testdata/getters.tmpl | 9 - .../testing/testdata/passing_unit_test/car.go | 7 - .../testdata/passing_unit_test/car_test.go | 11 - .../testdata/passing_unit_test/expected.go | 5 - .../testdata/two_generated_files/car.go | 7 - .../testdata/two_generated_files/expected.go | 1 - internal/testing/utils.go | 35 --- internal/testing/utils_test.go | 30 --- main.go | 12 - pkg/generator/declaration.go | 5 - pkg/generator/function.go | 100 -------- pkg/generator/interface.go | 54 ---- pkg/generator/struct.go | 93 ------- 72 files changed, 541 insertions(+), 1206 deletions(-) delete mode 100644 .go-version delete mode 100644 ADOPTERS.md delete mode 100644 CHANGELOG.md delete mode 100644 Makefile delete mode 100644 cmd/genz/genz.go delete mode 100644 cmd/genz/root.go delete mode 100644 cmd/genz/test.go delete mode 100644 cmd/genz/version.go delete mode 100644 examples/1_validator/main.tmpl create mode 100644 examples/1_validator/main_test.go delete mode 100644 examples/2_getters/getters.tmpl create mode 100644 examples/2_getters/main.go create mode 100644 examples/2_getters/main_test.go create mode 100644 examples/3_mock/main.go rename examples/{4_mock => 3_mock}/mock.tmpl (100%) rename examples/{4_mock => 3_mock}/test/expected.go (100%) rename examples/{4_mock => 3_mock}/test/hello.go (100%) rename examples/{4_mock => 3_mock}/test/hello_test.go (100%) delete mode 100644 examples/3_tests/getters.tmpl delete mode 100644 examples/3_tests/tests/0_no_getter/car.go delete mode 100644 examples/3_tests/tests/0_no_getter/expected.go delete mode 100644 examples/3_tests/tests/1_1_getter/car.go delete mode 100644 examples/3_tests/tests/1_1_getter/car_test.go delete mode 100644 examples/3_tests/tests/1_1_getter/expected.go create mode 100644 genz/cli/command.go rename {pkg/generator => genz}/code.go (99%) create mode 100644 genz/declaration.go create mode 100644 genz/generator.go rename pkg/models/types.go => genz/models/element.go (100%) rename {pkg/parser => genz}/parser.go (76%) create mode 100644 genz/testutils/helper.go delete mode 100644 internal/command/command.go delete mode 100644 internal/command/command_test.go delete mode 100644 internal/generator/generator.go delete mode 100644 internal/generator/generator_test.go delete mode 100644 internal/testing/test.go delete mode 100644 internal/testing/test_test.go delete mode 100644 internal/testing/testdata/accurate_expected/car.go delete mode 100644 internal/testing/testdata/accurate_expected/expected.go delete mode 100644 internal/testing/testdata/difference_with_expected/car.go delete mode 100644 internal/testing/testdata/difference_with_expected/expected.go delete mode 100644 internal/testing/testdata/empty_directory/.KEEP delete mode 100644 internal/testing/testdata/failing_unit_test/car.go delete mode 100644 internal/testing/testdata/failing_unit_test/car_test.go delete mode 100644 internal/testing/testdata/failing_unit_test/expected.go delete mode 100644 internal/testing/testdata/getters.tmpl delete mode 100644 internal/testing/testdata/passing_unit_test/car.go delete mode 100644 internal/testing/testdata/passing_unit_test/car_test.go delete mode 100644 internal/testing/testdata/passing_unit_test/expected.go delete mode 100644 internal/testing/testdata/two_generated_files/car.go delete mode 100644 internal/testing/testdata/two_generated_files/expected.go delete mode 100644 internal/testing/utils.go delete mode 100644 internal/testing/utils_test.go delete mode 100644 main.go delete mode 100644 pkg/generator/declaration.go delete mode 100644 pkg/generator/function.go delete mode 100644 pkg/generator/interface.go delete mode 100644 pkg/generator/struct.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dd6b6e6..01c0db9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,10 +26,11 @@ jobs: uses: actions/setup-go@v4 with: go-version: ${{ matrix.go }} - - run: make deps - - run: make lint - - run: make install - - run: make test - - run: make build - - run: make test-examples - if: matrix.os != 'windows-latest' + - name: Download dependencies + run: go mod download + - name: Install golangci-lint + run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.1 + - name: Run golangci-lint + run: golangci-lint run + - name: Run tests + run: go test -v ./... diff --git a/.go-version b/.go-version deleted file mode 100644 index 0fdd235..0000000 --- a/.go-version +++ /dev/null @@ -1 +0,0 @@ -1.20 \ No newline at end of file diff --git a/ADOPTERS.md b/ADOPTERS.md deleted file mode 100644 index e69de29..0000000 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index e69de29..0000000 diff --git a/Makefile b/Makefile deleted file mode 100644 index 5ce6954..0000000 --- a/Makefile +++ /dev/null @@ -1,33 +0,0 @@ -BINARY_NAME=genz -SUPPORTED_OS=linux darwin windows -SUPPORTED_ARCH=amd64 arm64 - -deps: - go mod download - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.1 - -install: - go install . - -lint: - golangci-lint run - -test: - go test -v ./... - -test-coverage: - go test -coverprofile=coverage.out ./... - go tool cover -html=coverage.out - -test-examples: - $(BINARY_NAME) test -directory ./examples/ -v - -build-all: - for os in $(SUPPORTED_OS); do \ - for arch in $(SUPPORTED_ARCH); do \ - GOOS=$$os GOARCH=$$arch go build -o bin/$(BINARY_NAME)-$$os-$$arch; \ - done; \ - done - -build: - go build -o bin/$(BINARY_NAME) \ No newline at end of file diff --git a/cmd/genz/genz.go b/cmd/genz/genz.go deleted file mode 100644 index 5b61323..0000000 --- a/cmd/genz/genz.go +++ /dev/null @@ -1,134 +0,0 @@ -package genz - -import ( - "bytes" - "flag" - "fmt" - "github.com/Joffref/genz/internal/parser" - "github.com/Joffref/genz/pkg/models" - "io" - "log" - "os" - "path/filepath" - "plugin" - "strings" - - "github.com/Joffref/genz/internal/command" - "github.com/Joffref/genz/internal/utils" -) - -type generateCommand struct { -} - -const ( - generateCommandUsage = `Usage of genz: - genz [flags] -type T -generator path/to/my/custom/generator:entrypoint [directory] - genz [flags] -type T -generator path/to/my/custom/generator:entrypoint files... # Must be a single package -Flags:` -) - -var ( - generateCmd = flag.NewFlagSet("", flag.ExitOnError) - typeName = generateCmd.String("type", "", "name of the type to parse") - generator = generateCmd.String("generator", "", "name of the generator to use (e.g: path/to/my/custom/generator:entrypoint)") - output = generateCmd.String("output", "", "output file name; default srcdir/.gen.go") - buildTags = generateCmd.String("tags", "", "comma-separated list of build tags to apply") -) - -func init() { - log.SetFlags(0) - log.SetPrefix("genz: ") - generateCmd.Usage = func() { - fmt.Fprintf(os.Stderr, "%s\n", generateCommandUsage) - generateCmd.PrintDefaults() - } - command.RegisterCommand("generate", generateCommand{}) - command.SetRootCommand(generateCommand{}) -} - -func (c generateCommand) ValidateArgs() error { - if len(*typeName) == 0 { - generateCmd.Usage() - return fmt.Errorf("missing 'type' argument") - } - if len(*generator) == 0 { - generateCmd.Usage() - return fmt.Errorf("missing 'generator' argument") - } - if len(strings.Split(*generator, ":")) != 2 { - generateCmd.Usage() - return fmt.Errorf("invalid 'generator' argument: should be path/to/my/custom/generator:entrypoint") - } - return nil -} - -func (c generateCommand) FlagSet() *flag.FlagSet { - return generateCmd -} - -func (c generateCommand) Run() error { - var tags []string - if len(*buildTags) > 0 { - tags = strings.Split(*buildTags, ",") - } - - pluginPath := strings.Split(*generator, ":")[0] - if _, err := os.Stat(pluginPath); err != nil { - return fmt.Errorf("could not find generator plugin: %s", err) - } - - p, err := plugin.Open(pluginPath) - if err != nil { - return fmt.Errorf("could not open generator plugin: %s", err) - } - - userDefinedGenerator, err := p.Lookup(strings.Split(*generator, ":")[1]) - if err != nil { - return fmt.Errorf("could not find generator entrypoint: %s", err) - } - - userDefinedGeneratorFunc, ok := userDefinedGenerator.(func(writer io.Writer, element models.ParsedElement) error) - if !ok { - return fmt.Errorf("could not find generator entrypoint: %s", err) - } - - // We accept either one directory or a list of files. Which do we have? - args := generateCmd.Args() - if len(args) == 0 { - // Default: process whole package in current directory. - args = []string{"."} - } - - parsedElement, err := parser.Parser(utils.LoadPackage(args, tags), *typeName) - - var buf bytes.Buffer - err = userDefinedGeneratorFunc(&buf, parsedElement) - if err != nil { - return fmt.Errorf("generating code: %s", err) - } - src := utils.Format(buf) - - var dir string - if len(args) == 1 && utils.IsDirectory(args[0]) { - dir = args[0] - } else { - if len(tags) != 0 { - return fmt.Errorf("-tags option applies only to directories, not when files are specified") - } - dir = filepath.Dir(args[0]) - } - - // Write to file. - outputName := *output - if outputName == "" { - baseName := fmt.Sprintf("%s.gen.go", *typeName) - outputName = filepath.Join(dir, strings.ToLower(baseName)) - } - - if err := os.WriteFile(outputName, src, 0644); err != nil { - return fmt.Errorf("writing output: %s", err) - } - - log.Printf("wrote %s (%d bytes)", outputName, len(src)) - return nil -} diff --git a/cmd/genz/root.go b/cmd/genz/root.go deleted file mode 100644 index ead60f0..0000000 --- a/cmd/genz/root.go +++ /dev/null @@ -1,37 +0,0 @@ -//nolint:unused -package genz - -import ( - "github.com/Joffref/genz/internal/command" - "os" -) - -const ( - // Version is the current version of genz - Version = "0.0.1" -) - -func Execute() error { - if len(os.Args) == 1 { - command.RootCommand().FlagSet().Usage() - return nil - } - for name, cmd := range command.Commands() { - if name == os.Args[1] { - if err := cmd.FlagSet().Parse(os.Args[2:]); err != nil { - return err - } - if err := cmd.ValidateArgs(); err != nil { - return err - } - return cmd.Run() - } - } - if err := command.RootCommand().FlagSet().Parse(os.Args[1:]); err != nil { - return err - } - if err := command.RootCommand().ValidateArgs(); err != nil { - return err - } - return command.RootCommand().Run() -} diff --git a/cmd/genz/test.go b/cmd/genz/test.go deleted file mode 100644 index b82a95a..0000000 --- a/cmd/genz/test.go +++ /dev/null @@ -1,46 +0,0 @@ -package genz - -import ( - "flag" - "fmt" - "github.com/Joffref/genz/internal/command" - "github.com/Joffref/genz/internal/testing" - "log" -) - -const ( - testUsage = "run tests" -) - -type testCommand struct { -} - -var ( - test = flag.NewFlagSet("test", flag.ExitOnError) - directory = test.String("directory", ".", "directory where to run the tests") - exitOnErr = test.Bool("exit-on-error", false, "exit on first error") - verbose = test.Bool("verbose", false, "verbose mode") -) - -func (t testCommand) FlagSet() *flag.FlagSet { - return test -} - -func (t testCommand) Run() error { - if err := testing.RunTests(*directory, *verbose, *exitOnErr); err != nil { - return fmt.Errorf("test(s) exited with error(s)") - } - return nil -} - -func (t testCommand) ValidateArgs() error { - return nil -} - -func init() { - test.BoolVar(verbose, "v", false, "verbose mode") // alias - test.Usage = func() { - log.Printf("%s\n", testUsage) - } - command.RegisterCommand("test", testCommand{}) -} diff --git a/cmd/genz/version.go b/cmd/genz/version.go deleted file mode 100644 index 0e7983e..0000000 --- a/cmd/genz/version.go +++ /dev/null @@ -1,38 +0,0 @@ -package genz - -import ( - "flag" - "github.com/Joffref/genz/internal/command" - "log" -) - -const ( - versionUsage = "print the version" -) - -type versionCommand struct { -} - -var ( - version = flag.NewFlagSet("version", flag.ExitOnError) -) - -func init() { - version.Usage = func() { - log.Printf("%s\n", versionUsage) - } - command.RegisterCommand("version", versionCommand{}) -} - -func (v versionCommand) FlagSet() *flag.FlagSet { - return version -} - -func (v versionCommand) Run() error { - log.Printf("genz version %s\n", Version) - return nil -} - -func (v versionCommand) ValidateArgs() error { - return nil -} diff --git a/examples/1_validator/main.go b/examples/1_validator/main.go index 176f6b2..3e6b426 100644 --- a/examples/1_validator/main.go +++ b/examples/1_validator/main.go @@ -1,27 +1,31 @@ -//go:generate go build -buildmode=plugin -o main.so main.go - package main import ( "bytes" - "github.com/Joffref/genz/pkg/generator" - "github.com/Joffref/genz/pkg/models" + genz2 "github.com/Joffref/genz/genz" + "github.com/Joffref/genz/genz/cli" + "github.com/Joffref/genz/genz/models" "io" ) +func main() { + err := cli.NewCommandFromGenerator("validator", MyCustomGenerator).Execute() + if err != nil { + return + } +} + func MyCustomGenerator(file io.Writer, element models.ParsedElement) error { - err := generator. - NewCode(file, element.PackageName). + code := genz2.NewCode(file, element.PackageName). WithHeaderComments("File generated by GenZ with template validator"). WithImports("fmt", "unicode"). WithDeclarations( - generator. - Function("Validate"). + genz2.Function("Validate"). WithReceiver("v", element.Type.InternalName, false). WithReturns("error"). WithBody(body(element.Element)), - ). - Generate() + ) + err := code.Generate() if err != nil { return err } diff --git a/examples/1_validator/main.tmpl b/examples/1_validator/main.tmpl deleted file mode 100644 index af62d31..0000000 --- a/examples/1_validator/main.tmpl +++ /dev/null @@ -1,33 +0,0 @@ -// File generated by GenZ with template validator -package test - -import ( - "fmt" - "unicode" -) - -func (v {{ .Type.InternalName }}) Validate() error { -{{ range .Attributes }} {{$attribute := .}} - {{ if eq .Type.InternalName "string" }} - {{ range .Comments }} - {{ if eq . "+required" }} - if v.{{ $attribute.Name }} == "" { - return fmt.Errorf("attribute '{{ $attribute.Name }}' must be set") - } - {{ end }} - {{ if eq . "+startsWithCapital" }} - if v.{{ $attribute.Name }} != "" && !unicode.IsUpper(rune(v.{{ $attribute.Name }}[0])) { - return fmt.Errorf("attribute '{{ $attribute.Name }}' should start with a capital letter") - } - {{ end }} - {{ end }} - {{ end }} - {{ if eq .Type.InternalName "int" "uint" "uint8" "uint32" "float32" "float64" }} - // WORK IN PROGRESS - {{ range .Comments }} - {{ $comparators := split "," . }} - {{ end }} - {{ end }} -{{ end }} - return nil -} diff --git a/examples/1_validator/main_test.go b/examples/1_validator/main_test.go new file mode 100644 index 0000000..9fa38ec --- /dev/null +++ b/examples/1_validator/main_test.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/Joffref/genz/genz/testutils" + "os" + "testing" +) + +func TestMyCustomGenerator(t *testing.T) { + + typeFile, err := os.ReadFile("./test/human.go") + + parsedElement := testutils.ParseElement(t, string(typeFile), "Human") + + expected, err := os.ReadFile("./test/expected.go") + if err != nil { + t.Error(err) + } + + testutils.IsExpected(t, string(expected), MyCustomGenerator, parsedElement) +} diff --git a/examples/1_validator/test/human.go b/examples/1_validator/test/human.go index e1baf2d..5c2f5dc 100644 --- a/examples/1_validator/test/human.go +++ b/examples/1_validator/test/human.go @@ -1,6 +1,6 @@ package test -//go:generate genz -type Human -generator ../main.so:MyCustomGenerator -output human_validator.gen.go +//go:generate go run github.com/Joffref/genz/examples/1_validator -type Human -output human.gen.go type Human struct { //+startsWithCapital Firstname string diff --git a/examples/2_getters/getters.tmpl b/examples/2_getters/getters.tmpl deleted file mode 100644 index 43bd5de..0000000 --- a/examples/2_getters/getters.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -package test - -{{ range .Attributes }} - {{ if has "+getter" .Comments }}{{ $receiverName := substr 0 1 $.Type.InternalName | lower}} -func ({{ $receiverName }} *{{ $.Type.InternalName }}) Get{{ camelcase .Name }}() {{ .Type.InternalName }} { - return {{ $receiverName }}.{{.Name}} -} - {{ end }} -{{ end }} diff --git a/examples/2_getters/main.go b/examples/2_getters/main.go new file mode 100644 index 0000000..b9228b9 --- /dev/null +++ b/examples/2_getters/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.com/Joffref/genz/genz" + "github.com/Joffref/genz/genz/cli" + "github.com/Joffref/genz/genz/models" + "io" + "slices" +) + +func main() { + cli.NewCommandFromGenerator("getters", MyCustomGenerator).Execute() +} + +func MyCustomGenerator(w io.Writer, parsedElement models.ParsedElement) error { + code := genz.NewCode(w, parsedElement.PackageName) + + for _, attribute := range parsedElement.Attributes { + if slices.Contains(attribute.Comments, "+getter") { + code.WithDeclarations( + genz.Function("Get"+attribute.Name). + WithReceiver("v", parsedElement.Type.InternalName, false). + WithReturns(attribute.Type.InternalName). + WithBody("return v." + attribute.Name), + ) + } + } + return code.Generate() +} diff --git a/examples/2_getters/main_test.go b/examples/2_getters/main_test.go new file mode 100644 index 0000000..064f49a --- /dev/null +++ b/examples/2_getters/main_test.go @@ -0,0 +1,20 @@ +package main + +import ( + "github.com/Joffref/genz/genz/testutils" + "os" + "testing" +) + +func TestMyCustomGenerator(t *testing.T) { + typeFile, err := os.ReadFile("./test/car.go") + + parsedElement := testutils.ParseElement(t, string(typeFile), "Car") + + expected, err := os.ReadFile("./test/expected.go") + if err != nil { + t.Error(err) + } + + testutils.IsExpected(t, string(expected), MyCustomGenerator, parsedElement) +} diff --git a/examples/2_getters/test/car.go b/examples/2_getters/test/car.go index f4ac14b..b7f5b1c 100644 --- a/examples/2_getters/test/car.go +++ b/examples/2_getters/test/car.go @@ -1,15 +1,15 @@ package test -//go:generate genz -type Car -template ../getters.tmpl -output car.gen.go +//go:generate go run github.com/Joffref/genz/examples/2_getters -type Car -output car.gen.go type Car struct { //+getter - model string + Model string //+getter - wheels []Wheel + Wheels []Wheel - motorReference string + MotorReference string } type Wheel struct{} diff --git a/examples/2_getters/test/expected.go b/examples/2_getters/test/expected.go index 5807fc1..fc354ec 100644 --- a/examples/2_getters/test/expected.go +++ b/examples/2_getters/test/expected.go @@ -1,9 +1,9 @@ package test -func (c *Car) GetModel() string { - return c.model +func (v Car) GetModel() string { + return v.Model } -func (c *Car) GetWheels() []Wheel { - return c.wheels +func (v Car) GetWheels() []Wheel { + return v.Wheels } diff --git a/examples/3_mock/main.go b/examples/3_mock/main.go new file mode 100644 index 0000000..149a144 --- /dev/null +++ b/examples/3_mock/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "bytes" + "github.com/Joffref/genz/genz" + "github.com/Joffref/genz/genz/cli" + "github.com/Joffref/genz/genz/models" + "io" + "text/template" +) + +func main() { + if err := cli.NewCommandFromGenerator("mock-gen", MyCustomGenerator).Execute(); err != nil { + panic(err) + } +} + +func MyCustomGenerator(buf io.Writer, parsedElement models.ParsedElement) error { + // TODO: implement + + code := genz.NewCode(buf, parsedElement.PackageName). + WithImports(parsedElement.PackageImports...) + + mockStruct := genz.Struct("Mock" + parsedElement.Type.InternalName) + var attributes map[string]string + for _, method := range parsedElement.Methods { + attributeName := method.Name + "Func" + tmpl := "func({{ range $index, $element := .Params }} param{{$index}} {{ .Name }}{{ end }}) {{ range .Returns }}{{ .InternalName }}{{ end }}" + parse, err := template.New("test").Parse(tmpl) + if err != nil { + return err + } + buf := &bytes.Buffer{} + if err := parse.Execute(buf, method); err != nil { + return err + } + attributes[attributeName] = buf.String() + } + panic("not implemented") + + return code.WithDeclarations(mockStruct).Generate() +} diff --git a/examples/4_mock/mock.tmpl b/examples/3_mock/mock.tmpl similarity index 100% rename from examples/4_mock/mock.tmpl rename to examples/3_mock/mock.tmpl diff --git a/examples/4_mock/test/expected.go b/examples/3_mock/test/expected.go similarity index 100% rename from examples/4_mock/test/expected.go rename to examples/3_mock/test/expected.go diff --git a/examples/4_mock/test/hello.go b/examples/3_mock/test/hello.go similarity index 100% rename from examples/4_mock/test/hello.go rename to examples/3_mock/test/hello.go diff --git a/examples/4_mock/test/hello_test.go b/examples/3_mock/test/hello_test.go similarity index 100% rename from examples/4_mock/test/hello_test.go rename to examples/3_mock/test/hello_test.go diff --git a/examples/3_tests/getters.tmpl b/examples/3_tests/getters.tmpl deleted file mode 100644 index da822f9..0000000 --- a/examples/3_tests/getters.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -package main - -{{ range .Attributes }} - {{ if has "+getter" .Comments }}{{ $receiverName := substr 0 1 $.Type.InternalName | lower}} -func ({{ $receiverName }} *{{ $.Type.InternalName }}) Get{{ camelcase .Name }}() {{ .Type.InternalName }} { - return {{ $receiverName }}.{{.Name}} -} - {{ end }} -{{ end }} diff --git a/examples/3_tests/tests/0_no_getter/car.go b/examples/3_tests/tests/0_no_getter/car.go deleted file mode 100644 index b00aefc..0000000 --- a/examples/3_tests/tests/0_no_getter/car.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -//go:generate genz -type Car -template ../../getters.tmpl -output car.gen.go -type Car struct { - model string -} diff --git a/examples/3_tests/tests/0_no_getter/expected.go b/examples/3_tests/tests/0_no_getter/expected.go deleted file mode 100644 index 06ab7d0..0000000 --- a/examples/3_tests/tests/0_no_getter/expected.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/examples/3_tests/tests/1_1_getter/car.go b/examples/3_tests/tests/1_1_getter/car.go deleted file mode 100644 index 7fd070c..0000000 --- a/examples/3_tests/tests/1_1_getter/car.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -//go:generate genz -type Car -template ../../getters.tmpl -output car.gen.go -type Car struct { - //+getter - model string -} diff --git a/examples/3_tests/tests/1_1_getter/car_test.go b/examples/3_tests/tests/1_1_getter/car_test.go deleted file mode 100644 index a40bb54..0000000 --- a/examples/3_tests/tests/1_1_getter/car_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import "testing" - -func TestCarGetModel(t *testing.T) { - c := &Car{} - if c.GetModel() != c.model { - t.Errorf("Expected %s, got %s", c.model, c.GetModel()) - } - c.model = "Ford" - if c.GetModel() != "Ford" { - t.Errorf("Expected %s, got %s", c.model, c.GetModel()) - } - c.model = "Ferrari" - if c.GetModel() != "Ferrari" { - t.Errorf("Expected %s, got %s", c.model, c.GetModel()) - } -} diff --git a/examples/3_tests/tests/1_1_getter/expected.go b/examples/3_tests/tests/1_1_getter/expected.go deleted file mode 100644 index 5cc88f9..0000000 --- a/examples/3_tests/tests/1_1_getter/expected.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func (c *Car) GetModel() string { - return c.model -} diff --git a/genz/cli/command.go b/genz/cli/command.go new file mode 100644 index 0000000..b5a00c8 --- /dev/null +++ b/genz/cli/command.go @@ -0,0 +1,101 @@ +package cli + +import ( + "bytes" + "flag" + "fmt" + "github.com/Joffref/genz/genz" + "os" +) + +type Command interface { + FlagSet() *flag.FlagSet + Execute() error + GetConfig() ([]byte, error) + ValidateArgs() error +} + +var ( + defaultCmd = flag.NewFlagSet("", flag.ExitOnError) + typeName = defaultCmd.String("type", "", "name of the type to parse") + output = defaultCmd.String("output", "", "output file name; default srcdir/.gen.go") + config = defaultCmd.String("config", "", "path to the config file, if any") +) + +type commandFromGenerator struct { + generator genz.Generator + name string +} + +func NewCommandFromGenerator(name string, generator genz.Generator) Command { + parseFlags() + return commandFromGenerator{ + generator: generator, + name: name, + } +} + +func (c commandFromGenerator) FlagSet() *flag.FlagSet { + return defaultCmd +} + +func (c commandFromGenerator) GetConfig() ([]byte, error) { + file, err := os.ReadFile(*config) + if err != nil { + return nil, err + } + return file, nil +} + +func (c commandFromGenerator) Execute() error { + if err := c.ValidateArgs(); err != nil { + return err + } + if len(*output) == 0 { + *output = fmt.Sprintf("%s.gen.go", *typeName) + } + + // We accept either one directory or a list of files. Which do we have? + args := defaultCmd.Args() + if len(args) == 0 { + // Default: process whole package in current directory. + args = []string{"."} + } + + parsedElement, err := genz.Parse(*typeName, args, *output) + if err != nil { + return err + } + + var buf bytes.Buffer + if err = c.generator(&buf, parsedElement); err != nil { + return err + } + + file, err := os.OpenFile(*output, os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + + if _, err = file.Write(buf.Bytes()); err != nil { + return err + } + + return nil +} + +func (c commandFromGenerator) ValidateArgs() error { + if len(*typeName) == 0 { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", c.name) + defaultCmd.PrintDefaults() + return fmt.Errorf("missing 'type' argument") + } + return nil +} + +func parseFlags() { + if err := defaultCmd.Parse(os.Args[1:]); err != nil { + fmt.Println(err) + os.Exit(2) + } +} diff --git a/pkg/generator/code.go b/genz/code.go similarity index 99% rename from pkg/generator/code.go rename to genz/code.go index ce4e0b5..03d8148 100644 --- a/pkg/generator/code.go +++ b/genz/code.go @@ -1,4 +1,4 @@ -package generator +package genz import ( "bytes" diff --git a/genz/declaration.go b/genz/declaration.go new file mode 100644 index 0000000..64650c7 --- /dev/null +++ b/genz/declaration.go @@ -0,0 +1,240 @@ +package genz + +import ( + "bytes" + "fmt" +) + +type Declaration interface { + Generate() string +} + +type FuncDecl struct { + name string + inputs map[string]string + returns []string + receiverName string + receiverType string + isPtrReceiver bool + isSignature bool + body string +} + +func Function(name string) *FuncDecl { + return &FuncDecl{ + name: name, + } +} + +func (f *FuncDecl) WithReceiver(receiverName string, receiverType string, isPtrReceiver bool) *FuncDecl { + f.isPtrReceiver = isPtrReceiver + f.receiverType = receiverType + f.receiverName = receiverName + return f +} + +func (f *FuncDecl) WithInputs(inputs map[string]string) *FuncDecl { + if f.inputs == nil { + f.inputs = make(map[string]string) + } + for inputName, inputType := range inputs { + f.inputs[inputName] = inputType + } + return f +} + +func (f *FuncDecl) WithReturns(returns ...string) *FuncDecl { + f.returns = returns + return f +} + +func (f *FuncDecl) WithBody(body string) *FuncDecl { + f.body = body + return f +} + +func (f *FuncDecl) SignatureOnly() *FuncDecl { + f.body = "" + f.isSignature = true + return f +} + +func (f *FuncDecl) Generate() string { + var buf bytes.Buffer + buf.WriteString("func ") + if f.receiverName != "" { + buf.WriteString("(") + buf.WriteString(f.receiverName) + if f.isPtrReceiver { + buf.WriteString("*") + } + buf.WriteString(" ") + buf.WriteString(f.receiverType) + buf.WriteString(")") + } + buf.WriteString(" ") + buf.WriteString(f.name) + buf.WriteString("(") + for inputName, inputType := range f.inputs { + buf.WriteString(inputName) + buf.WriteString(" ") + buf.WriteString(inputType) + buf.WriteString(", ") + } + buf.WriteString(") ") + if len(f.returns) > 0 { + buf.WriteString("(") + for _, returnType := range f.returns { + buf.WriteString(returnType) + buf.WriteString(", ") + } + buf.WriteString(")") + } + if f.isSignature { + buf.WriteString("\n") + } else { + buf.WriteString(" {") + if f.body != "" { + buf.WriteString("\n") + buf.WriteString(f.body) + buf.WriteString("\n") + } + buf.WriteString("}") + } + return buf.String() +} + +type InterfaceDecl struct { + name string + comments []string + methods map[string]struct { + methodName string + methodDecl *FuncDecl + } +} + +func Interface(name string) *InterfaceDecl { + return &InterfaceDecl{ + name: name, + methods: make(map[string]struct { + methodName string + methodDecl *FuncDecl + }), + } +} + +func (i *InterfaceDecl) WithComments(comments ...string) *InterfaceDecl { + i.comments = append(i.comments, comments...) + return i +} + +func (i *InterfaceDecl) WithMethod(methodName string, methodDecl *FuncDecl) *InterfaceDecl { + methodDecl.SignatureOnly() + i.methods[methodName] = struct { + methodName string + methodDecl *FuncDecl + }{methodName: methodName, methodDecl: methodDecl} + return i +} + +func (i *InterfaceDecl) Generate() string { + var buf bytes.Buffer + if len(i.comments) > 0 { + for _, comment := range i.comments { + buf.WriteString(fmt.Sprintf("// %s\n", comment)) + } + } + buf.WriteString(fmt.Sprintf("type %s interface {\n", i.name)) + for _, method := range i.methods { + buf.WriteString(fmt.Sprintf("%s\n", method.methodDecl.Generate())) + } + buf.WriteString("}\n") + return buf.String() +} + +type StructDecl struct { + name string + fields map[string]struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + } +} + +func Struct(name string) *StructDecl { + return &StructDecl{ + name: name, + fields: make(map[string]struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }), + } +} + +func (s *StructDecl) WithField(fieldName string, fieldType string) *StructDecl { + s.fields[fieldName] = struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }{fieldName: fieldName, fieldType: fieldType} + return s +} + +func (s *StructDecl) WithFieldInlineComment(fieldName string, fieldType string, inlineComment string) *StructDecl { + s.fields[fieldName] = struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }{fieldName: fieldName, fieldType: fieldType, inlineComment: inlineComment} + return s +} + +func (s *StructDecl) WithFieldComments(fieldName string, fieldType string, comments []string) *StructDecl { + s.fields[fieldName] = struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }{fieldName: fieldName, fieldType: fieldType, comments: comments} + return s +} + +func (s *StructDecl) WithFieldTags(fieldName string, fieldType string, tags map[string]string) *StructDecl { + s.fields[fieldName] = struct { + fieldName string + fieldType string + inlineComment string + comments []string + tags map[string]string + }{fieldName: fieldName, fieldType: fieldType, tags: tags} + return s +} + +func (s *StructDecl) Generate() string { + var buf bytes.Buffer + buf.WriteString(fmt.Sprintf("type %s struct {\n", s.name)) + for _, field := range s.fields { + if field.inlineComment != "" { + buf.WriteString(fmt.Sprintf("\t%s %s // %s\n", field.fieldName, field.fieldType, field.inlineComment)) + } else if len(field.comments) > 0 { + buf.WriteString(fmt.Sprintf("\t%s %s // %s\n", field.fieldName, field.fieldType, field.comments[0])) + for _, comment := range field.comments[1:] { + buf.WriteString(fmt.Sprintf("\t// %s\n", comment)) + } + } else { + buf.WriteString(fmt.Sprintf("\t%s %s\n", field.fieldName, field.fieldType)) + } + } + buf.WriteString("}\n") + return buf.String() +} diff --git a/genz/generator.go b/genz/generator.go new file mode 100644 index 0000000..3205244 --- /dev/null +++ b/genz/generator.go @@ -0,0 +1,10 @@ +package genz + +import ( + "github.com/Joffref/genz/genz/models" + "io" +) + +// Generator is a function that generates code from a parsed element. +// Generator is user-defined and can be used to generate any Go code. +type Generator func(buffer io.Writer, parsedElement models.ParsedElement) error diff --git a/pkg/models/types.go b/genz/models/element.go similarity index 100% rename from pkg/models/types.go rename to genz/models/element.go diff --git a/pkg/parser/parser.go b/genz/parser.go similarity index 76% rename from pkg/parser/parser.go rename to genz/parser.go index 207c164..e4e5953 100644 --- a/pkg/parser/parser.go +++ b/genz/parser.go @@ -1,20 +1,19 @@ -package parser +package genz import ( "fmt" + "github.com/Joffref/genz/genz/models" "github.com/Joffref/genz/internal/parser" "github.com/Joffref/genz/internal/utils" - "github.com/Joffref/genz/pkg/models" ) // Parse parses a type from a package -// Note: patterns is a list of patterns to match packages +// Note: patterns is a list of patterns to match packages (e.g . ; ./... ; github.com/Joffref/genz/...) func Parse(typeName string, patterns []string, buildTags ...string) (models.ParsedElement, error) { pkg := utils.LoadPackage(patterns, buildTags) if pkg == nil { return models.ParsedElement{}, fmt.Errorf("could not load package") } - fmt.Println("pkg:", pkg.PkgPath) element, err := parser.Parser(pkg, typeName) if err != nil { return models.ParsedElement{}, err diff --git a/genz/testutils/helper.go b/genz/testutils/helper.go new file mode 100644 index 0000000..68a1e82 --- /dev/null +++ b/genz/testutils/helper.go @@ -0,0 +1,34 @@ +package testutils + +import ( + "bytes" + "github.com/Joffref/genz/genz" + "github.com/Joffref/genz/genz/models" + "github.com/Joffref/genz/internal/parser" + "github.com/Joffref/genz/internal/testutils" + "testing" +) + +// ParseElement parses the given input and returns the parsed element +// If it fails, it fails the test +func ParseElement(t *testing.T, input string, typeName string) models.ParsedElement { + p := testutils.CreatePkgWithCode(t, input) + element, err := parser.Parser(p, typeName) + if err != nil { + t.Error(err) + } + return element +} + +// IsExpected checks that the generator generates the expected code +// If not, it fails the test +func IsExpected(t *testing.T, expect string, generator genz.Generator, parsedElement models.ParsedElement) { + var buffer bytes.Buffer + err := generator(&buffer, parsedElement) + if err != nil { + t.Error(err) + } + if buffer.String() != expect { + t.Errorf("expected %s, got %s", expect, buffer.String()) + } +} diff --git a/internal/command/command.go b/internal/command/command.go deleted file mode 100644 index b13c8b2..0000000 --- a/internal/command/command.go +++ /dev/null @@ -1,30 +0,0 @@ -package command - -import "flag" - -type Command interface { - FlagSet() *flag.FlagSet - Run() error - ValidateArgs() error -} - -var ( - rootCommand Command // root command - commands = map[string]Command{} // available commands name -> Command -) - -func RegisterCommand(name string, cmd Command) { - commands[name] = cmd -} - -func SetRootCommand(cmd Command) { - rootCommand = cmd -} - -func Commands() map[string]Command { - return commands -} - -func RootCommand() Command { - return rootCommand -} diff --git a/internal/command/command_test.go b/internal/command/command_test.go deleted file mode 100644 index 2526186..0000000 --- a/internal/command/command_test.go +++ /dev/null @@ -1,59 +0,0 @@ -package command_test - -import ( - "flag" - "reflect" - "testing" - - "github.com/Joffref/genz/internal/command" -) - -type testCmd struct{} - -func (t testCmd) FlagSet() *flag.FlagSet { panic("not implemented") } -func (t testCmd) Run() error { panic("not implemented") } -func (t testCmd) ValidateArgs() error { panic("not implemented") } - -func TestRegisterCommandSuccess(t *testing.T) { - expected := map[string]command.Command{} - if !reflect.DeepEqual(command.Commands(), expected) { - t.Fatalf("Commands doesnt match expected") - } - - command.RegisterCommand("foo", &testCmd{}) - expected = map[string]command.Command{ - "foo": &testCmd{}, - } - if !reflect.DeepEqual(command.Commands(), expected) { - t.Fatalf("Commands doesnt match expected") - } - - command.RegisterCommand("bar", command.RootCommand()) - expected = map[string]command.Command{ - "foo": &testCmd{}, - "bar": command.RootCommand(), - } - if !reflect.DeepEqual(command.Commands(), expected) { - t.Fatalf("Commands doesnt match expected") - } - - command.RegisterCommand("bar", &testCmd{}) - expected = map[string]command.Command{ - "foo": &testCmd{}, - "bar": &testCmd{}, - } - if !reflect.DeepEqual(command.Commands(), expected) { - t.Fatalf("Commands doesnt match expected") - } -} - -func TestRootCommandSuccess(t *testing.T) { - if command.RootCommand() != nil { - t.Fatalf("expected nil, got %s", command.RootCommand()) - } - - command.SetRootCommand(testCmd{}) - if !reflect.DeepEqual(command.RootCommand(), testCmd{}) { - t.Fatalf("expected testCmd, got %s", command.RootCommand()) - } -} diff --git a/internal/generator/generator.go b/internal/generator/generator.go deleted file mode 100644 index ebae861..0000000 --- a/internal/generator/generator.go +++ /dev/null @@ -1,42 +0,0 @@ -//nolint:unused -package generator - -import ( - "bytes" - "fmt" - "github.com/Joffref/genz/pkg/models" - "html/template" - "log" - - "github.com/Masterminds/sprig/v3" - "golang.org/x/tools/go/packages" -) - -type parseFunc func(pkg *packages.Package, typeName string) (models.ParsedElement, error) - -func Generate( - pkg *packages.Package, - templateContent string, - typeName string, - parse parseFunc, -) (bytes.Buffer, error) { - log.Printf("generating template for type %s", typeName) - - parsedElement, err := parse(pkg, typeName) - if err != nil { - return bytes.Buffer{}, fmt.Errorf("failed to inspect package: %v", err) - } - - tmpl, err := template.New("template").Funcs(sprig.FuncMap()).Parse(templateContent) - if err != nil { - return bytes.Buffer{}, fmt.Errorf("failed to parse template: %v", err) - } - buf := bytes.Buffer{} - err = tmpl.Execute(&buf, parsedElement) - if err != nil { - return bytes.Buffer{}, fmt.Errorf("failed to execute template: %v", err) - } - - log.Printf("generated buffer (%d bytes)", buf.Len()) - return buf, nil -} diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go deleted file mode 100644 index aa5ffb5..0000000 --- a/internal/generator/generator_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package generator_test - -import ( - "errors" - "github.com/Joffref/genz/pkg/models" - "strings" - "testing" - - "github.com/Joffref/genz/internal/generator" - "golang.org/x/tools/go/packages" -) - -func TestGenerateErrorParse(t *testing.T) { - parseFunc := func(pkg *packages.Package, structName string) (models.ParsedElement, error) { - return models.ParsedElement{}, errors.New("failed to parse package") - } - _, err := generator.Generate(nil, "template", "typeName", parseFunc) - if err == nil { - t.Fatal("expected error, got nil") - } - if err.Error() != "failed to inspect package: failed to parse package" { - t.Fatalf("error does not match expected: %v", err) - } -} - -func TestGenerateErrorTemplateParse(t *testing.T) { - parseFunc := func(pkg *packages.Package, structName string) (models.ParsedElement, error) { - return models.ParsedElement{}, nil - } - _, err := generator.Generate(nil, "{{", "typeName", parseFunc) - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), "failed to parse template") { - t.Fatalf("expected string not found in error: %v", err) - } -} - -func TestGenerateErrorTemplateExecute(t *testing.T) { - parseFunc := func(pkg *packages.Package, structName string) (models.ParsedElement, error) { - return models.ParsedElement{}, nil - } - _, err := generator.Generate(nil, "{{.Foo}}", "typeName", parseFunc) - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), "failed to execute template") { - t.Fatalf("expected string not found in error: %v", err) - } -} - -func TestGenerateSuccess(t *testing.T) { - parseFunc := func(pkg *packages.Package, structName string) (models.ParsedElement, error) { - return models.ParsedElement{ - Element: models.Element{ - Type: models.Type{ - Name: "TypeName", - }, - }, - }, nil - } - buf, err := generator.Generate(nil, "{{.Type.Name}}", "TypeName", parseFunc) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if buf.String() != "TypeName" { - t.Fatalf("expected TypeName, got %s", buf.String()) - } -} diff --git a/internal/parser/helper.go b/internal/parser/helper.go index c0c7699..ff23427 100644 --- a/internal/parser/helper.go +++ b/internal/parser/helper.go @@ -2,7 +2,7 @@ package parser import ( "fmt" - "github.com/Joffref/genz/pkg/models" + "github.com/Joffref/genz/genz/models" "go/ast" "go/types" "golang.org/x/tools/go/packages" diff --git a/internal/parser/interface.go b/internal/parser/interface.go index eb440cd..f5696e0 100644 --- a/internal/parser/interface.go +++ b/internal/parser/interface.go @@ -1,10 +1,10 @@ package parser import ( + "github.com/Joffref/genz/genz/models" "go/ast" "go/types" - "github.com/Joffref/genz/pkg/models" "golang.org/x/tools/go/packages" ) diff --git a/internal/parser/interface_test.go b/internal/parser/interface_test.go index 8b4dc70..4f80d05 100644 --- a/internal/parser/interface_test.go +++ b/internal/parser/interface_test.go @@ -1,12 +1,12 @@ package parser import ( + "github.com/Joffref/genz/genz/models" "go/ast" "reflect" "testing" "github.com/Joffref/genz/internal/testutils" - "github.com/Joffref/genz/pkg/models" "github.com/google/go-cmp/cmp" ) diff --git a/internal/parser/methods.go b/internal/parser/methods.go index b5f1a8e..5c05771 100644 --- a/internal/parser/methods.go +++ b/internal/parser/methods.go @@ -1,12 +1,11 @@ package parser import ( + "github.com/Joffref/genz/genz/models" "go/ast" "go/doc" "go/types" "strings" - - "github.com/Joffref/genz/pkg/models" ) func parseMethod(name string, signature *types.Signature) (models.Method, error) { diff --git a/internal/parser/package.go b/internal/parser/package.go index 3e8d6c9..04844da 100644 --- a/internal/parser/package.go +++ b/internal/parser/package.go @@ -1,7 +1,7 @@ package parser import ( - "github.com/Joffref/genz/pkg/models" + "github.com/Joffref/genz/genz/models" "golang.org/x/tools/go/packages" ) diff --git a/internal/parser/package_test.go b/internal/parser/package_test.go index e4ec17a..d603893 100644 --- a/internal/parser/package_test.go +++ b/internal/parser/package_test.go @@ -1,8 +1,8 @@ package parser import ( + "github.com/Joffref/genz/genz/models" "github.com/Joffref/genz/internal/testutils" - "github.com/Joffref/genz/pkg/models" "sort" "testing" ) diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 4cad738..cdf2221 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -2,9 +2,9 @@ package parser import ( "fmt" + "github.com/Joffref/genz/genz/models" "go/ast" - "github.com/Joffref/genz/pkg/models" "golang.org/x/tools/go/packages" ) diff --git a/internal/parser/struct.go b/internal/parser/struct.go index d1d7606..d1793d2 100644 --- a/internal/parser/struct.go +++ b/internal/parser/struct.go @@ -2,13 +2,12 @@ package parser import ( "fmt" + "github.com/Joffref/genz/genz/models" "go/ast" "go/doc" "go/types" "strings" - "github.com/Joffref/genz/pkg/models" - "golang.org/x/tools/go/packages" ) diff --git a/internal/parser/struct_test.go b/internal/parser/struct_test.go index f4e618d..af7d79f 100644 --- a/internal/parser/struct_test.go +++ b/internal/parser/struct_test.go @@ -1,13 +1,12 @@ package parser import ( + "github.com/Joffref/genz/genz/models" "go/ast" "reflect" "testing" "github.com/Joffref/genz/internal/testutils" - "github.com/Joffref/genz/pkg/models" - "github.com/google/go-cmp/cmp" ) diff --git a/internal/testing/test.go b/internal/testing/test.go deleted file mode 100644 index 1316138..0000000 --- a/internal/testing/test.go +++ /dev/null @@ -1,100 +0,0 @@ -package testing - -import ( - "errors" - "fmt" - "github.com/Joffref/genz/internal/utils" - "log" - "os" - "path" - "strings" -) - -func RunTests(directory string, verbose, exitOnError bool) error { - entries, err := os.ReadDir(directory) - if err != nil { - return fmt.Errorf("failed to read directory %s: %w", directory, err) - } - var subDirectories []string - for _, f := range entries { - if f.IsDir() { - subDirectories = append(subDirectories, f.Name()) - continue - } - if f.Name() == "expected.go" { - log.Printf("=== ☶ - Running test in %s ===\n", directory) - if err := runTest(directory, verbose); err != nil { - log.Print(err.Error()) - log.Printf("=== ✘ - End of test in %s with error ===\n\n\n", directory) - return err - } - log.Printf("=== ✔ - End of test in %s without error ===\n\n\n", directory) - } - } - var errs error - for _, name := range subDirectories { - if err := RunTests(path.Join(directory, name), verbose, exitOnError); err != nil { - if exitOnError { - return err - } - errs = errors.Join(errs, err) - continue - } - } - return errs -} - -func runTest(directory string, verbose bool) error { - fmt.Println("running test in", directory) - if err := utils.RunCommand([]string{"go", "generate", fmt.Sprintf("./%s", directory)}, verbose); err != nil { - return err - } - expected, err := os.ReadFile(path.Join(directory, "expected.go")) - if err != nil { - return fmt.Errorf("failed to read expected.go file: %w", err) - } - dir, err := os.ReadDir(directory) - if err != nil { - return err - } - if len(dir) == 0 { - return fmt.Errorf("no file in directory %s", directory) - } - var generatedFiles []string - for _, f := range dir { - if strings.HasSuffix(f.Name(), ".gen.go") { - generatedFiles = append(generatedFiles, path.Join(directory, f.Name())) - } - } - if len(generatedFiles) == 0 { - return fmt.Errorf("no generated file in directory %s\nSpecify output file with -output flag in genz command", directory) - } - if len(generatedFiles) > 1 { - var errs error - if err := removeFiles(generatedFiles); err != nil { - errs = errors.Join(errs, err) - } - errs = errors.Join(errs, fmt.Errorf("too many generated files in directory %s\nAt the moment, GenZ only supports one generated file per directory for test ", directory)) - return errs - } - actual, err := os.ReadFile(generatedFiles[0]) - if err != nil { - return fmt.Errorf("failed to read generated file: %w", err) - } - if err := removeFiles(generatedFiles); err != nil { - return err - } - if err := assertOutputIsEqual("expected.go", generatedFiles[0], expected, actual, verbose); err != nil { - return err - } - if verbose { - log.Printf("Running go test in %s\n", directory) - } - if err := utils.RunCommand([]string{"go", "test", "-v", fmt.Sprintf("./%s", directory)}, verbose); err != nil { - return err - } - if verbose { - log.Printf("All tests passed in %s\n", directory) - } - return nil -} diff --git a/internal/testing/test_test.go b/internal/testing/test_test.go deleted file mode 100644 index 6469aed..0000000 --- a/internal/testing/test_test.go +++ /dev/null @@ -1,82 +0,0 @@ -package testing - -import ( - "runtime" - "testing" -) - -func TestRunTests(t *testing.T) { - if runtime.GOOS == "windows" { // TODO: Fix this test on windows - t.Skip("Skipping test on windows") - } - tests := map[string]struct { - directory string - wantErr bool - }{ - "Non existing directory should return an error": { - directory: "", - wantErr: true, - }, - "Directory with no expected.go file should not return an error": { - directory: "./testdata/empty_directory", - wantErr: false, - }, - "Directory with expected.go file should not return an error": { - directory: "./testdata/accurate_expected", - wantErr: false, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - if err := RunTests(tc.directory, false, false); (err != nil) != tc.wantErr { - t.Errorf("RunTests() error = %v, wantErr %v", err, tc.wantErr) - } - }) - } -} - -func Test_runTest(t *testing.T) { - if runtime.GOOS == "windows" { // TODO: fix this test on windows - t.Skip("Skipping test on windows") - } - tests := map[string]struct { - directory string - wantErr bool - }{ - "Non existing directory should return an error": { - directory: "", - wantErr: true, - }, - "Directory with no expected.go file should return an error": { - directory: "./testdata/empty_directory", - wantErr: true, - }, - "Directory with expected.go and identical generated file should not return an error": { - directory: "./testdata/accurate_expected", - wantErr: false, - }, - "Directory with two generated files should return an error": { - directory: "./testdata/two_generated_files", - wantErr: true, - }, - "Directory with difference between expected.go and generated file should return an error": { - directory: "./testdata/inaccurate_expected", - wantErr: true, - }, - "Directory with failing test should return an error": { - directory: "./testdata/failing_unit_test", - wantErr: true, - }, - "Directory with working test should not return an error": { - directory: "./testdata/passing_unit_test", - wantErr: false, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - if err := runTest(tc.directory, false); (err != nil) != tc.wantErr { - t.Errorf("runTest() error = %v, wantErr %v", err, tc.wantErr) - } - }) - } -} diff --git a/internal/testing/testdata/accurate_expected/car.go b/internal/testing/testdata/accurate_expected/car.go deleted file mode 100644 index 3b3a07b..0000000 --- a/internal/testing/testdata/accurate_expected/car.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -//go:generate genz -type Car -template ../getters.tmpl -output car.gen.go -type Car struct { - model string -} diff --git a/internal/testing/testdata/accurate_expected/expected.go b/internal/testing/testdata/accurate_expected/expected.go deleted file mode 100644 index 06ab7d0..0000000 --- a/internal/testing/testdata/accurate_expected/expected.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/internal/testing/testdata/difference_with_expected/car.go b/internal/testing/testdata/difference_with_expected/car.go deleted file mode 100644 index 3b3a07b..0000000 --- a/internal/testing/testdata/difference_with_expected/car.go +++ /dev/null @@ -1,6 +0,0 @@ -package main - -//go:generate genz -type Car -template ../getters.tmpl -output car.gen.go -type Car struct { - model string -} diff --git a/internal/testing/testdata/difference_with_expected/expected.go b/internal/testing/testdata/difference_with_expected/expected.go deleted file mode 100644 index 096cc92..0000000 --- a/internal/testing/testdata/difference_with_expected/expected.go +++ /dev/null @@ -1,3 +0,0 @@ -package main - -var isDifferent = true diff --git a/internal/testing/testdata/empty_directory/.KEEP b/internal/testing/testdata/empty_directory/.KEEP deleted file mode 100644 index e69de29..0000000 diff --git a/internal/testing/testdata/failing_unit_test/car.go b/internal/testing/testdata/failing_unit_test/car.go deleted file mode 100644 index c8794e6..0000000 --- a/internal/testing/testdata/failing_unit_test/car.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -//go:generate genz -type Car -template ../getters.tmpl -output car.gen.go -type Car struct { - //+getter - model string -} diff --git a/internal/testing/testdata/failing_unit_test/car_test.go b/internal/testing/testdata/failing_unit_test/car_test.go deleted file mode 100644 index acdfafb..0000000 --- a/internal/testing/testdata/failing_unit_test/car_test.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "testing" - -func Test_FailingTest(t *testing.T) { - t.Errorf("This test should fail") -} diff --git a/internal/testing/testdata/failing_unit_test/expected.go b/internal/testing/testdata/failing_unit_test/expected.go deleted file mode 100644 index 5cc88f9..0000000 --- a/internal/testing/testdata/failing_unit_test/expected.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func (c *Car) GetModel() string { - return c.model -} diff --git a/internal/testing/testdata/getters.tmpl b/internal/testing/testdata/getters.tmpl deleted file mode 100644 index da822f9..0000000 --- a/internal/testing/testdata/getters.tmpl +++ /dev/null @@ -1,9 +0,0 @@ -package main - -{{ range .Attributes }} - {{ if has "+getter" .Comments }}{{ $receiverName := substr 0 1 $.Type.InternalName | lower}} -func ({{ $receiverName }} *{{ $.Type.InternalName }}) Get{{ camelcase .Name }}() {{ .Type.InternalName }} { - return {{ $receiverName }}.{{.Name}} -} - {{ end }} -{{ end }} diff --git a/internal/testing/testdata/passing_unit_test/car.go b/internal/testing/testdata/passing_unit_test/car.go deleted file mode 100644 index c8794e6..0000000 --- a/internal/testing/testdata/passing_unit_test/car.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -//go:generate genz -type Car -template ../getters.tmpl -output car.gen.go -type Car struct { - //+getter - model string -} diff --git a/internal/testing/testdata/passing_unit_test/car_test.go b/internal/testing/testdata/passing_unit_test/car_test.go deleted file mode 100644 index 28e18e3..0000000 --- a/internal/testing/testdata/passing_unit_test/car_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import "testing" - -func Test_WorkingTest(t *testing.T) { - t.Log("This test should pass") - c := &Car{model: "Ford"} - if c.GetModel() != "Ford" { - t.Errorf("Expected model to be Ford, got %s", c.GetModel()) - } -} diff --git a/internal/testing/testdata/passing_unit_test/expected.go b/internal/testing/testdata/passing_unit_test/expected.go deleted file mode 100644 index 5cc88f9..0000000 --- a/internal/testing/testdata/passing_unit_test/expected.go +++ /dev/null @@ -1,5 +0,0 @@ -package main - -func (c *Car) GetModel() string { - return c.model -} diff --git a/internal/testing/testdata/two_generated_files/car.go b/internal/testing/testdata/two_generated_files/car.go deleted file mode 100644 index b3a3a45..0000000 --- a/internal/testing/testdata/two_generated_files/car.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -//go:generate genz -type Car -template ../getters.tmpl -output car.gen.go -//go:generate genz -type Car -template ../getters.tmpl -output car1.gen.go -type Car struct { - model string -} diff --git a/internal/testing/testdata/two_generated_files/expected.go b/internal/testing/testdata/two_generated_files/expected.go deleted file mode 100644 index 06ab7d0..0000000 --- a/internal/testing/testdata/two_generated_files/expected.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/internal/testing/utils.go b/internal/testing/utils.go deleted file mode 100644 index 81927d4..0000000 --- a/internal/testing/utils.go +++ /dev/null @@ -1,35 +0,0 @@ -package testing - -import ( - "fmt" - "github.com/google/go-cmp/cmp" - "log" - "os" -) - -// assertOutputIsEqual compares the expected and actual byte slices and returns an error if they are different. -// If verbose is true, the output is printed. This is useful for debugging. -func assertOutputIsEqual(expectedFileName, actualFileName string, expected, actual []byte, verbose bool) error { - if verbose { - log.Printf("Comparing %s and %s\n", expectedFileName, actualFileName) - } - if string(expected) != string(actual) { - if verbose { - return fmt.Errorf("Difference between %s and %s:\n%s", expectedFileName, actualFileName, cmp.Diff(string(expected), string(actual))) - } - return fmt.Errorf("Difference between %s and %s:\n", expectedFileName, actualFileName) - } - if verbose { - log.Printf("No difference between %s and %s\n", expectedFileName, actualFileName) - } - return nil -} - -func removeFiles(generatedFiles []string) error { - for _, f := range generatedFiles { - if err := os.Remove(f); err != nil { - return fmt.Errorf("failed to remove file %s: %w", f, err) - } - } - return nil -} diff --git a/internal/testing/utils_test.go b/internal/testing/utils_test.go deleted file mode 100644 index cc50f80..0000000 --- a/internal/testing/utils_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package testing - -import "testing" - -func TestAssertOutputIsEqual(t *testing.T) { - type args struct { - expected []byte - actual []byte - } - tests := map[string]struct { - args args - wantErr bool - }{ - "Same content should not return an error": { - args: args{expected: []byte("foo"), actual: []byte("foo")}, - wantErr: false, - }, - "Different content should return an error": { - args: args{expected: []byte("foo"), actual: []byte("bar")}, - wantErr: true, - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - if err := assertOutputIsEqual("", "", tc.args.expected, tc.args.actual, false); (err != nil) != tc.wantErr { - t.Errorf("assertOutputIsEqual() error = %v, wantErr %v", err, tc.wantErr) - } - }) - } -} diff --git a/main.go b/main.go deleted file mode 100644 index 78a93e6..0000000 --- a/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "github.com/Joffref/genz/cmd/genz" - "log" -) - -func main() { - if err := genz.Execute(); err != nil { - log.Fatal(err) - } -} diff --git a/pkg/generator/declaration.go b/pkg/generator/declaration.go deleted file mode 100644 index b56f469..0000000 --- a/pkg/generator/declaration.go +++ /dev/null @@ -1,5 +0,0 @@ -package generator - -type Declaration interface { - Generate() string -} diff --git a/pkg/generator/function.go b/pkg/generator/function.go deleted file mode 100644 index bf80aee..0000000 --- a/pkg/generator/function.go +++ /dev/null @@ -1,100 +0,0 @@ -package generator - -import ( - "bytes" -) - -type FuncDecl struct { - name string - inputs map[string]string - returns []string - receiverName string - receiverType string - isPtrReceiver bool - isSignature bool - body string -} - -func Function(name string) *FuncDecl { - return &FuncDecl{ - name: name, - } -} - -func (f *FuncDecl) WithReceiver(receiverName string, receiverType string, isPtrReceiver bool) *FuncDecl { - f.isPtrReceiver = isPtrReceiver - f.receiverType = receiverType - f.receiverName = receiverName - return f -} - -func (f *FuncDecl) WithInputs(inputs map[string]string) *FuncDecl { - if f.inputs == nil { - f.inputs = make(map[string]string) - } - for inputName, inputType := range inputs { - f.inputs[inputName] = inputType - } - return f -} - -func (f *FuncDecl) WithReturns(returns ...string) *FuncDecl { - f.returns = returns - return f -} - -func (f *FuncDecl) WithBody(body string) *FuncDecl { - f.body = body - return f -} - -func (f *FuncDecl) SignatureOnly() *FuncDecl { - f.body = "" - f.isSignature = true - return f -} - -func (f *FuncDecl) Generate() string { - var buf bytes.Buffer - buf.WriteString("func ") - if f.receiverName != "" { - buf.WriteString("(") - buf.WriteString(f.receiverName) - if f.isPtrReceiver { - buf.WriteString("*") - } - buf.WriteString(" ") - buf.WriteString(f.receiverType) - buf.WriteString(")") - } - buf.WriteString(" ") - buf.WriteString(f.name) - buf.WriteString("(") - for inputName, inputType := range f.inputs { - buf.WriteString(inputName) - buf.WriteString(" ") - buf.WriteString(inputType) - buf.WriteString(", ") - } - buf.WriteString(") ") - if len(f.returns) > 0 { - buf.WriteString("(") - for _, returnType := range f.returns { - buf.WriteString(returnType) - buf.WriteString(", ") - } - buf.WriteString(")") - } - if f.isSignature { - buf.WriteString("\n") - } else { - buf.WriteString(" {") - if f.body != "" { - buf.WriteString("\n") - buf.WriteString(f.body) - buf.WriteString("\n") - } - buf.WriteString("}") - } - return buf.String() -} diff --git a/pkg/generator/interface.go b/pkg/generator/interface.go deleted file mode 100644 index 020c920..0000000 --- a/pkg/generator/interface.go +++ /dev/null @@ -1,54 +0,0 @@ -package generator - -import ( - "bytes" - "fmt" -) - -type InterfaceDecl struct { - name string - comments []string - methods map[string]struct { - methodName string - methodDecl *FuncDecl - } -} - -func Interface(name string) *InterfaceDecl { - return &InterfaceDecl{ - name: name, - methods: make(map[string]struct { - methodName string - methodDecl *FuncDecl - }), - } -} - -func (i *InterfaceDecl) WithComments(comments ...string) *InterfaceDecl { - i.comments = append(i.comments, comments...) - return i -} - -func (i *InterfaceDecl) WithMethod(methodName string, methodDecl *FuncDecl) *InterfaceDecl { - methodDecl.SignatureOnly() - i.methods[methodName] = struct { - methodName string - methodDecl *FuncDecl - }{methodName: methodName, methodDecl: methodDecl} - return i -} - -func (i *InterfaceDecl) Generate() string { - var buf bytes.Buffer - if len(i.comments) > 0 { - for _, comment := range i.comments { - buf.WriteString(fmt.Sprintf("// %s\n", comment)) - } - } - buf.WriteString(fmt.Sprintf("type %s interface {\n", i.name)) - for _, method := range i.methods { - buf.WriteString(fmt.Sprintf("%s\n", method.methodDecl.Generate())) - } - buf.WriteString("}\n") - return buf.String() -} diff --git a/pkg/generator/struct.go b/pkg/generator/struct.go deleted file mode 100644 index a03910e..0000000 --- a/pkg/generator/struct.go +++ /dev/null @@ -1,93 +0,0 @@ -package generator - -import ( - "bytes" - "fmt" -) - -type StructDecl struct { - name string - fields map[string]struct { - fieldName string - fieldType string - inlineComment string - comments []string - tags map[string]string - } -} - -func Struct(name string) *StructDecl { - return &StructDecl{ - name: name, - fields: make(map[string]struct { - fieldName string - fieldType string - inlineComment string - comments []string - tags map[string]string - }), - } -} - -func (s *StructDecl) WithField(fieldName string, fieldType string) *StructDecl { - s.fields[fieldName] = struct { - fieldName string - fieldType string - inlineComment string - comments []string - tags map[string]string - }{fieldName: fieldName, fieldType: fieldType} - return s -} - -func (s *StructDecl) WithFieldInlineComment(fieldName string, fieldType string, inlineComment string) *StructDecl { - s.fields[fieldName] = struct { - fieldName string - fieldType string - inlineComment string - comments []string - tags map[string]string - }{fieldName: fieldName, fieldType: fieldType, inlineComment: inlineComment} - return s -} - -func (s *StructDecl) WithFieldComments(fieldName string, fieldType string, comments []string) *StructDecl { - s.fields[fieldName] = struct { - fieldName string - fieldType string - inlineComment string - comments []string - tags map[string]string - }{fieldName: fieldName, fieldType: fieldType, comments: comments} - return s -} - -func (s *StructDecl) WithFieldTags(fieldName string, fieldType string, tags map[string]string) *StructDecl { - s.fields[fieldName] = struct { - fieldName string - fieldType string - inlineComment string - comments []string - tags map[string]string - }{fieldName: fieldName, fieldType: fieldType, tags: tags} - return s -} - -func (s *StructDecl) Generate() string { - var buf bytes.Buffer - buf.WriteString(fmt.Sprintf("type %s struct {\n", s.name)) - for _, field := range s.fields { - if field.inlineComment != "" { - buf.WriteString(fmt.Sprintf("\t%s %s // %s\n", field.fieldName, field.fieldType, field.inlineComment)) - } else if len(field.comments) > 0 { - buf.WriteString(fmt.Sprintf("\t%s %s // %s\n", field.fieldName, field.fieldType, field.comments[0])) - for _, comment := range field.comments[1:] { - buf.WriteString(fmt.Sprintf("\t// %s\n", comment)) - } - } else { - buf.WriteString(fmt.Sprintf("\t%s %s\n", field.fieldName, field.fieldType)) - } - } - buf.WriteString("}\n") - return buf.String() -} From e9380533f90fe306fef1ce19e0013a09f0b4bcd3 Mon Sep 17 00:00:00 2001 From: Joffref Date: Tue, 9 Jan 2024 10:25:34 +0100 Subject: [PATCH 3/4] Fix CI Signed-off-by: Joffref --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01c0db9..5d6c7d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Download dependencies run: go mod download - name: Install golangci-lint - run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.1 + run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 - name: Run golangci-lint run: golangci-lint run - name: Run tests From a96b51893c24690a212fa3f1b196f23628bfa55a Mon Sep 17 00:00:00 2001 From: Joffref Date: Tue, 16 Jan 2024 21:47:36 +0100 Subject: [PATCH 4/4] Fix CI Signed-off-by: Joffref --- .github/workflows/ci.yml | 2 +- examples/2_getters/main.go | 12 +++++- go.mod | 12 +----- go.sum | 82 +------------------------------------- 4 files changed, 14 insertions(+), 94 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d6c7d9..dfa3204 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Download dependencies run: go mod download - name: Install golangci-lint - run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.55.2 + run: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - name: Run golangci-lint run: golangci-lint run - name: Run tests diff --git a/examples/2_getters/main.go b/examples/2_getters/main.go index b9228b9..6616b0e 100644 --- a/examples/2_getters/main.go +++ b/examples/2_getters/main.go @@ -5,7 +5,6 @@ import ( "github.com/Joffref/genz/genz/cli" "github.com/Joffref/genz/genz/models" "io" - "slices" ) func main() { @@ -16,7 +15,7 @@ func MyCustomGenerator(w io.Writer, parsedElement models.ParsedElement) error { code := genz.NewCode(w, parsedElement.PackageName) for _, attribute := range parsedElement.Attributes { - if slices.Contains(attribute.Comments, "+getter") { + if Contains(attribute.Comments, "+getter") { code.WithDeclarations( genz.Function("Get"+attribute.Name). WithReceiver("v", parsedElement.Type.InternalName, false). @@ -27,3 +26,12 @@ func MyCustomGenerator(w io.Writer, parsedElement models.ParsedElement) error { } return code.Generate() } + +func Contains(slice []string, element string) bool { + for _, e := range slice { + if e == element { + return true + } + } + return false +} diff --git a/go.mod b/go.mod index fcc7f98..f307d7e 100644 --- a/go.mod +++ b/go.mod @@ -3,22 +3,12 @@ module github.com/Joffref/genz go 1.21 require ( - github.com/Masterminds/sprig/v3 v3.2.3 github.com/google/go-cmp v0.5.9 golang.org/x/tools v0.14.0 ) require ( - github.com/Masterminds/goutils v1.1.1 // indirect - github.com/Masterminds/semver/v3 v3.2.1 // indirect - github.com/google/uuid v1.4.0 // indirect - github.com/huandu/xstrings v1.4.0 // indirect - github.com/imdario/mergo v0.3.16 // indirect - github.com/mitchellh/copystructure v1.2.0 // indirect - github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/shopspring/decimal v1.3.1 // indirect - github.com/spf13/cast v1.5.1 // indirect - golang.org/x/crypto v0.14.0 // indirect + github.com/google/uuid v1.5.0 // indirect (used for testing purposes) golang.org/x/mod v0.13.0 // indirect golang.org/x/sys v0.13.0 // indirect ) diff --git a/go.sum b/go.sum index 9c8052d..945625c 100644 --- a/go.sum +++ b/go.sum @@ -1,91 +1,13 @@ -github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= -github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= -github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= -github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= -github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= -github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= -github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= -github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= -github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= -github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= -github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= -golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc= golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=