├── .go-version ├── renovate.json ├── .gitignore ├── internal ├── constructor ├── version.go ├── test │ ├── multitypes │ │ ├── structure.go │ │ ├── structure_with_multiple_imports.go │ │ └── structure_test.go │ ├── init_return_propagation │ │ ├── structure.go │ │ └── structure_test.go │ ├── structure.go │ └── structure_test.go ├── file_parser.go ├── getter_generator.go └── package_parser.go ├── go.mod ├── .github └── workflows │ ├── release.yml │ └── go.yml ├── .goreleaser.yml ├── Makefile ├── LICENSE ├── go.sum ├── cmd └── gonstructor │ └── gonstructor.go └── README.md /.go-version: -------------------------------------------------------------------------------- 1 | 1.22.2 2 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.exe~ 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | *.test 8 | 9 | *.out 10 | 11 | /vendor/ 12 | /Godeps/ 13 | 14 | /internal/test/*_gen.go 15 | /internal/test/**/*_gen.go 16 | /dist/ 17 | 18 | /gonstructor 19 | -------------------------------------------------------------------------------- /internal/constructor/generator.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | import g "github.com/moznion/gowrtr/generator" 4 | 5 | // Generator is an interface that has the responsibility to generate a constructor code. 6 | type Generator interface { 7 | // Generate generates a constructor statement. 8 | Generate(indentLevel int) g.Statement 9 | } 10 | -------------------------------------------------------------------------------- /internal/constructor/utils.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | import "fmt" 4 | 5 | // optionally returns the given string with the prefix applied 6 | // if the use prefix boolean is true 7 | func withConditionalPrefix(s, prefix string, usePrefix bool) string { 8 | if !usePrefix { 9 | prefix = "" 10 | } 11 | return fmt.Sprintf("%s%s", prefix, s) 12 | } 13 | -------------------------------------------------------------------------------- /internal/version.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | ) 7 | 8 | var ver string 9 | var rev string 10 | 11 | // ShowVersion shows the version and revision as JSON string. 12 | func ShowVersion() { 13 | versionJSON, _ := json.Marshal(map[string]string{ 14 | "version": ver, 15 | "revision": rev, 16 | }) 17 | fmt.Printf("%s\n", versionJSON) 18 | } 19 | -------------------------------------------------------------------------------- /internal/constructor/field.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | // Field represents a field of the structure for a constructor to be generated. 4 | type Field struct { 5 | // FieldName is a name of the field. 6 | FieldName string 7 | // FieldType is a type of the field. 8 | FieldType string 9 | // ShouldIgnore marks whether the field should be ignored or not in a constructor. 10 | ShouldIgnore bool 11 | } 12 | -------------------------------------------------------------------------------- /internal/test/multitypes/structure.go: -------------------------------------------------------------------------------- 1 | package multitypes 2 | 3 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=AlphaStructure --type=BravoStructure --constructorTypes=allArgs,builder --withGetter --output=./alpha_and_bravo_gen.go" 4 | 5 | type AlphaStructure struct { 6 | foo string 7 | bar int 8 | } 9 | 10 | type BravoStructure struct { 11 | buz string 12 | qux int 13 | } 14 | -------------------------------------------------------------------------------- /internal/constructor/utils_test.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestWithConditionalPrefix(t *testing.T) { 10 | t.Run("apply prefix", func(t *testing.T) { 11 | got := withConditionalPrefix("Struct", "*", true) 12 | assert.Equal(t, "*Struct", got) 13 | }) 14 | 15 | t.Run("ignore prefix", func(t *testing.T) { 16 | got := withConditionalPrefix("Struct", "*", false) 17 | assert.Equal(t, "Struct", got) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/moznion/gonstructor 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.25.1 6 | 7 | require ( 8 | github.com/iancoleman/strcase v0.3.0 9 | github.com/moznion/gowrtr v1.7.0 10 | github.com/stretchr/testify v1.11.1 11 | golang.org/x/tools v0.37.0 12 | ) 13 | 14 | require ( 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/pkg/errors v0.9.1 // indirect 17 | github.com/pmezard/go-difflib v1.0.0 // indirect 18 | golang.org/x/mod v0.28.0 // indirect 19 | golang.org/x/sync v0.17.0 // indirect 20 | gopkg.in/yaml.v3 v3.0.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /internal/constructor/struct.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | g "github.com/moznion/gowrtr/generator" 8 | ) 9 | 10 | func generateStructure(typeName string, keyValues []string, indentLevel int, returnValue bool) string { 11 | indent := g.BuildIndent(indentLevel) 12 | nextIndent := g.BuildIndent(indentLevel + 1) 13 | 14 | structure := fmt.Sprintf( 15 | "%s{\n%s%s,\n%s}", 16 | typeName, 17 | nextIndent, 18 | strings.Join(keyValues, ",\n"+nextIndent), 19 | indent, 20 | ) 21 | return withConditionalPrefix(structure, "&", !returnValue) 22 | } 23 | -------------------------------------------------------------------------------- /internal/file_parser.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/parser" 7 | "go/token" 8 | ) 9 | 10 | // ParseFiles parses the files to get AST. 11 | func ParseFiles(files []string) ([]*ast.File, error) { 12 | fset := token.NewFileSet() 13 | 14 | astFiles := make([]*ast.File, len(files)) 15 | for i, file := range files { 16 | parsed, err := parser.ParseFile(fset, file, nil, parser.ParseComments) 17 | if err != nil { 18 | return nil, fmt.Errorf("failed to parse file: %w", err) 19 | } 20 | astFiles[i] = parsed 21 | } 22 | return astFiles, nil 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*.*.*' 7 | 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Set up Go 13 | uses: actions/setup-go@v5 14 | with: 15 | go-version: '1.25.x' 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | - name: Run GoReleaser 19 | uses: goreleaser/goreleaser-action@v6 20 | with: 21 | version: latest 22 | args: release --clean 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Test 6 | strategy: 7 | matrix: 8 | go-version: [1.24.x, 1.25.x] 9 | os: [ubuntu-latest] 10 | runs-on: ${{ matrix.os }} 11 | steps: 12 | - name: Set up Go 13 | uses: actions/setup-go@v5 14 | with: 15 | go-version: ${{ matrix.go-version }} 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v4 18 | - name: Install go toolchains 19 | run: | 20 | go install golang.org/x/tools/cmd/goimports@latest 21 | - name: Do test 22 | run: make check-ci 23 | - name: golangci-lint 24 | uses: golangci/golangci-lint-action@v8 25 | with: 26 | version: latest 27 | 28 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: gonstructor 2 | 3 | before: 4 | hooks: 5 | - go mod tidy 6 | 7 | builds: 8 | - 9 | main: ./cmd/gonstructor/gonstructor.go 10 | ldflags: 11 | - "-X github.com/moznion/gonstructor/internal.rev={{ .FullCommit }}" 12 | - "-X github.com/moznion/gonstructor/internal.ver={{ .Version }}" 13 | env: 14 | - CGO_ENABLED=0 15 | goos: 16 | - linux 17 | - darwin 18 | - windows 19 | goarch: 20 | - 386 21 | - amd64 22 | - arm 23 | - arm64 24 | 25 | checksum: 26 | name_template: 'checksums.txt' 27 | 28 | snapshot: 29 | name_template: "{{ .Tag }}-next" 30 | 31 | changelog: 32 | sort: desc 33 | filters: 34 | exclude: 35 | - '^docs:' 36 | - '^test:' 37 | - '^:pencil:' 38 | 39 | -------------------------------------------------------------------------------- /internal/test/multitypes/structure_with_multiple_imports.go: -------------------------------------------------------------------------------- 1 | package multitypes 2 | 3 | import ( 4 | "io" 5 | "net" 6 | ) 7 | 8 | // NOTE: 9 | // This structure file is for checking whether the compilation passes for the generated file according to this. 10 | // ref: https://github.com/moznion/gonstructor/pull/21 11 | // 12 | // Until the above pull request, the generated code hadn't passed the compilation. 13 | 14 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureWhichNeedsImport1 --type=StructureWhichNeedsImport2 --output structure_with_multiple_imports_gen.go --constructorTypes=allArgs,builder --withGetter" 15 | 16 | type StructureWhichNeedsImport1 struct { 17 | foo net.Conn 18 | } 19 | 20 | type StructureWhichNeedsImport2 struct { 21 | bar io.Reader 22 | } 23 | -------------------------------------------------------------------------------- /internal/getter_generator.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/iancoleman/strcase" 7 | "github.com/moznion/gonstructor/internal/constructor" 8 | g "github.com/moznion/gowrtr/generator" 9 | ) 10 | 11 | const receiverName = "s" 12 | 13 | // GenerateGetters generates getters for each field. 14 | func GenerateGetters(typeName string, fields []*constructor.Field) g.Statement { 15 | stmt := g.NewRoot() 16 | for _, field := range fields { 17 | stmt = stmt.AddStatements( 18 | g.NewFunc( 19 | g.NewFuncReceiver(receiverName, "*"+typeName), 20 | g.NewFuncSignature(fmt.Sprintf("Get%s", strcase.ToCamel(field.FieldName))). 21 | AddReturnTypes(field.FieldType), 22 | g.NewReturnStatement(fmt.Sprintf("%s.%s", receiverName, field.FieldName)), 23 | ), 24 | ) 25 | } 26 | return stmt 27 | } 28 | -------------------------------------------------------------------------------- /internal/package_parser.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/tools/go/packages" 7 | ) 8 | 9 | // ParsePackage parses files according to given directory pattern to get the package information. 10 | func ParsePackage(dirPattern []string) (*packages.Package, error) { 11 | pkgs, err := packages.Load(&packages.Config{ 12 | Mode: packages.NeedName | 13 | packages.NeedFiles | 14 | packages.NeedCompiledGoFiles | 15 | packages.NeedImports | 16 | packages.NeedTypes | 17 | packages.NeedTypesSizes | 18 | packages.NeedSyntax | 19 | packages.NeedTypesInfo, 20 | Tests: false, 21 | }, dirPattern...) 22 | if err != nil { 23 | return nil, fmt.Errorf("failed to load package: %w", err) 24 | } 25 | if len(pkgs) != 1 { 26 | return nil, fmt.Errorf("ambiguous error; %d packages found", len(pkgs)) 27 | } 28 | return pkgs[0], nil 29 | } 30 | -------------------------------------------------------------------------------- /internal/constructor/strcase_test.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestToLowerCase(t *testing.T) { 8 | tests := []struct { 9 | input string 10 | want string 11 | }{ 12 | {input: "", want: ""}, 13 | {input: "ID", want: "id"}, 14 | {input: "utf8", want: "utf8"}, 15 | {input: "Utf8", want: "utf8"}, 16 | {input: "UTF8", want: "utf8"}, 17 | {input: "utf8name", want: "utf8name"}, 18 | {input: "utf8_name", want: "utf8name"}, 19 | {input: "Name", want: "name"}, 20 | {input: "name", want: "name"}, 21 | {input: "NAME", want: "name"}, 22 | {input: "UserName", want: "userName"}, 23 | {input: "userName", want: "userName"}, 24 | {input: "user_Name", want: "userName"}, 25 | {input: "MoZnIoN", want: "moZnIoN"}, 26 | } 27 | 28 | for _, test := range tests { 29 | got := toLowerCamel(test.input) 30 | if got != test.want { 31 | t.Errorf("toLowerCamel: want %v, but %v for %v:", test.want, got, test.input) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PKGS := $(shell go list ./...) 2 | PKGS_WITHOUT_TEST := $(shell go list ./... | grep -v "gonstructor/internal/test") 3 | RELEASE_DIR=dist 4 | REVISION=$(shell git rev-parse --verify HEAD) 5 | INTERNAL_PACKAGE=github.com/moznion/gonstructor/internal 6 | 7 | check: test lint vet fmt-check 8 | check-ci: test vet fmt-check 9 | 10 | build4test: clean 11 | mkdir -p $(RELEASE_DIR) 12 | go build -ldflags "-X $(INTERNAL_PACKAGE).rev=$(REVISION) -X $(INTERNAL_PACKAGE).ver=TESTING" \ 13 | -o $(RELEASE_DIR)/gonstructor_test cmd/gonstructor/gonstructor.go 14 | 15 | gen4test: build4test 16 | go generate $(PKGS) 17 | 18 | test: clean-test-gen gen4test 19 | go test -v $(PKGS) 20 | 21 | clean-test-gen: 22 | rm -f internal/test/*_gen.go internal/test/**/*_gen.go 23 | 24 | lint: 25 | lint: 26 | golangci-lint run ./... 27 | 28 | vet: 29 | go vet $(PKGS) 30 | 31 | fmt-check: 32 | gofmt -l -s **/*.go | grep [^*][.]go$$; \ 33 | EXIT_CODE=$$?; \ 34 | if [ $$EXIT_CODE -eq 0 ]; then exit 1; fi; \ 35 | goimports -l **/*.go | grep [^*][.]go$$; \ 36 | EXIT_CODE=$$?; \ 37 | if [ $$EXIT_CODE -eq 0 ]; then exit 1; fi \ 38 | 39 | fmt: 40 | gofmt -w -s **/*.go 41 | goimports -w **/*.go 42 | 43 | clean: 44 | rm -rf bin/gonstructor* 45 | 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2019 moznion, http://moznion.net/ 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | 13 | -------------------------------------------------------------------------------- /internal/test/init_return_propagation/structure.go: -------------------------------------------------------------------------------- 1 | package init_return_propagation 2 | 3 | import "errors" 4 | 5 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureWithInitFoo --constructorTypes=allArgs,builder --init initialize -propagateInitFuncReturns" 6 | type StructureWithInitFoo struct { 7 | foo string 8 | checked bool `gonstructor:"-"` 9 | } 10 | 11 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureWithInitBar --constructorTypes=allArgs,builder --init initializeWithActualValueReceiver -propagateInitFuncReturns" 12 | type StructureWithInitBar struct { 13 | foo string 14 | checked bool `gonstructor:"-"` 15 | } 16 | 17 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureWithInitBuz --constructorTypes=allArgs,builder --init initializeWithError -propagateInitFuncReturns" 18 | type StructureWithInitBuz struct { 19 | foo string 20 | checked bool `gonstructor:"-"` 21 | } 22 | 23 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureWithInitQux --constructorTypes=allArgs,builder --init initializeWithMultipleReturns -propagateInitFuncReturns" 24 | type StructureWithInitQux struct { 25 | foo string 26 | checked bool `gonstructor:"-"` 27 | } 28 | 29 | func (s *StructureWithInitFoo) initialize() { 30 | s.checked = true 31 | } 32 | 33 | func (s StructureWithInitBar) initializeWithActualValueReceiver() { 34 | } 35 | 36 | func (s *StructureWithInitBuz) initializeWithError() error { 37 | s.checked = true 38 | return errors.New("err") 39 | } 40 | 41 | func (s *StructureWithInitQux) initializeWithMultipleReturns() (*string, error) { 42 | s.checked = true 43 | 44 | str := "str" 45 | return &str, errors.New("err") 46 | } 47 | -------------------------------------------------------------------------------- /internal/test/structure.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import "io" 4 | 5 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=Structure --constructorTypes=allArgs,builder --withGetter" 6 | type Structure struct { 7 | foo string 8 | bar io.Reader 9 | Buz chan interface{} 10 | qux interface{} `gonstructor:"-"` 11 | } 12 | 13 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=ChildStructure --output=./super_duper_child_structure_gen.go" 14 | type ChildStructure struct { 15 | structure *Structure 16 | foobar string 17 | } 18 | 19 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureWithInit --constructorTypes=allArgs,builder --withGetter --init initialize" 20 | type StructureWithInit struct { 21 | foo string 22 | status string `gonstructor:"-"` 23 | qux interface{} `gonstructor:"-"` 24 | } 25 | 26 | func (structure *StructureWithInit) initialize() { 27 | structure.status = "ok" 28 | } 29 | 30 | type Embedded struct { 31 | Bar string 32 | } 33 | 34 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureWithEmbedding --constructorTypes=allArgs,builder --withGetter" 35 | type StructureWithEmbedding struct { 36 | Embedded 37 | foo string 38 | } 39 | 40 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureWithPointerEmbedding --constructorTypes=allArgs,builder --withGetter" 41 | type StructureWithPointerEmbedding struct { 42 | *Embedded 43 | foo string 44 | } 45 | 46 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureValue --constructorTypes=allArgs,builder --withGetter --returnValue" 47 | type StructureValue struct { 48 | foo string 49 | } 50 | 51 | //go:generate sh -c "$(cd ./\"$(git rev-parse --show-cdup)\" || exit; pwd)/dist/gonstructor_test --type=StructureWithSetterPrefix --constructorTypes=builder --setterPrefix=With" 52 | type StructureWithSetterPrefix struct { 53 | foo string 54 | bar int 55 | } 56 | -------------------------------------------------------------------------------- /internal/test/multitypes/structure_test.go: -------------------------------------------------------------------------------- 1 | package multitypes 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestFooAndBarStructureAllArgsConstructor(t *testing.T) { 10 | givenFooString := "foo" 11 | givenBarInt := 12345 12 | givenBuzString := "buz" 13 | givenQuxInt := 54321 14 | 15 | gotAlpha := NewAlphaStructure(givenFooString, givenBarInt) 16 | gotBravo := NewBravoStructure(givenBuzString, givenQuxInt) 17 | 18 | assert.IsType(t, &AlphaStructure{}, gotAlpha) 19 | assert.IsType(t, &BravoStructure{}, gotBravo) 20 | 21 | assert.EqualValues(t, givenFooString, gotAlpha.foo) 22 | assert.EqualValues(t, givenBarInt, gotAlpha.bar) 23 | 24 | assert.EqualValues(t, givenBuzString, gotBravo.buz) 25 | assert.EqualValues(t, givenQuxInt, gotBravo.qux) 26 | 27 | // test for getters 28 | assert.EqualValues(t, givenFooString, gotAlpha.GetFoo()) 29 | assert.EqualValues(t, givenBarInt, gotAlpha.GetBar()) 30 | 31 | assert.EqualValues(t, givenBuzString, gotBravo.GetBuz()) 32 | assert.EqualValues(t, givenQuxInt, gotBravo.GetQux()) 33 | } 34 | 35 | func TestStructureBuilder(t *testing.T) { 36 | givenFooString := "foo" 37 | givenBarInt := 12345 38 | givenBuzString := "buz" 39 | givenQuxInt := 54321 40 | 41 | alphaBuilder := NewAlphaStructureBuilder() 42 | gotAlpha := alphaBuilder.Foo(givenFooString). 43 | Bar(givenBarInt). 44 | Build() 45 | assert.IsType(t, &AlphaStructure{}, gotAlpha) 46 | 47 | assert.EqualValues(t, givenFooString, gotAlpha.foo) 48 | assert.EqualValues(t, givenBarInt, gotAlpha.bar) 49 | 50 | bravoBuilder := NewBravoStructureBuilder() 51 | gotBravo := bravoBuilder.Buz(givenBuzString). 52 | Qux(givenQuxInt). 53 | Build() 54 | assert.IsType(t, &BravoStructure{}, gotBravo) 55 | 56 | assert.EqualValues(t, givenBuzString, gotBravo.buz) 57 | assert.EqualValues(t, givenQuxInt, gotBravo.qux) 58 | } 59 | 60 | func TestStructureWhichNeedsImport1Instantiation(t *testing.T) { 61 | s := NewStructureWhichNeedsImport1(nil) 62 | assert.IsType(t, &StructureWhichNeedsImport1{}, s) 63 | } 64 | 65 | func TestStructureWhichNeedsImport2Instantiation(t *testing.T) { 66 | s := NewStructureWhichNeedsImport2(nil) 67 | assert.IsType(t, &StructureWhichNeedsImport2{}, s) 68 | } 69 | -------------------------------------------------------------------------------- /internal/test/init_return_propagation/structure_test.go: -------------------------------------------------------------------------------- 1 | package init_return_propagation 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestInitFuncPropagation_InitializeWithNoReturnValue(t *testing.T) { 10 | givenString := "givenstr" 11 | 12 | got := NewStructureWithInitFoo(givenString) 13 | assert.IsType(t, &StructureWithInitFoo{}, got) 14 | assert.EqualValues(t, givenString, got.foo) 15 | assert.True(t, true, got.checked) 16 | 17 | got = NewStructureWithInitFooBuilder().Foo(givenString).Build() 18 | assert.IsType(t, &StructureWithInitFoo{}, got) 19 | assert.EqualValues(t, givenString, got.foo) 20 | assert.True(t, true, got.checked) 21 | } 22 | 23 | func TestInitFuncPropagation_InitializeWithActualValueReceiver(t *testing.T) { 24 | givenString := "givenstr" 25 | 26 | got := NewStructureWithInitBar(givenString) 27 | assert.IsType(t, &StructureWithInitBar{}, got) 28 | assert.EqualValues(t, givenString, got.foo) 29 | assert.True(t, true, got.checked) 30 | 31 | got = NewStructureWithInitBarBuilder().Foo(givenString).Build() 32 | assert.IsType(t, &StructureWithInitBar{}, got) 33 | assert.EqualValues(t, givenString, got.foo) 34 | assert.True(t, true, got.checked) 35 | } 36 | 37 | func TestInitFuncPropagation_InitializeWithError(t *testing.T) { 38 | givenString := "givenstr" 39 | 40 | got, err := NewStructureWithInitBuz(givenString) 41 | assert.IsType(t, &StructureWithInitBuz{}, got) 42 | assert.EqualValues(t, givenString, got.foo) 43 | assert.True(t, true, got.checked) 44 | assert.EqualError(t, err, "err") 45 | 46 | got, err = NewStructureWithInitBuzBuilder().Foo(givenString).Build() 47 | assert.IsType(t, &StructureWithInitBuz{}, got) 48 | assert.EqualValues(t, givenString, got.foo) 49 | assert.True(t, true, got.checked) 50 | assert.EqualError(t, err, "err") 51 | } 52 | 53 | func TestInitFuncPropagation_InitializeWithMultipleReturns(t *testing.T) { 54 | givenString := "givenstr" 55 | 56 | got, gotStr, err := NewStructureWithInitQux(givenString) 57 | assert.IsType(t, &StructureWithInitQux{}, got) 58 | assert.EqualValues(t, givenString, got.foo) 59 | assert.True(t, true, got.checked) 60 | assert.EqualValues(t, *gotStr, "str") 61 | assert.EqualError(t, err, "err") 62 | 63 | got, gotStr, err = NewStructureWithInitQuxBuilder().Foo(givenString).Build() 64 | assert.IsType(t, &StructureWithInitQux{}, got) 65 | assert.EqualValues(t, givenString, got.foo) 66 | assert.True(t, true, got.checked) 67 | assert.EqualValues(t, *gotStr, "str") 68 | assert.EqualError(t, err, "err") 69 | } 70 | -------------------------------------------------------------------------------- /internal/constructor/strcase.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | import ( 4 | "strings" 5 | "unicode" 6 | ) 7 | 8 | // https://github.com/golang/lint/blob/206c0f020eba0f7fbcfbc467a5eb808037df2ed6/lint.go#L731 9 | var commonInitialisms = map[string]bool{ 10 | "ACL": true, 11 | "API": true, 12 | "ASCII": true, 13 | "CPU": true, 14 | "CSS": true, 15 | "DNS": true, 16 | "EOF": true, 17 | "GUID": true, 18 | "HTML": true, 19 | "HTTP": true, 20 | "HTTPS": true, 21 | "ID": true, 22 | "IP": true, 23 | "JSON": true, 24 | "LHS": true, 25 | "QPS": true, 26 | "RAM": true, 27 | "RHS": true, 28 | "RPC": true, 29 | "SLA": true, 30 | "SMTP": true, 31 | "SQL": true, 32 | "SSH": true, 33 | "TCP": true, 34 | "TLS": true, 35 | "TTL": true, 36 | "UDP": true, 37 | "UI": true, 38 | "UID": true, 39 | "UUID": true, 40 | "URI": true, 41 | "URL": true, 42 | "UTF8": true, 43 | "VM": true, 44 | "XML": true, 45 | "XMPP": true, 46 | "XSRF": true, 47 | "XSS": true, 48 | } 49 | 50 | func toLowerCamel(name string) string { 51 | // Fast path for simple cases: "_" and all lowercase. 52 | if name == "_" { 53 | return name 54 | } 55 | allLower := true 56 | for _, r := range name { 57 | if !unicode.IsLower(r) { 58 | allLower = false 59 | break 60 | } 61 | } 62 | if allLower { 63 | return name 64 | } 65 | 66 | // Split camelCase at any lower->upper transition, and split on underscores. 67 | // Check each word for common initialisms. 68 | runes := []rune(name) 69 | w, i := 0, 0 // index of start of word, scan 70 | for i+1 <= len(runes) { 71 | eow := false // whether we hit the end of a word 72 | if i+1 == len(runes) { 73 | eow = true 74 | } else if runes[i+1] == '_' { 75 | // underscore; shift the remainder forward over any run of underscores 76 | eow = true 77 | n := 1 78 | for i+n+1 < len(runes) && runes[i+n+1] == '_' { 79 | n++ 80 | } 81 | 82 | // Leave at most one underscore if the underscore is between two digits 83 | if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { 84 | n-- 85 | } 86 | 87 | copy(runes[i+1:], runes[i+n+1:]) 88 | runes = runes[:len(runes)-n] 89 | } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { 90 | // lower->non-lower 91 | eow = true 92 | } 93 | i++ 94 | if !eow { 95 | continue 96 | } 97 | 98 | // [w,i) is a word. 99 | word := string(runes[w:i]) 100 | if u := strings.ToUpper(word); commonInitialisms[u] { 101 | if w == 0 { 102 | u = strings.ToLower(u) 103 | } 104 | copy(runes[w:], []rune(u)) 105 | } else if w == 0 { 106 | copy(runes[w:], []rune(strings.ToLower(word))) 107 | } 108 | w = i 109 | } 110 | return string(runes) 111 | } 112 | -------------------------------------------------------------------------------- /internal/constructor/all_args_constructor_generator.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/iancoleman/strcase" 8 | g "github.com/moznion/gowrtr/generator" 9 | ) 10 | 11 | // AllArgsConstructorGenerator is a struct type that has the responsibility to 12 | // generate a statement of a constructor with all of arguments. 13 | type AllArgsConstructorGenerator struct { 14 | TypeName string 15 | Fields []*Field 16 | InitFunc string 17 | InitFuncReturnTypes []string 18 | PropagateInitFuncReturns bool 19 | ReturnValue bool 20 | } 21 | 22 | // Generate generates a constructor statement with all of arguments. 23 | func (cg *AllArgsConstructorGenerator) Generate(indentLevel int) g.Statement { 24 | funcSignature := g.NewFuncSignature(fmt.Sprintf("New%s", strcase.ToCamel(cg.TypeName))) 25 | 26 | retStructureKeyValues := make([]string, 0) 27 | for _, field := range cg.Fields { 28 | if field.ShouldIgnore { 29 | continue 30 | } 31 | funcSignature = funcSignature.AddParameters( 32 | g.NewFuncParameter(toLowerCamel(field.FieldName), field.FieldType), 33 | ) 34 | retStructureKeyValues = append( 35 | retStructureKeyValues, 36 | fmt.Sprintf("%s: %s", field.FieldName, toLowerCamel(field.FieldName)), 37 | ) 38 | } 39 | 40 | funcSignature = funcSignature. 41 | AddReturnTypes(withConditionalPrefix(cg.TypeName, "*", !cg.ReturnValue)). 42 | AddReturnTypes(func() []string { 43 | if len(cg.InitFuncReturnTypes) <= 0 { 44 | return []string{} 45 | } 46 | return cg.InitFuncReturnTypes 47 | }()...) 48 | 49 | retStructure := generateStructure(cg.TypeName, retStructureKeyValues, indentLevel+1, cg.ReturnValue) 50 | 51 | var stmts []g.Statement 52 | 53 | if cg.InitFunc != "" { 54 | stmts = []g.Statement{ 55 | g.NewRawStatementf("r := %s", retStructure), 56 | g.NewNewline(), 57 | } 58 | 59 | if cg.PropagateInitFuncReturns && len(cg.InitFuncReturnTypes) > 0 { 60 | initFuncReturnValues := make([]string, len(cg.InitFuncReturnTypes)) 61 | for i := 0; i < len(cg.InitFuncReturnTypes); i++ { 62 | initFuncReturnValues[i] = fmt.Sprintf("ret_%s%d", cg.InitFunc, i) 63 | } 64 | 65 | stmts = append(stmts, 66 | g.NewRawStatementf( 67 | "%s := r.%s()", 68 | strings.Join(initFuncReturnValues, ", "), 69 | cg.InitFunc, 70 | ), 71 | g.NewNewline(), 72 | g.NewReturnStatement("r").AddReturnItems(initFuncReturnValues...), 73 | ) 74 | } else { 75 | stmts = append(stmts, 76 | g.NewRawStatementf("r.%s()", cg.InitFunc), 77 | g.NewNewline(), 78 | g.NewReturnStatement("r"), 79 | ) 80 | } 81 | } else { 82 | stmts = []g.Statement{ 83 | g.NewReturnStatement(retStructure), 84 | } 85 | } 86 | 87 | fn := g.NewFunc( 88 | nil, 89 | funcSignature, 90 | stmts..., 91 | ) 92 | 93 | return fn 94 | } 95 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 5 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 6 | github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= 7 | github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 8 | github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= 9 | github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 10 | github.com/moznion/gowrtr v1.6.0 h1:LKF1Alq3PbN8ePANPzQu9/kLEupaLfqviq28ETt1Za4= 11 | github.com/moznion/gowrtr v1.6.0/go.mod h1:3Aa/LF0Z8sG4VpNxtzd+2pzIDvMX3N2O17LVkybHDNA= 12 | github.com/moznion/gowrtr v1.7.0 h1:bIOdlAeEHDJEs9o2TIS7Oq3HsPIvxGauPHf3a8IVjXE= 13 | github.com/moznion/gowrtr v1.7.0/go.mod h1:sjAFodAvRj5fljRjf9yht52GMBVzELkyxcE31B1vJgg= 14 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 15 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 16 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 17 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 18 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 19 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 20 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 21 | github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= 22 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 23 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 24 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 25 | golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= 26 | golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= 27 | golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= 28 | golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= 29 | golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE= 30 | golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w= 31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 34 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 35 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 36 | -------------------------------------------------------------------------------- /internal/constructor/field_collector.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/types" 7 | "reflect" 8 | "strings" 9 | ) 10 | 11 | const gonstructorTag = "gonstructor" 12 | 13 | // CollectConstructorFieldsFromAST collects fields to include in a constructor from the AST. 14 | func CollectConstructorFieldsFromAST(typeName string, astFiles []*ast.File) ([]*Field, error) { 15 | for _, astFile := range astFiles { 16 | for _, decl := range astFile.Decls { 17 | genDecl, ok := decl.(*ast.GenDecl) 18 | if !ok { 19 | continue 20 | } 21 | 22 | for _, spec := range genDecl.Specs { 23 | typeSpec, ok := spec.(*ast.TypeSpec) 24 | if !ok { 25 | continue 26 | } 27 | 28 | structName := typeSpec.Name.Name 29 | if typeName != structName { 30 | continue 31 | } 32 | 33 | structType, ok := typeSpec.Type.(*ast.StructType) 34 | if !ok { 35 | continue 36 | } 37 | 38 | return convertStructFieldsToConstructorOnes(structType.Fields.List), nil 39 | } 40 | } 41 | } 42 | 43 | return nil, fmt.Errorf("there is no suitable struct that matches given typeName [given=%s]", typeName) 44 | } 45 | 46 | // CollectInitFuncReturnTypes collects the return types of the init function. 47 | func CollectInitFuncReturnTypes(typeName string, initFuncName string, astFiles []*ast.File) ([]string, error) { 48 | for _, astFile := range astFiles { 49 | for _, decl := range astFile.Decls { 50 | funcDecl, ok := decl.(*ast.FuncDecl) 51 | if !ok { 52 | continue 53 | } 54 | 55 | if funcDecl.Name.Name != initFuncName { 56 | continue 57 | } 58 | 59 | if len(funcDecl.Recv.List) <= 0 { 60 | continue 61 | } 62 | 63 | if typ, ok := funcDecl.Recv.List[0].Type.(*ast.Ident); !ok || typ.Name != typeName { 64 | if typ, ok := funcDecl.Recv.List[0].Type.(*ast.StarExpr); !ok || typ.X.(*ast.Ident).Name != typeName { 65 | continue 66 | } 67 | } 68 | 69 | if funcDecl.Type.Results == nil { // no return values/types in the init function 70 | return []string{}, nil 71 | } 72 | 73 | resultTypes := funcDecl.Type.Results.List 74 | resultTypeNames := make([]string, len(resultTypes)) 75 | for i, resultType := range resultTypes { 76 | if ident, ok := resultType.Type.(*ast.Ident); ok { 77 | resultTypeNames[i] = ident.Name 78 | continue 79 | } 80 | 81 | resultTypeNames[i] = fmt.Sprintf("*%s", resultType.Type.(*ast.StarExpr).X.(*ast.Ident).Name) 82 | } 83 | return resultTypeNames, nil 84 | } 85 | } 86 | 87 | return nil, fmt.Errorf("there is no init function \"%s\" associated with type \"%s\"", initFuncName, typeName) 88 | } 89 | 90 | func convertStructFieldsToConstructorOnes(fields []*ast.Field) []*Field { 91 | fs := make([]*Field, len(fields)) 92 | for i, field := range fields { 93 | shouldIgnore := false 94 | if field.Tag != nil && len(field.Tag.Value) >= 1 { 95 | customTag := reflect.StructTag(field.Tag.Value[1 : len(field.Tag.Value)-1]) 96 | shouldIgnore = customTag.Get(gonstructorTag) == "-" 97 | } 98 | 99 | fieldType := types.ExprString(field.Type) 100 | 101 | var fieldName string 102 | if len(field.Names) > 0 { 103 | fieldName = field.Names[0].Name 104 | } else { 105 | // split 'mypackage.MyType' 106 | chunks := strings.Split(fieldType, ".") 107 | 108 | // it could be a pointer: '*mypackage.MyType' or '*MyType' 109 | fieldName = strings.TrimPrefix(chunks[len(chunks)-1], "*") 110 | } 111 | 112 | fs[i] = &Field{ 113 | FieldName: fieldName, 114 | FieldType: fieldType, 115 | ShouldIgnore: shouldIgnore, 116 | } 117 | } 118 | return fs 119 | } 120 | -------------------------------------------------------------------------------- /internal/constructor/builder_constructor_generator.go: -------------------------------------------------------------------------------- 1 | package constructor 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/iancoleman/strcase" 8 | g "github.com/moznion/gowrtr/generator" 9 | ) 10 | 11 | // BuilderGenerator is a struct type that has the responsibility to generate a statement of a builder. 12 | type BuilderGenerator struct { 13 | TypeName string 14 | Fields []*Field 15 | InitFunc string 16 | InitFuncReturnTypes []string 17 | PropagateInitFuncReturns bool 18 | ReturnValue bool 19 | SetterPrefix string 20 | } 21 | 22 | // Generate generates a builder statement. 23 | func (cg *BuilderGenerator) Generate(indentLevel int) g.Statement { 24 | builderConstructorName := fmt.Sprintf("New%sBuilder", strcase.ToCamel(cg.TypeName)) 25 | builderType := fmt.Sprintf("%sBuilder", strcase.ToCamel(cg.TypeName)) 26 | 27 | builderConstructorFunc := 28 | g.NewFunc( 29 | nil, 30 | g.NewFuncSignature(builderConstructorName).AddReturnTypes(fmt.Sprintf("*%s", builderType)), 31 | g.NewReturnStatement(fmt.Sprintf("&%s{}", builderType)), 32 | ) 33 | 34 | builderStruct := g.NewStruct(builderType) 35 | fieldRegistererFunctions := make([]*g.Func, 0) 36 | retStructureKeyValues := make([]string, 0) 37 | for _, field := range cg.Fields { 38 | if field.ShouldIgnore { 39 | continue 40 | } 41 | 42 | builderStruct = builderStruct.AddField( 43 | toLowerCamel(field.FieldName), 44 | field.FieldType, 45 | ) 46 | 47 | fieldRegistererFunctions = append(fieldRegistererFunctions, g.NewFunc( 48 | g.NewFuncReceiver("b", "*"+builderType), 49 | g.NewFuncSignature(withConditionalPrefix(strcase.ToCamel(field.FieldName), cg.SetterPrefix, cg.SetterPrefix != "")). 50 | AddParameters(g.NewFuncParameter(toLowerCamel(field.FieldName), field.FieldType)). 51 | AddReturnTypes("*"+builderType), 52 | g.NewRawStatement(fmt.Sprintf("b.%s = %s", toLowerCamel(field.FieldName), toLowerCamel(field.FieldName))), 53 | g.NewReturnStatement("b"), 54 | )) 55 | 56 | retStructureKeyValues = append(retStructureKeyValues, fmt.Sprintf("%s: b.%s", field.FieldName, toLowerCamel(field.FieldName))) 57 | } 58 | 59 | buildResult := generateStructure(cg.TypeName, retStructureKeyValues, indentLevel+1, cg.ReturnValue) 60 | 61 | var buildStmts []g.Statement 62 | if cg.InitFunc != "" { 63 | buildStmts = append(buildStmts, g.NewRawStatementf("r := %s", buildResult)) 64 | 65 | if cg.PropagateInitFuncReturns && len(cg.InitFuncReturnTypes) > 0 { 66 | initFuncReturnValues := make([]string, len(cg.InitFuncReturnTypes)) 67 | for i := 0; i < len(cg.InitFuncReturnTypes); i++ { 68 | initFuncReturnValues[i] = fmt.Sprintf("ret_%s%d", cg.InitFunc, i) 69 | } 70 | 71 | buildStmts = append(buildStmts, g.NewRawStatementf( 72 | "%s := r.%s()", 73 | strings.Join(initFuncReturnValues, ", "), 74 | cg.InitFunc, 75 | )) 76 | buildStmts = append(buildStmts, g.NewReturnStatement("r").AddReturnItems(initFuncReturnValues...)) 77 | } else { 78 | buildStmts = append(buildStmts, g.NewRawStatementf("r.%s()", cg.InitFunc)) 79 | buildStmts = append(buildStmts, g.NewReturnStatement("r")) 80 | } 81 | } else { 82 | buildStmts = append( 83 | buildStmts, 84 | g.NewReturnStatement(buildResult), 85 | ) 86 | } 87 | 88 | buildFunc := g.NewFunc( 89 | g.NewFuncReceiver("b", "*"+builderType), 90 | g.NewFuncSignature("Build").AddReturnTypes(withConditionalPrefix(cg.TypeName, "*", !cg.ReturnValue)).AddReturnTypes(func() []string { 91 | if len(cg.InitFuncReturnTypes) <= 0 { 92 | return []string{} 93 | } 94 | return cg.InitFuncReturnTypes 95 | }()...), 96 | buildStmts..., 97 | ) 98 | 99 | stmt := g.NewRoot(builderStruct, builderConstructorFunc) 100 | for _, f := range fieldRegistererFunctions { 101 | stmt = stmt.AddStatements(g.NewNewline(), f) 102 | } 103 | return stmt.AddStatements(g.NewNewline(), buildFunc) 104 | } 105 | -------------------------------------------------------------------------------- /internal/test/structure_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestStructureAllArgsConstructor(t *testing.T) { 11 | givenString := "givenstr" 12 | givenIOReader := strings.NewReader(givenString) 13 | givenChan := make(chan interface{}) 14 | 15 | got := NewStructure(givenString, givenIOReader, givenChan) 16 | assert.IsType(t, &Structure{}, got) 17 | 18 | assert.EqualValues(t, givenString, got.foo) 19 | assert.EqualValues(t, givenIOReader, got.bar) 20 | assert.EqualValues(t, givenChan, got.Buz) 21 | assert.EqualValues(t, nil, got.qux) 22 | 23 | // test for getters 24 | assert.EqualValues(t, givenString, got.GetFoo()) 25 | assert.EqualValues(t, givenIOReader, got.GetBar()) 26 | assert.EqualValues(t, givenChan, got.GetBuz()) 27 | assert.EqualValues(t, nil, got.GetQux()) 28 | } 29 | 30 | func TestStructureBuilder(t *testing.T) { 31 | givenString := "givenstr" 32 | givenIOReader := strings.NewReader(givenString) 33 | givenChan := make(chan interface{}) 34 | 35 | b := NewStructureBuilder() 36 | got := b.Foo(givenString). 37 | Bar(givenIOReader). 38 | Buz(givenChan). 39 | Build() 40 | assert.IsType(t, &Structure{}, got) 41 | 42 | assert.EqualValues(t, givenString, got.foo) 43 | assert.EqualValues(t, givenIOReader, got.bar) 44 | assert.EqualValues(t, givenChan, got.Buz) 45 | assert.EqualValues(t, nil, got.qux) 46 | } 47 | 48 | func TestChildStructureAllArgsConstructor(t *testing.T) { 49 | structure := NewStructure("givenstr", strings.NewReader("givenstr"), make(chan interface{})) 50 | givenString := "foobar" 51 | 52 | got := NewChildStructure(structure, givenString) 53 | assert.IsType(t, &ChildStructure{}, got) 54 | 55 | assert.EqualValues(t, structure, got.structure) 56 | assert.EqualValues(t, givenString, got.foobar) 57 | } 58 | 59 | func TestStructureWithInitAllArgsConstructor(t *testing.T) { 60 | givenString := "givenstr" 61 | 62 | got := NewStructureWithInit(givenString) 63 | assert.IsType(t, &StructureWithInit{}, got) 64 | 65 | assert.EqualValues(t, givenString, got.foo) 66 | assert.EqualValues(t, "ok", got.status) 67 | assert.EqualValues(t, nil, got.qux) 68 | 69 | // test for getters 70 | assert.EqualValues(t, givenString, got.GetFoo()) 71 | assert.EqualValues(t, "ok", got.GetStatus()) 72 | assert.EqualValues(t, nil, got.GetQux()) 73 | } 74 | 75 | func TestStructureWithInitBuilder(t *testing.T) { 76 | givenString := "givenstr" 77 | 78 | b := NewStructureWithInitBuilder() 79 | got := b.Foo(givenString). 80 | Build() 81 | assert.IsType(t, &StructureWithInit{}, got) 82 | 83 | assert.EqualValues(t, givenString, got.foo) 84 | assert.EqualValues(t, "ok", got.status) 85 | assert.EqualValues(t, nil, got.qux) 86 | } 87 | 88 | func TestStructureWithEmbeddingAllArgsConstructor(t *testing.T) { 89 | got := NewStructureWithEmbedding(Embedded{Bar: "bar"}, "foo") 90 | assert.IsType(t, &StructureWithEmbedding{}, got) 91 | 92 | assert.EqualValues(t, "foo", got.foo) 93 | assert.EqualValues(t, "bar", got.Bar) 94 | assert.EqualValues(t, "bar", got.Embedded.Bar) // nolint:staticcheck // for inspection 95 | 96 | // test for getters 97 | assert.EqualValues(t, "foo", got.GetFoo()) 98 | assert.EqualValues(t, "bar", got.GetEmbedded().Bar) 99 | } 100 | 101 | func TestStructureWithEmbeddingBuilder(t *testing.T) { 102 | b := NewStructureWithEmbeddingBuilder() 103 | got := b.Foo("foo").Embedded(Embedded{Bar: "bar"}).Build() 104 | assert.IsType(t, &StructureWithEmbedding{}, got) 105 | 106 | assert.EqualValues(t, "foo", got.foo) 107 | assert.EqualValues(t, "bar", got.Bar) 108 | assert.EqualValues(t, "bar", got.Embedded.Bar) // nolint:staticcheck // for inspection 109 | } 110 | 111 | func TestStructureWithPointerEmbeddingAllArgsConstructor(t *testing.T) { 112 | got := NewStructureWithPointerEmbedding(&Embedded{Bar: "bar"}, "foo") 113 | assert.IsType(t, &StructureWithPointerEmbedding{}, got) 114 | 115 | assert.EqualValues(t, "foo", got.foo) 116 | assert.EqualValues(t, "bar", got.Bar) 117 | assert.EqualValues(t, "bar", got.Embedded.Bar) // nolint:staticcheck // for inspection 118 | 119 | // test for getters 120 | assert.EqualValues(t, "foo", got.GetFoo()) 121 | assert.EqualValues(t, "bar", got.GetEmbedded().Bar) 122 | } 123 | 124 | func TestStructureWithPointerEmbeddingBuilder(t *testing.T) { 125 | b := NewStructureWithPointerEmbeddingBuilder() 126 | got := b.Foo("foo").Embedded(&Embedded{Bar: "bar"}).Build() 127 | assert.IsType(t, &StructureWithPointerEmbedding{}, got) 128 | 129 | assert.EqualValues(t, "foo", got.foo) 130 | assert.EqualValues(t, "bar", got.Bar) 131 | assert.EqualValues(t, "bar", got.Embedded.Bar) // nolint:staticcheck // for inspection 132 | } 133 | 134 | func TestStructureWithReturnValueBuilder(t *testing.T) { 135 | got := NewStructureValueBuilder().Foo("foo").Build() 136 | assert.IsType(t, StructureValue{}, got) 137 | } 138 | 139 | func TestStructureWithReturnValueAllArgs(t *testing.T) { 140 | got := NewStructureValue("foo") 141 | assert.IsType(t, StructureValue{}, got) 142 | } 143 | 144 | func TestStructureWithSetterPrefix(t *testing.T) { 145 | b := NewStructureWithSetterPrefixBuilder() 146 | got := b.WithFoo("foo"). 147 | WithBar(123). 148 | Build() 149 | assert.IsType(t, &StructureWithSetterPrefix{}, got) 150 | 151 | assert.EqualValues(t, "foo", got.foo) 152 | assert.EqualValues(t, 123, got.bar) 153 | } 154 | -------------------------------------------------------------------------------- /cmd/gonstructor/gonstructor.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "github.com/iancoleman/strcase" 12 | "github.com/moznion/gonstructor/internal" 13 | "github.com/moznion/gonstructor/internal/constructor" 14 | g "github.com/moznion/gowrtr/generator" 15 | ) 16 | 17 | const ( 18 | allArgsConstructorType = "allArgs" 19 | builderConstructorType = "builder" 20 | ) 21 | 22 | type stringArrayFlag []string 23 | 24 | func (a *stringArrayFlag) String() string { 25 | return strings.Join(*a, ", ") 26 | } 27 | 28 | func (a *stringArrayFlag) Set(item string) error { 29 | *a = append(*a, item) 30 | return nil 31 | } 32 | 33 | func main() { 34 | var typeNames stringArrayFlag 35 | flag.Var(&typeNames, "type", `[mandatory] A type name. It accepts this option occurs multiple times to output the generated code of the multi types into a single file. If this option is given multiple times, the "-output" option becomes mandatory.`) 36 | output := flag.String("output", "", `[optional] Output file name (default "srcdir/_gen.go"). See also "-type" option's description.'`) 37 | constructorTypesStr := flag.String("constructorTypes", allArgsConstructorType, fmt.Sprintf(`[optional] comma-separated list of constructor types; it expects "%s" and "%s"`, allArgsConstructorType, builderConstructorType)) 38 | shouldShowVersion := flag.Bool("version", false, "[optional] show the version information") 39 | withGetter := flag.Bool("withGetter", false, "[optional] generate a constructor along with getter functions for each field") 40 | initFunc := flag.String("init", "", "[optional] name of function to call on object after creating it") 41 | propagateInitFuncReturns := flag.Bool("propagateInitFuncReturns", false, `[optional] If this option is specified, the generated constructor propagates the return values that come from the init function specified by the "-init" option, e.g. when the init function returns an "error" value, the generated constructor returns (*YourStructType, error). Known issue: If this option is used with the multiple --type options, probably it won't be the expected result.`) 42 | returnValue := flag.Bool("returnValue", false, "[optional] return \"value\" instead of pointer") 43 | setterPrefix := flag.String("setterPrefix", "", "[optional] prefix for setter methods in builder pattern (e.g., 'With' generates WithFoo instead of Foo)") 44 | 45 | flag.Parse() 46 | 47 | if *shouldShowVersion { 48 | internal.ShowVersion() 49 | return 50 | } 51 | 52 | if len(typeNames) <= 0 { 53 | flag.Usage() 54 | os.Exit(2) 55 | } 56 | if len(typeNames) >= 2 && *output == "" { 57 | _, _ = fmt.Fprintln(os.Stderr, `[error] if "-type" option appears two or more times, the "-output" option must be specified.`) 58 | flag.Usage() 59 | os.Exit(2) 60 | } 61 | 62 | constructorTypes, err := getConstructorTypes(*constructorTypesStr) 63 | if err != nil { 64 | log.Printf("[error] %s", err) 65 | flag.Usage() 66 | os.Exit(2) 67 | } 68 | 69 | args := flag.Args() 70 | if len(args) <= 0 { 71 | args = []string{"."} 72 | } 73 | 74 | pkg, err := internal.ParsePackage(args) 75 | if err != nil { 76 | log.Fatal(fmt.Errorf("[error] failed to parse a package: %w", err)) 77 | } 78 | 79 | astFiles, err := internal.ParseFiles(pkg.GoFiles) 80 | if err != nil { 81 | log.Fatal(fmt.Errorf("[error] failed to parse a file: %w", err)) 82 | } 83 | 84 | alreadyOpenedWithTruncFilesSet := map[string]bool{} 85 | fileHeaderGenerated := false 86 | rootStmt := g.NewRoot() 87 | for i, typeName := range typeNames { 88 | fields, err := constructor.CollectConstructorFieldsFromAST(typeName, astFiles) 89 | if err != nil { 90 | log.Fatal(fmt.Errorf("[error] failed to collect fields from files: %w", err)) 91 | } 92 | 93 | var initFuncReturnTypes []string 94 | if *initFunc != "" { 95 | initFuncReturnTypes, err = constructor.CollectInitFuncReturnTypes(typeName, *initFunc, astFiles) 96 | if err != nil { 97 | log.Fatal(fmt.Errorf("[error] failed to collect the return types of the init func: %w", err)) 98 | } 99 | } 100 | 101 | if !fileHeaderGenerated { 102 | rootStmt = rootStmt.AddStatements( 103 | g.NewComment(fmt.Sprintf(" Code generated by gonstructor %s; DO NOT EDIT.", strings.Join(os.Args[1:], " "))), 104 | g.NewNewline(), 105 | g.NewPackage(pkg.Name), 106 | g.NewNewline(), 107 | ) 108 | } 109 | fileHeaderGenerated = true 110 | 111 | for _, constructorType := range constructorTypes { 112 | var constructorGenerator constructor.Generator 113 | switch constructorType { 114 | case allArgsConstructorType: 115 | constructorGenerator = &constructor.AllArgsConstructorGenerator{ 116 | TypeName: typeName, 117 | Fields: fields, 118 | InitFunc: *initFunc, 119 | InitFuncReturnTypes: initFuncReturnTypes, 120 | PropagateInitFuncReturns: *propagateInitFuncReturns, 121 | ReturnValue: *returnValue, 122 | } 123 | 124 | case builderConstructorType: 125 | constructorGenerator = &constructor.BuilderGenerator{ 126 | TypeName: typeName, 127 | Fields: fields, 128 | InitFunc: *initFunc, 129 | InitFuncReturnTypes: initFuncReturnTypes, 130 | PropagateInitFuncReturns: *propagateInitFuncReturns, 131 | ReturnValue: *returnValue, 132 | SetterPrefix: *setterPrefix, 133 | } 134 | 135 | default: 136 | // unreachable, just in case 137 | log.Fatalf("[error] unexpected constructor type has come [given=%s]", constructorType) 138 | } 139 | 140 | rootStmt = rootStmt.AddStatements(constructorGenerator.Generate(0)) 141 | } 142 | 143 | if *withGetter { 144 | rootStmt = rootStmt.AddStatements(internal.GenerateGetters(typeName, fields)) 145 | } 146 | 147 | if i != len(typeNames)-1 { // insert a newline *between* the generated type-code (i.e. don't append a trailing newline) 148 | rootStmt = rootStmt.AddStatements(g.NewNewline()) 149 | } 150 | } 151 | 152 | code, err := rootStmt.Goimports().EnableSyntaxChecking().Generate(0) 153 | if err != nil { 154 | log.Fatal(fmt.Errorf("[error] failed to generate code: %w", err)) 155 | } 156 | 157 | err = func() error { 158 | fileName := getFilenameToGenerate(typeNames[0], *output, args) 159 | 160 | fileOpenFlag := os.O_APPEND | os.O_WRONLY | os.O_CREATE 161 | if !alreadyOpenedWithTruncFilesSet[fileName] { 162 | // truncate and make a blank file 163 | fileOpenFlag = os.O_TRUNC | os.O_WRONLY | os.O_CREATE 164 | } 165 | 166 | f, err := os.OpenFile(fileName, fileOpenFlag, 0644) 167 | if err != nil { 168 | return fmt.Errorf("[error] failed open a file to output the generated code: %w", err) 169 | } 170 | 171 | defer func() { 172 | _ = f.Close() 173 | }() 174 | 175 | alreadyOpenedWithTruncFilesSet[fileName] = true 176 | 177 | _, err = f.WriteString(code) 178 | if err != nil { 179 | return fmt.Errorf("[error] failed output generated code to a file: %w", err) 180 | } 181 | 182 | return nil 183 | }() 184 | if err != nil { 185 | log.Fatal(err) 186 | } 187 | } 188 | 189 | func getConstructorTypes(constructorTypes string) ([]string, error) { 190 | typs := strings.Split(constructorTypes, ",") 191 | for _, typ := range typs { 192 | if typ != allArgsConstructorType && typ != builderConstructorType { 193 | return nil, fmt.Errorf("unexpected constructor type has come [given=%s]", typ) 194 | } 195 | } 196 | return typs, nil 197 | } 198 | 199 | func isDirectory(name string) bool { 200 | info, err := os.Stat(name) 201 | if err != nil { 202 | log.Fatal(err) 203 | } 204 | return info.IsDir() 205 | } 206 | 207 | func getFilenameToGenerate(typeName string, output string, args []string) string { 208 | if output != "" { 209 | return output 210 | } 211 | 212 | var dir string 213 | if len(args) == 1 && isDirectory(args[0]) { 214 | dir = args[0] 215 | } else { 216 | dir = filepath.Dir(args[0]) 217 | } 218 | return fmt.Sprintf("%s/%s_gen.go", dir, strcase.ToSnake(typeName)) 219 | } 220 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gonstructor 2 | 3 | A command-line tool to generate a constructor for the struct. 4 | 5 | ## Installation 6 | 7 | ``` 8 | $ go install github.com/moznion/gonstructor/cmd/gonstructor@latest 9 | ``` 10 | 11 | Also, you can get the pre-built binaries on [Releases](https://github.com/moznion/gonstructor/releases). 12 | 13 | Or get it with [gobinaries.com](https://gobinaries.com): 14 | 15 | ```bash 16 | curl -sf https://gobinaries.com/moznion/gonstructor | sh 17 | ``` 18 | 19 | ## Dependencies 20 | 21 | gonstructor depends on [goimports](https://pkg.go.dev/golang.org/x/tools/cmd/goimports) for fixing import paths and formatting code, you need to install it: 22 | 23 | ``` 24 | $ go install golang.org/x/tools/cmd/goimports@latest 25 | ``` 26 | 27 | ## Usage 28 | 29 | ``` 30 | Usage of gonstructor: 31 | -constructorTypes string 32 | [optional] comma-separated list of constructor types; it expects "allArgs" and "builder" (default "allArgs") 33 | -init string 34 | [optional] name of function to call on object after creating it 35 | -output string 36 | [optional] Output file name (default "srcdir/_gen.go"). See also "-type" option's description.' 37 | -propagateInitFuncReturns 38 | [optional] If this option is specified, the generated constructor propagates the return values that come from the init function specified by the "-init" option, e.g. when the init function returns an "error" value, the generated constructor returns (*YourStructType, error). Known issue: If this option is used with the multiple --type options, probably it won't be the expected result. 39 | -returnValue 40 | [optional] return "value" instead of pointer 41 | -setterPrefix string 42 | [optional] prefix for setter methods in builder pattern (e.g., 'With' generates WithFoo instead of Foo) 43 | -type value 44 | [mandatory] A type name. It accepts this option occurs multiple times to output the generated code of the multi types into a single file. If this option is given multiple times, the "-output" option becomes mandatory. 45 | -version 46 | [optional] show the version information 47 | -withGetter 48 | [optional] generate a constructor along with getter functions for each field 49 | ``` 50 | 51 | ## Motivation 52 | 53 | Data encapsulation is a good practice to make software, and it is necessary to clearly indicate the boundary of the structure by controlling the accessibility of the data fields (i.e. private or public) for that. Basically keeping the data fields be private and immutable would be good to make software be robust because it can avoid unexpected field changing. 54 | 55 | Golang has a simple way to do that by choosing the initial character's type: upper case or lower case. Once it has decided to use a field as private, it needs to make something like a constructor function, but golang doesn't have a mechanism to support constructor now. 56 | 57 | Therefore this project aims to automatically generate constructors to use structures with private and immutable, easily. 58 | 59 | ## Pre requirements to run 60 | 61 | - [goimports](https://godoc.org/golang.org/x/tools/cmd/goimports) 62 | 63 | ## Synopsis 64 | 65 | ### Generate all args constructor 66 | 67 | 1. write a struct type with `go:generate` 68 | 69 | e.g. 70 | 71 | ```go 72 | //go:generate gonstructor --type=Structure --constructorTypes=allArgs 73 | type Structure struct { 74 | foo string 75 | bar io.Reader 76 | Buz chan interface{} 77 | } 78 | ``` 79 | 80 | 2. execute `go generate ./...` 81 | 3. then `gonstructor` generates a constructor code 82 | 83 | e.g. 84 | 85 | ```go 86 | func NewStructure(foo string, bar io.Reader, buz chan interface{}) *Structure { 87 | return &Structure{foo: foo, bar: bar, Buz: buz} 88 | } 89 | ``` 90 | 91 | ### Generate builder (builder means GoF pattern's one) 92 | 93 | 1. write a struct type with `go:generate` 94 | 95 | e.g. 96 | 97 | ```go 98 | //go:generate gonstructor --type=Structure --constructorTypes=builder 99 | type Structure struct { 100 | foo string 101 | bar io.Reader 102 | Buz chan interface{} 103 | } 104 | ``` 105 | 106 | 2. execute `go generate ./...` 107 | 3. then `gonstructor` generates a buildr code 108 | 109 | e.g. 110 | 111 | ```go 112 | type StructureBuilder struct { 113 | foo string 114 | bar io.Reader 115 | buz chan interface{} 116 | bufferSize int 117 | } 118 | 119 | func NewStructureBuilder() *StructureBuilder { 120 | return &StructureBuilder{} 121 | } 122 | 123 | func (b *StructureBuilder) Foo(foo string) *StructureBuilder { 124 | b.foo = foo 125 | return b 126 | } 127 | 128 | func (b *StructureBuilder) Bar(bar io.Reader) *StructureBuilder { 129 | b.bar = bar 130 | return b 131 | } 132 | 133 | func (b *StructureBuilder) Buz(buz chan interface{}) *StructureBuilder { 134 | b.buz = buz 135 | return b 136 | } 137 | 138 | func (b *StructureBuilder) BufferSize(bufferSize int) *StructureBuilder { 139 | b.bufferSize = bufferSize 140 | return b 141 | } 142 | 143 | func (b *StructureBuilder) Build() *Structure { 144 | return &Structure{ 145 | foo: b.foo, 146 | bar: b.bar, 147 | Buz: b.buz, 148 | bufferSize: b.bufferSize, 149 | } 150 | } 151 | ``` 152 | 153 | ### Call a initializer 154 | 155 | 1. write a struct type with `go:generate` 156 | 2. write a function that initializes internal fields 157 | 3. pass its name to `-init` parameter 158 | 159 | e.g. 160 | 161 | ```go 162 | //go:generate gonstructor --type=Structure -init construct 163 | type Structure struct { 164 | foo string 165 | bar io.Reader 166 | Buz chan interface{} 167 | bufferSize int 168 | buffer chan []byte `gonstructor:"-"` 169 | } 170 | 171 | func (structure *Structure) construct() { 172 | structure.buffer = make(chan []byte, structure.bufferSize) 173 | } 174 | ``` 175 | 176 | 2. execute `go generate ./...` 177 | 3. then `gonstructor` generates a buildr code 178 | 179 | e.g. 180 | 181 | ```go 182 | func NewStructure( 183 | foo string, 184 | bar io.Reader, 185 | buz chan interface{}, 186 | bufferSize int, 187 | ) *Structure { 188 | r := &Structure{ 189 | foo: foo, 190 | bar: bar, 191 | Buz: buz, 192 | bufferSize: bufferSize, 193 | } 194 | 195 | r.construct() 196 | 197 | return r 198 | } 199 | ``` 200 | 201 | ## How to ignore to contain a field in a constructor 202 | 203 | `gonstructor:"-"` supports that. 204 | 205 | e.g. 206 | 207 | ```go 208 | type Structure struct { 209 | foo string 210 | bar int64 `gonstructor:"-"` 211 | } 212 | ``` 213 | 214 | The generated code according to the above structure doesn't contain `bar` field. 215 | 216 | ## How to output the generated code of each type into a single file 217 | 218 | This CLI tool can have the `--type` option multiple times, and it must have also `--output` option. 219 | 220 | example: 221 | 222 | ``` 223 | //go:generate gonstructor --type=AlphaStructure --type=BravoStructure --constructorTypes=allArgs,builder --withGetter --output=./alpha_and_bravo_gen.go" 224 | ``` 225 | 226 | ## How to propagate the returned values come from `-init` func 227 | 228 | `-propagateInitFuncReturns` option supports that. 229 | 230 | For example, 231 | 232 | ```go 233 | //go:generate gonstructor --type=Struct --constructorTypes=allArgs,builder --init validate --propagateInitFuncReturns 234 | type Struct struct { 235 | foo string 236 | } 237 | 238 | func (s *Struct) validate() error { 239 | // do something with the created `Struct` value. 240 | return err 241 | } 242 | ``` 243 | 244 | then it generates the following Go code: 245 | 246 | ```go 247 | func NewStruct(foo string) (*Struct, error) { 248 | r := &Struct{ 249 | foo: foo, 250 | } 251 | 252 | ret_validate0 := r.validate() 253 | 254 | return r, ret_validate0 255 | } 256 | ``` 257 | 258 | As you can see, the generated constructor `NewStruct()` returns the constructed value and an error that comes from `validate()` function. 259 | 260 | ## How to return a value instead of a pointer 261 | 262 | `-returnValue` option supports that. 263 | 264 | For example, 265 | 266 | ```go 267 | //go:generate gonstructor --type=Struct --constructorTypes=allArgs --returnValue 268 | type Struct struct { 269 | foo string 270 | } 271 | ``` 272 | 273 | then it generates the following Go code: 274 | 275 | ```go 276 | func NewStruct(foo string) Struct { 277 | return Struct{foo: foo} 278 | } 279 | ``` 280 | 281 | ## How to build binaries 282 | 283 | Binaries are built and uploaded by [goreleaser](https://goreleaser.com/). Please refer to the configuration file: [.goreleaser.yml](./.goreleaser.yml) 284 | 285 | ## Author 286 | 287 | moznion () 288 | --------------------------------------------------------------------------------