├── .circleci └── config.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── command ├── generate.go ├── root.go └── setup.go ├── constructor.tpl ├── constructor.yaml ├── file └── file.go ├── generator ├── constructor.constructor.go ├── constructor.go ├── constructor_test.go ├── source_code_reader.go ├── source_code_reader_interface_mock_test.go ├── template.go ├── template_reader.go ├── template_reader_mock_test.go ├── writer.go └── writer_mock_test.go ├── go.mod ├── go.sum ├── main.go ├── reader ├── ast_parser.go ├── ast_structure_convert.go ├── code.constructor.go ├── code.go ├── code_test.go ├── sort.go ├── template.go ├── template_test.go └── testdata │ ├── struct.go │ └── struct_for_multiple_type.go └── structure ├── alias.go ├── code.constructor.go ├── code.go ├── const.go └── reserved_words.go /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | test: 4 | docker: 5 | - image: circleci/golang:1.12.5 6 | working_directory: /go/src/github.com/bannzai/constructor 7 | steps: 8 | - checkout 9 | - run: 10 | name: install-dependency 11 | command: | 12 | export GO111MODULE=on 13 | make dependency 14 | - run: make test-verbose 15 | 16 | 17 | workflows: 18 | version: 2 19 | test: 20 | jobs: 21 | - test 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | vendor 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 hirose yudai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT?=constructor 2 | PACKAGE?=github.com/$(PROJECT) 3 | 4 | install: 5 | go install 6 | 7 | test: 8 | go test ./... 9 | 10 | test-verbose: 11 | go test ./... -v 12 | 13 | setup: 14 | constructor setup 15 | 16 | dry-run: install 17 | constructor generate --source structure/code.go --destination structure/code.constructor.go --package structure 18 | go generate ./... 19 | 20 | delete: 21 | rm -f ./constructor.yaml ./constructor.tpl 22 | rm -f ./structure/constructor.go 23 | 24 | reset: delete setup 25 | 26 | dependency: 27 | go mod vendor 28 | 29 | update-dependency: 30 | go mod tidy 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # constructor 2 | `constructor` is generated [Constructor](https://golang.org/doc/effective_go.html#composite_literals) for struct. 3 | 4 | Example result. 5 | ```go 6 | type Foo struct { 7 | Bar string 8 | } 9 | 10 | func NewFoo(bar string) Foo { 11 | return Foo{ 12 | Bar: bar, 13 | } 14 | } 15 | ``` 16 | 17 | ## Install 18 | Getting `constructor` via `go get`. 19 | 20 | ```shell 21 | $ go get -u github.com/bannzai/constructor 22 | ``` 23 | 24 | ## Usage 25 | First, You can prepare configuration files when exec `constructor setup`. After created `constructor.tpl`. 26 | `constructor` necessary these configuration files. 27 | 28 | ```shell 29 | $ constructor setup 30 | ``` 31 | 32 | Next, Execute `constructor generate`, after you edited configuration files. 33 | And you confirm diff between before and after executing it. You found about constructor functions `.go` file. 34 | 35 | ```shell 36 | $ constructor generate --source=structure/code.go --destination=structure/code.constructor.go --package=structure 37 | ``` 38 | 39 |
40 | 41 | $ cat structure/code.constructor.go 42 | 43 | ```go 44 | // DO NOT EDIT THIS FILE. 45 | // File generated by constructor. 46 | // https://github.com/bannzai/constructor 47 | package structure 48 | 49 | // NewCodeStructure insitanciate Code 50 | func NewCodeStructure( 51 | filePath Path, 52 | structs []Struct, 53 | ) Code { 54 | return Code{ 55 | FilePath: filePath, 56 | Structs: structs, 57 | } 58 | } 59 | 60 | // NewFieldStructure insitanciate Field 61 | func NewFieldStructure( 62 | name string, 63 | _type string, 64 | ) Field { 65 | return Field{ 66 | Name: name, 67 | Type: _type, 68 | } 69 | } 70 | 71 | // NewStructStructure insitanciate Struct 72 | func NewStructStructure( 73 | fields []Field, 74 | name string, 75 | ) Struct { 76 | return Struct{ 77 | Fields: fields, 78 | Name: name, 79 | } 80 | } 81 | ``` 82 | 83 |
84 | 85 | ## Help 86 | 87 | #### constructor --help 88 | ```shell 89 | $ constructor --help  90 | 91 | This application is a tool to generate constructor functions for each struct quickly. 92 | When you execute "constructor generate [flags]", 93 | It is generating constructor functions under the package. 94 | You get "./constructor.tpl" via to execute "constructor setup". 95 | This is default template for [constructor]. 96 | You can edit this template, If you customize generated files and pass it. 97 | 98 | Usage: 99 | construtor [flags] 100 | construtor [command] 101 | 102 | Available Commands: 103 | generate generate constructor functions 104 | help Help about any command 105 | setup setup will create ./constructor.yaml 106 | 107 | Flags: 108 | -h, --help help for construtor 109 | 110 | Use "construtor [command] --help" for more information about a command. 111 | ``` 112 | 113 | #### constructor generate --help 114 | ```shell 115 | $ constructor generate --help  116 | constructor can be add constructor functions for each go struct. 117 | 118 | Usage: 119 | construtor generate [flags] 120 | 121 | Flags: 122 | --destination string Destination go file path 123 | -h, --help help for generate 124 | --ignoreFields string Not contains generated fields. It is list with commas. (e.g id,name,age 125 | --package string Package name for generated constructor. 126 | --source string Source go file path 127 | --template string Constructor functions format template file path. Default is ./constructor.tpl (default "constructor.tpl") 128 | --type string Specify struct about generated constructor function. 129 | 130 | ``` 131 | 132 | ## go:generate 133 | **constructor** recommended to use `go:generate`. 134 | For example. 135 | 136 | ``` 137 | //go:generate constructor generate --source=$GOFILE --destination=$GOPATH/src/github.com/bannzai/constructor/generator/constructor.constructor.go --package=$GOPACKAGE --template=$GOPATH/src/github.com/bannzai/constructor/constructor.tpl --type=Constructor --ignoreFields=TemplateReader,SourceCodeReader 138 | ``` 139 | 140 | ## Customize template 141 | It is possible to use customize template. 142 | Two ways for preparing template files. 143 | 144 | 1. Edit `constructor.tpl` 145 | 2. Create new `[YOUR_CUSTOMIZE_TEMPLATE].tpl` 146 | 147 | And, It can be passed to `constructor generate [REQUIRED_FLAGS] --template=[YOUR_CUSTOMIZE_TEMPLATE].tpl`. 148 | 149 | ## LICENSE 150 | **constructor** is available under the MIT license. See the LICENSE file for more info. 151 | -------------------------------------------------------------------------------- /command/generate.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package command 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/bannzai/constructor/generator" 22 | "github.com/bannzai/constructor/reader" 23 | "github.com/bannzai/constructor/structure" 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | type GenerateOptions struct { 28 | sourceFilePath string 29 | destinationFilePath string 30 | templateFilePath string 31 | structType string 32 | ignoreFields string 33 | packageName string 34 | } 35 | 36 | var generateOptions = GenerateOptions{} 37 | 38 | // generateCmd represents the generate command 39 | var generateCmd = &cobra.Command{ 40 | Use: "generate", 41 | Short: "generate constructor functions", 42 | Long: `constructor can be add constructor functions for each go struct. `, 43 | Run: func(command *cobra.Command, args []string) { 44 | generate() 45 | }, 46 | } 47 | 48 | func generate() { 49 | ignoreFieldNames := []string{} 50 | if len(generateOptions.ignoreFields) > 0 { 51 | for _, splited := range strings.Split(generateOptions.ignoreFields, ",") { 52 | ignoreFieldNames = append(ignoreFieldNames, strings.Trim(splited, " ")) 53 | } 54 | } 55 | 56 | generator.Constructor{ 57 | TemplateReader: reader.Template{}, 58 | SourceCodeReader: reader.Code{}, 59 | FileWriter: generator.FileWriterImpl{}, 60 | }.Generate( 61 | generateOptions.sourceFilePath, 62 | generateOptions.destinationFilePath, 63 | generateOptions.templateFilePath, 64 | generateOptions.structType, 65 | ignoreFieldNames, 66 | generateOptions.packageName, 67 | ) 68 | } 69 | 70 | func init() { 71 | rootCmd.AddCommand(generateCmd) 72 | generateCmd.Flags().StringVar(&generateOptions.sourceFilePath, "source", "", "Source go file path") 73 | generateCmd.Flags().StringVar(&generateOptions.destinationFilePath, "destination", "", "Destination go file path") 74 | generateCmd.Flags().StringVar(&generateOptions.templateFilePath, "template", structure.TemplateFileName, fmt.Sprintf("Constructor functions format template file path. Default is ./%s", structure.TemplateFileName)) 75 | generateCmd.Flags().StringVar(&generateOptions.structType, "type", "", "Specify struct about generated constructor function.") 76 | generateCmd.Flags().StringVar(&generateOptions.ignoreFields, "ignoreFields", "", "Not contains generated fields. It is list with commas. (e.g id,name,age") 77 | generateCmd.Flags().StringVar(&generateOptions.packageName, "package", "", "Package name for generated constructor.") 78 | requiredFlags := []string{"source", "destination", "package"} 79 | for _, name := range requiredFlags { 80 | if err := generateCmd.MarkFlagRequired(name); err != nil { 81 | panic(err) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /command/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package command 16 | 17 | import ( 18 | "os" 19 | 20 | "github.com/spf13/cobra" 21 | ) 22 | 23 | // rootCmd represents the root command 24 | var rootCmd = &cobra.Command{ 25 | Use: "construtor", 26 | Short: "[constructor] can be generated constructor function for each struct", 27 | Long: ` 28 | [constructor] generate constructor functions for each struct. 29 | When you execute "constructor generate [flags]", 30 | get constructor functions under the package. 31 | You got "./constructor.tpl" after execute "constructor setup". 32 | This is default template for [constructor]. 33 | You can edit this template, If you customize generated files and pass it. 34 | `, 35 | Args: cobra.MinimumNArgs(1), 36 | Run: func(command *cobra.Command, args []string) { 37 | command.Help() 38 | }, 39 | } 40 | 41 | func Execute() { 42 | if err := rootCmd.Execute(); err != nil { 43 | os.Exit(1) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /command/setup.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2019 NAME HERE 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package command 16 | 17 | import ( 18 | "github.com/bannzai/constructor/generator" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | // setupCmd represents the setup command 23 | var setupCmd = &cobra.Command{ 24 | Use: "setup", 25 | Short: "setup will create ./constructor.yaml", 26 | Long: ` setup will create ./constructor.yaml. 27 | constructor.yaml is configuration file for "constructor generate". 28 | `, 29 | Run: func(command *cobra.Command, args []string) { 30 | generator.Template{}.Setup() 31 | }, 32 | } 33 | 34 | func init() { 35 | rootCmd.AddCommand(setupCmd) 36 | } 37 | -------------------------------------------------------------------------------- /constructor.tpl: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE. 2 | // File generated by constructor. 3 | // https://github.com/bannzai/constructor 4 | 5 | {{- $dot := . }} 6 | package {{.Package}} 7 | 8 | {{range $i, $struct := .Structs -}} 9 | {{- $suffix := upperCamelCase $dot.Package -}} 10 | {{- $structureName := .Name -}} 11 | // New{{$structureName}}{{$suffix}} insitanciate {{.Name}} 12 | func New{{$structureName}}{{$suffix}}( 13 | {{range $i, $field := .Fields -}} 14 | {{parameterCase $field.Name}} {{$field.Type}}, 15 | {{end -}} 16 | ) {{$structureName}} { 17 | return {{$structureName}}{ 18 | {{range $i, $field := .Fields -}} 19 | {{$field.Name}}: {{argumentCase $field.Name}}, 20 | {{end -}} 21 | } 22 | } 23 | {{end}} 24 | -------------------------------------------------------------------------------- /constructor.yaml: -------------------------------------------------------------------------------- 1 | definitions: 2 | - package: "structure" 3 | sourcePath: "structure/*.go" 4 | ignoredPaths: ["structure/argument.go"] 5 | templatePaths: ["constructor.tpl"] 6 | destinationPath: "structure/constructor.go" 7 | -------------------------------------------------------------------------------- /file/file.go: -------------------------------------------------------------------------------- 1 | package file 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "os" 7 | 8 | "golang.org/x/tools/imports" 9 | ) 10 | 11 | func FileExists(fileName string) bool { 12 | file, err := os.Stat(fileName) 13 | if os.IsNotExist(err) { 14 | return false 15 | } 16 | return !file.IsDir() 17 | } 18 | 19 | func WriteFile(destinationPath string, content string) { 20 | if err := ioutil.WriteFile(destinationPath, []byte(content), 0644); err != nil { 21 | panic(err) 22 | } 23 | fmt.Fprintf(os.Stdout, "Generated %s. \n", destinationPath) 24 | } 25 | 26 | func GoImports(path string) { 27 | // reference: https://github.com/golang/tools/blob/master/cmd/goimports/goimports.go#L41 28 | options := &imports.Options{ 29 | TabWidth: 8, 30 | TabIndent: true, 31 | Comments: true, 32 | Fragment: true, 33 | FormatOnly: false, 34 | } 35 | f, err := os.Open(path) 36 | if err != nil { 37 | panic(err) 38 | } 39 | defer f.Close() 40 | src, err := ioutil.ReadAll(f) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | res, err := imports.Process(path, src, options) 46 | if err != nil { 47 | panic(err) 48 | } 49 | WriteFile(path, string(res)) 50 | } 51 | -------------------------------------------------------------------------------- /generator/constructor.constructor.go: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE. 2 | // File generated by constructor. 3 | // https://github.com/bannzai/constructor 4 | package generator 5 | 6 | // NewConstructorGenerator insitanciate Constructor 7 | func NewConstructorGenerator( 8 | fileWriter FileWriter, 9 | ) Constructor { 10 | return Constructor{ 11 | FileWriter: fileWriter, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /generator/constructor.go: -------------------------------------------------------------------------------- 1 | //go:generate constructor generate --source=$GOFILE --destination=$GOPATH/src/github.com/bannzai/constructor/generator/constructor.constructor.go --package=$GOPACKAGE --template=$GOPATH/src/github.com/bannzai/constructor/constructor.tpl --type=Constructor --ignoreFields=TemplateReader,SourceCodeReader 2 | package generator 3 | 4 | import ( 5 | "bytes" 6 | 7 | "github.com/bannzai/constructor/structure" 8 | ) 9 | 10 | type Constructor struct { 11 | TemplateReader 12 | SourceCodeReader 13 | FileWriter 14 | } 15 | 16 | func (generator Constructor) Generate(sourcePath, destinationPath, templatePath string, typeName string, ignoreFieldNames []string, packageName string) { 17 | templateExecutor := generator.TemplateReader.Read(templatePath) 18 | var sourceCode structure.Code 19 | if len(typeName) > 0 { 20 | sourceCode = generator.SourceCodeReader.ReadWithType(sourcePath, typeName, ignoreFieldNames) 21 | } else { 22 | sourceCode = generator.SourceCodeReader.Read(sourcePath, ignoreFieldNames) 23 | } 24 | 25 | buf := &bytes.Buffer{} 26 | if err := templateExecutor.Execute(buf, map[string]interface{}{ 27 | "Structs": sourceCode.Structs, 28 | "Package": packageName, 29 | }); err != nil { 30 | panic(err) 31 | } 32 | 33 | content := buf.String() 34 | generator.FileWriter.Write(destinationPath, content) 35 | } 36 | -------------------------------------------------------------------------------- /generator/constructor_test.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | template "html/template" 5 | "testing" 6 | 7 | structure "github.com/bannzai/constructor/structure" 8 | "github.com/golang/mock/gomock" 9 | ) 10 | 11 | const testTemplate = "package {{.Package}}\n" + 12 | "\n" + 13 | "{{range $i, $struct := .Structs -}}\n" + 14 | "struct {{$struct.Name}} {\n" + 15 | " {{range $i, $field := $struct.Fields -}}\n" + 16 | " {{$field.Name}} {{$field.Type -}}\n" + 17 | " {{end}}\n" + 18 | "}\n" + 19 | "{{end -}}\n" 20 | 21 | func TestConstructor_Generate(t *testing.T) { 22 | ctrl := gomock.NewController(t) 23 | type fields struct { 24 | TemplateReader 25 | SourceCodeReader 26 | FileWriter 27 | } 28 | type args struct { 29 | sourcePath string 30 | destinationPath string 31 | templatePath string 32 | typeName string 33 | ignoreFieldNames []string 34 | packageName string 35 | } 36 | tests := []struct { 37 | name string 38 | fields fields 39 | args 40 | }{ 41 | { 42 | name: "Successfully generate constructor function when not specify type name", 43 | fields: fields{ 44 | TemplateReader: func() TemplateReader { 45 | mock := NewTemplateReaderMock(ctrl) 46 | mock.EXPECT().Read("template.tpl").Return( 47 | template.Must(template.New("template.tpl").Parse(testTemplate)), 48 | ) 49 | return mock 50 | }(), 51 | SourceCodeReader: func() SourceCodeReader { 52 | mock := NewMockSourceCodeReader(ctrl) 53 | mock.EXPECT().Read("source_code.go", []string{}).Return( 54 | structure.Code{ 55 | FilePath: "source_code.go", 56 | Structs: []structure.Struct{ 57 | structure.Struct{ 58 | Name: "X", 59 | Fields: []structure.Field{ 60 | structure.Field{ 61 | Name: "Field", 62 | Type: "int", 63 | }, 64 | }, 65 | }, 66 | structure.Struct{ 67 | Name: "Y", 68 | Fields: []structure.Field{ 69 | structure.Field{ 70 | Name: "Field", 71 | Type: "string", 72 | }, 73 | }, 74 | }, 75 | }, 76 | }, 77 | ) 78 | return mock 79 | }(), 80 | FileWriter: func() FileWriter { 81 | expect := "package abcd\n" + 82 | "\n" + 83 | "struct X {\n" + 84 | " Field int\n" + 85 | "}\n" + 86 | "struct Y {\n" + 87 | " Field string\n" + 88 | "}\n" 89 | 90 | mock := NewWriterMock(ctrl) 91 | mock.EXPECT().Write("destination.go", expect) 92 | return mock 93 | }(), 94 | }, 95 | args: args{ 96 | sourcePath: "source_code.go", 97 | destinationPath: "destination.go", 98 | templatePath: "template.tpl", 99 | typeName: "", 100 | ignoreFieldNames: []string{}, 101 | packageName: "abcd", 102 | }, 103 | }, 104 | { 105 | name: "Successfully generate constructor function when specify type name", 106 | fields: fields{ 107 | TemplateReader: func() TemplateReader { 108 | mock := NewTemplateReaderMock(ctrl) 109 | mock.EXPECT().Read("template.tpl").Return( 110 | template.Must(template.New("template.tpl").Parse(testTemplate)), 111 | ) 112 | return mock 113 | }(), 114 | SourceCodeReader: func() SourceCodeReader { 115 | mock := NewMockSourceCodeReader(ctrl) 116 | mock.EXPECT().ReadWithType("source_code.go", "X", []string{}).Return( 117 | structure.Code{ 118 | FilePath: "source_code.go", 119 | Structs: []structure.Struct{ 120 | structure.Struct{ 121 | Name: "X", 122 | Fields: []structure.Field{ 123 | structure.Field{ 124 | Name: "Field", 125 | Type: "int", 126 | }, 127 | }, 128 | }, 129 | }, 130 | }, 131 | ) 132 | return mock 133 | }(), 134 | FileWriter: func() FileWriter { 135 | expect := "package abcd\n" + 136 | "\n" + 137 | "struct X {\n" + 138 | " Field int\n" + 139 | "}\n" 140 | 141 | mock := NewWriterMock(ctrl) 142 | mock.EXPECT().Write("destination.go", expect) 143 | return mock 144 | }(), 145 | }, 146 | args: args{ 147 | sourcePath: "source_code.go", 148 | destinationPath: "destination.go", 149 | templatePath: "template.tpl", 150 | typeName: "X", 151 | ignoreFieldNames: []string{}, 152 | packageName: "abcd", 153 | }, 154 | }, 155 | } 156 | for _, tt := range tests { 157 | t.Run(tt.name, func(t *testing.T) { 158 | impl := Constructor{ 159 | TemplateReader: tt.fields.TemplateReader, 160 | SourceCodeReader: tt.fields.SourceCodeReader, 161 | FileWriter: tt.fields.FileWriter, 162 | } 163 | impl.Generate(tt.args.sourcePath, tt.args.destinationPath, tt.args.templatePath, tt.args.typeName, tt.args.ignoreFieldNames, tt.args.packageName) 164 | }) 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /generator/source_code_reader.go: -------------------------------------------------------------------------------- 1 | //go:generate mockgen -source=$GOFILE -destination=$GOPATH/src/github.com/bannzai/constructor/generator/source_code_reader_interface_mock_test.go -package=$GOPACKAGE 2 | package generator 3 | 4 | import "github.com/bannzai/constructor/structure" 5 | 6 | type SourceCodeReader interface { 7 | Read(filePath structure.Path, ignoreFieldNames []string) structure.Code 8 | ReadWithType(filePath structure.Path, generatedTypeName string, ignoreFieldNames []string) (code structure.Code) 9 | } 10 | -------------------------------------------------------------------------------- /generator/source_code_reader_interface_mock_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: source_code_reader.go 3 | 4 | // Package generator is a generated GoMock package. 5 | package generator 6 | 7 | import ( 8 | structure "github.com/bannzai/constructor/structure" 9 | gomock "github.com/golang/mock/gomock" 10 | reflect "reflect" 11 | ) 12 | 13 | // MockSourceCodeReader is a mock of SourceCodeReader interface 14 | type MockSourceCodeReader struct { 15 | ctrl *gomock.Controller 16 | recorder *MockSourceCodeReaderMockRecorder 17 | } 18 | 19 | // MockSourceCodeReaderMockRecorder is the mock recorder for MockSourceCodeReader 20 | type MockSourceCodeReaderMockRecorder struct { 21 | mock *MockSourceCodeReader 22 | } 23 | 24 | // NewMockSourceCodeReader creates a new mock instance 25 | func NewMockSourceCodeReader(ctrl *gomock.Controller) *MockSourceCodeReader { 26 | mock := &MockSourceCodeReader{ctrl: ctrl} 27 | mock.recorder = &MockSourceCodeReaderMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use 32 | func (m *MockSourceCodeReader) EXPECT() *MockSourceCodeReaderMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // Read mocks base method 37 | func (m *MockSourceCodeReader) Read(filePath structure.Path, ignoreFieldNames []string) structure.Code { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "Read", filePath, ignoreFieldNames) 40 | ret0, _ := ret[0].(structure.Code) 41 | return ret0 42 | } 43 | 44 | // Read indicates an expected call of Read 45 | func (mr *MockSourceCodeReaderMockRecorder) Read(filePath, ignoreFieldNames interface{}) *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockSourceCodeReader)(nil).Read), filePath, ignoreFieldNames) 48 | } 49 | 50 | // ReadWithType mocks base method 51 | func (m *MockSourceCodeReader) ReadWithType(filePath structure.Path, generatedTypeName string, ignoreFieldNames []string) structure.Code { 52 | m.ctrl.T.Helper() 53 | ret := m.ctrl.Call(m, "ReadWithType", filePath, generatedTypeName, ignoreFieldNames) 54 | ret0, _ := ret[0].(structure.Code) 55 | return ret0 56 | } 57 | 58 | // ReadWithType indicates an expected call of ReadWithType 59 | func (mr *MockSourceCodeReaderMockRecorder) ReadWithType(filePath, generatedTypeName, ignoreFieldNames interface{}) *gomock.Call { 60 | mr.mock.ctrl.T.Helper() 61 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadWithType", reflect.TypeOf((*MockSourceCodeReader)(nil).ReadWithType), filePath, generatedTypeName, ignoreFieldNames) 62 | } 63 | -------------------------------------------------------------------------------- /generator/template.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/bannzai/constructor/file" 7 | "github.com/bannzai/constructor/structure" 8 | ) 9 | 10 | type Template struct{} 11 | 12 | const defaultTemplate = `// DO NOT EDIT THIS FILE. 13 | // File generated by constructor. 14 | // https://github.com/bannzai/constructor 15 | 16 | {{- $dot := . }} 17 | package {{.Package}} 18 | 19 | {{range $i, $struct := .Structs -}} 20 | {{- $suffix := upperCamelCase $dot.Package -}} 21 | {{- $structureName := .Name -}} 22 | // New{{$structureName}}{{$suffix}} insitanciate {{.Name}} 23 | func New{{$structureName}}{{$suffix}}( 24 | {{range $i, $field := .Fields -}} 25 | {{parameterCase $field.Name}} {{$field.Type}}, 26 | {{end -}} 27 | ) {{$structureName}} { 28 | return {{$structureName}}{ 29 | {{range $i, $field := .Fields -}} 30 | {{$field.Name}}: {{argumentCase $field.Name}}, 31 | {{end -}} 32 | } 33 | } 34 | {{end}} 35 | ` 36 | 37 | func (impl Template) Setup() { 38 | if file.FileExists(structure.TemplateFileName) { 39 | fmt.Println(structure.TemplateFileName + " is already exists. Not generate " + structure.TemplateFileName) 40 | return 41 | } 42 | file.WriteFile(structure.TemplateFileName, defaultTemplate) 43 | } 44 | -------------------------------------------------------------------------------- /generator/template_reader.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "html/template" 5 | 6 | "github.com/bannzai/constructor/structure" 7 | ) 8 | 9 | type TemplateReader interface { 10 | Read(filePath structure.Path) *template.Template 11 | } 12 | -------------------------------------------------------------------------------- /generator/template_reader_mock_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: generator/template_reader.go 3 | 4 | // Package generator is a generated GoMock package. 5 | package generator 6 | 7 | import ( 8 | structure "github.com/bannzai/constructor/structure" 9 | gomock "github.com/golang/mock/gomock" 10 | template "html/template" 11 | reflect "reflect" 12 | ) 13 | 14 | // TemplateReaderMock is a mock of TemplateReader interface 15 | type TemplateReaderMock struct { 16 | ctrl *gomock.Controller 17 | recorder *TemplateReaderMockMockRecorder 18 | } 19 | 20 | // TemplateReaderMockMockRecorder is the mock recorder for TemplateReaderMock 21 | type TemplateReaderMockMockRecorder struct { 22 | mock *TemplateReaderMock 23 | } 24 | 25 | // NewTemplateReaderMock creates a new mock instance 26 | func NewTemplateReaderMock(ctrl *gomock.Controller) *TemplateReaderMock { 27 | mock := &TemplateReaderMock{ctrl: ctrl} 28 | mock.recorder = &TemplateReaderMockMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use 33 | func (m *TemplateReaderMock) EXPECT() *TemplateReaderMockMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // Read mocks base method 38 | func (m *TemplateReaderMock) Read(filePath structure.Path) *template.Template { 39 | ret := m.ctrl.Call(m, "Read", filePath) 40 | ret0, _ := ret[0].(*template.Template) 41 | return ret0 42 | } 43 | 44 | // Read indicates an expected call of Read 45 | func (mr *TemplateReaderMockMockRecorder) Read(filePath interface{}) *gomock.Call { 46 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*TemplateReaderMock)(nil).Read), filePath) 47 | } 48 | -------------------------------------------------------------------------------- /generator/writer.go: -------------------------------------------------------------------------------- 1 | package generator 2 | 3 | import ( 4 | "github.com/bannzai/constructor/file" 5 | "github.com/bannzai/constructor/structure" 6 | ) 7 | 8 | type FileWriter interface { 9 | Write(destinationPath structure.Path, content string) 10 | } 11 | 12 | type FileWriterImpl struct{} 13 | 14 | func (FileWriterImpl) Write(destinationPath structure.Path, content string) { 15 | file.WriteFile(destinationPath, content) 16 | file.GoImports(destinationPath) 17 | } 18 | -------------------------------------------------------------------------------- /generator/writer_mock_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: generator/writer.go 3 | 4 | // Package generator is a generated GoMock package. 5 | package generator 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | structure "github.com/bannzai/constructor/structure" 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // WriterMock is a mock of Writer interface 15 | type WriterMock struct { 16 | ctrl *gomock.Controller 17 | recorder *WriterMockMockRecorder 18 | } 19 | 20 | // WriterMockMockRecorder is the mock recorder for WriterMock 21 | type WriterMockMockRecorder struct { 22 | mock *WriterMock 23 | } 24 | 25 | // NewWriterMock creates a new mock instance 26 | func NewWriterMock(ctrl *gomock.Controller) *WriterMock { 27 | mock := &WriterMock{ctrl: ctrl} 28 | mock.recorder = &WriterMockMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use 33 | func (m *WriterMock) EXPECT() *WriterMockMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // Write mocks base method 38 | func (m *WriterMock) Write(destinationPath structure.Path, content string) { 39 | m.ctrl.Call(m, "Write", destinationPath, content) 40 | } 41 | 42 | // Write indicates an expected call of Write 43 | func (mr *WriterMockMockRecorder) Write(destinationPath, content interface{}) *gomock.Call { 44 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*WriterMock)(nil).Write), destinationPath, content) 45 | } 46 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bannzai/constructor 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/golang/mock v1.3.1 7 | github.com/spf13/cobra v0.0.4 8 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262 9 | gopkg.in/yaml.v2 v2.2.2 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 3 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 4 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= 5 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 6 | github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 9 | github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= 10 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 11 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 12 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 13 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 14 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 15 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 16 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 17 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= 20 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 21 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 22 | github.com/spf13/cobra v0.0.4 h1:S0tLZ3VOKl2Te0hpq8+ke0eSJPfCnNTPiDlsfwi1/NE= 23 | github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= 24 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 25 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 26 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 27 | github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= 28 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 29 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= 30 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 31 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 32 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 33 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 34 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 35 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 36 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 37 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 38 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg= 39 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 42 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 43 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/bannzai/constructor/command" 5 | ) 6 | 7 | func main() { 8 | command.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /reader/ast_parser.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | "io/ioutil" 8 | 9 | "github.com/bannzai/constructor/structure" 10 | ) 11 | 12 | func parseASTFile(filePath structure.Path) *ast.File { 13 | buf, err := ioutil.ReadFile(filePath) 14 | if err != nil { 15 | panic(err) 16 | } 17 | fileSet := token.NewFileSet() 18 | astFile, err := parser.ParseFile(fileSet, filePath, buf, 0) 19 | if err != nil { 20 | panic(err) 21 | } 22 | return astFile 23 | } 24 | 25 | func parseASTStructs(file *ast.File) (typeNameAndStruct map[string]*ast.StructType) { 26 | typeNameAndStruct = map[string]*ast.StructType{} 27 | ast.Inspect(file, func(node ast.Node) bool { 28 | lastChildNode := node == nil 29 | if lastChildNode { 30 | return false 31 | } 32 | 33 | typeSpec, ok := node.(*ast.TypeSpec) 34 | if !ok { 35 | return true 36 | } 37 | 38 | name := typeSpec.Name.Name 39 | structType, ok := typeSpec.Type.(*ast.StructType) 40 | if !ok { 41 | return true 42 | } 43 | 44 | typeNameAndStruct[name] = structType 45 | return true 46 | }) 47 | return 48 | } 49 | -------------------------------------------------------------------------------- /reader/ast_structure_convert.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "reflect" 7 | 8 | "github.com/bannzai/constructor/structure" 9 | ) 10 | 11 | type TypeAndNames = map[string][]string 12 | 13 | func convert(typeName string, ignoreFieldNames []string, astStruct *ast.StructType) structure.Struct { 14 | typeAndNames := TypeAndNames{} 15 | ast.Inspect(astStruct, func(node ast.Node) bool { 16 | lastChildNode := node == nil 17 | if lastChildNode { 18 | return false 19 | } 20 | 21 | field, ok := node.(*ast.Field) 22 | if !ok { 23 | return true 24 | } 25 | 26 | switch types := field.Type.(type) { 27 | case *ast.Ident: 28 | if len(field.Names) == 0 { 29 | /* 30 | type xxx interface {} 31 | type A struct { 32 | xxx 33 | } 34 | */ 35 | name := types.Name 36 | typeAndNames[name] = append(typeAndNames[name], name) 37 | } else { 38 | /* 39 | type A struct { 40 | iiii int 41 | } 42 | */ 43 | fieldTypeName := types.Name 44 | for _, nameIdentifier := range field.Names { 45 | name := nameIdentifier.Name 46 | typeAndNames[fieldTypeName] = append(typeAndNames[fieldTypeName], name) 47 | } 48 | } 49 | 50 | case *ast.ArrayType: 51 | var fieldTypeName string 52 | if selector, ok := types.Elt.(*ast.SelectorExpr); ok { 53 | x, sel := parseSelectorExpr(selector) 54 | fieldTypeName = "[]" + x + "." + sel 55 | } 56 | if ident, ok := types.Elt.(*ast.Ident); ok { 57 | fieldTypeName = "[]" + ident.Name 58 | } 59 | if len(fieldTypeName) == 0 { 60 | panic(fmt.Errorf("Unknown pattern when ast.ArrayType.Elt receive %v", reflect.TypeOf(types.Elt))) 61 | } 62 | for _, nameIdentifier := range field.Names { 63 | name := nameIdentifier.Name 64 | typeAndNames[fieldTypeName] = append(typeAndNames[fieldTypeName], name) 65 | } 66 | case *ast.MapType: 67 | var key string 68 | var value string 69 | if k, ok := types.Key.(*ast.Ident); ok { 70 | key = k.Name 71 | } 72 | if v, ok := types.Value.(*ast.Ident); ok { 73 | value = v.Name 74 | } 75 | if k, ok := types.Key.(*ast.SelectorExpr); ok { 76 | x, sel := parseSelectorExpr(k) 77 | key = x + "." + sel 78 | } 79 | if v, ok := types.Value.(*ast.SelectorExpr); ok { 80 | x, sel := parseSelectorExpr(v) 81 | value = x + "." + sel 82 | } 83 | 84 | if len(key) == 0 { 85 | panic(fmt.Errorf("Unknown pattern when ast.MapType.Key receive %v", reflect.TypeOf(types.Key))) 86 | } 87 | if len(value) == 0 { 88 | panic(fmt.Errorf("Unknown pattern when ast.MapType.Value receive %v", reflect.TypeOf(types.Value))) 89 | } 90 | 91 | fieldTypeName := "map[" + key + "]" + value 92 | for _, nameIdentifier := range field.Names { 93 | name := nameIdentifier.Name 94 | typeAndNames[fieldTypeName] = append(typeAndNames[fieldTypeName], name) 95 | } 96 | case *ast.FuncType: 97 | statement := "func(" 98 | for i, param := range types.Params.List { 99 | parameterType := param.Type.(*ast.Ident).Name 100 | parameterNames := param.Names[0:len(param.Names)] 101 | if i != 0 { 102 | statement += ", " 103 | } 104 | for i, parameterName := range parameterNames { 105 | if i == 0 { 106 | statement += parameterName.Name 107 | } else { 108 | statement += ", " + parameterName.Name 109 | } 110 | } 111 | statement += " " + parameterType 112 | } 113 | statement += ")" 114 | 115 | results := types.Results.List 116 | if len(results) > 1 { 117 | statement += "(" 118 | } 119 | for i, result := range types.Results.List { 120 | resultType := result.Type.(*ast.Ident).Name 121 | resultNames := result.Names[0:len(result.Names)] 122 | if i != 0 { 123 | statement += ", " 124 | } 125 | for i, resultName := range resultNames { 126 | if i == 0 { 127 | statement += resultName.Name 128 | } else { 129 | statement += ", " + resultName.Name 130 | } 131 | } 132 | statement += " " + resultType 133 | } 134 | if len(results) > 1 { 135 | statement += ")" 136 | } 137 | fieldName := field.Names[0].Name 138 | typeAndNames[statement] = append(typeAndNames[statement], fieldName) 139 | // No continue next child 140 | // Because ast.FuncType has *ast.Field node. 141 | // It will duplicate call function 142 | return false 143 | case *ast.SelectorExpr: 144 | x, sel := parseSelectorExpr(types) 145 | fieldTypeName := x + "." + sel 146 | for _, nameIdentifier := range field.Names { 147 | name := nameIdentifier.Name 148 | typeAndNames[fieldTypeName] = append(typeAndNames[fieldTypeName], name) 149 | } 150 | } 151 | return true 152 | }) 153 | 154 | fields := []structure.Field{} 155 | for fieldType, names := range typeAndNames { 156 | for _, name := range names { 157 | if shouldNotGenerate(name, ignoreFieldNames) { 158 | continue 159 | } 160 | fields = append(fields, structure.Field{ 161 | Name: name, 162 | Type: fieldType, 163 | }) 164 | } 165 | } 166 | 167 | fields = sortedFields(fields) 168 | 169 | return structure.Struct{ 170 | Name: typeName, 171 | Fields: fields, 172 | } 173 | } 174 | 175 | func shouldNotGenerate(field string, ignoreFields []string) bool { 176 | for _, ignore := range ignoreFields { 177 | if ignore == field { 178 | return true 179 | } 180 | } 181 | return false 182 | } 183 | 184 | func parseSelectorExpr(types *ast.SelectorExpr) (string, string) { 185 | x, ok := types.X.(*ast.Ident) 186 | if !ok { 187 | panic(fmt.Errorf("Unknown pattern when ast.SelectorExpr.X receive %v", reflect.TypeOf(types.X))) 188 | } 189 | return x.Name, types.Sel.Name 190 | } 191 | -------------------------------------------------------------------------------- /reader/code.constructor.go: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE. 2 | // File generated by constructor. 3 | // https://github.com/bannzai/constructor 4 | package reader 5 | 6 | // NewCodeReader insitanciate Code 7 | func NewCodeReader() Code { 8 | return Code{} 9 | } 10 | -------------------------------------------------------------------------------- /reader/code.go: -------------------------------------------------------------------------------- 1 | //go:generate constructor generate --source=$GOFILE --destination=$GOPATH/src/github.com/bannzai/constructor/reader/code.constructor.go --package=$GOPACKAGE --template=$GOPATH/src/github.com/bannzai/constructor/constructor.tpl 2 | package reader 3 | 4 | import ( 5 | "github.com/bannzai/constructor/structure" 6 | ) 7 | 8 | type Code struct{} 9 | 10 | func (impl Code) Read(filePath structure.Path, ignoreFieldNames []string) structure.Code { 11 | return impl.ReadWithType(filePath, "", ignoreFieldNames) 12 | } 13 | 14 | func (impl Code) ReadWithType(filePath structure.Path, generatedTypeName string, ignoreFieldNames []string) (code structure.Code) { 15 | code.FilePath = filePath 16 | isNotSpecifyType := 0 == len(generatedTypeName) 17 | for typeName, structure := range parseASTStructs(parseASTFile(code.FilePath)) { 18 | if !isNotSpecifyType { 19 | if typeName != generatedTypeName { 20 | continue 21 | } 22 | } 23 | code.Structs = append(code.Structs, convert(typeName, ignoreFieldNames, structure)) 24 | } 25 | code.Structs = sortedStructs(code.Structs) 26 | return 27 | } 28 | -------------------------------------------------------------------------------- /reader/code_test.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "go/ast" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/bannzai/constructor/structure" 9 | ) 10 | 11 | const testdataPath = "testdata/" 12 | const testdataStructPath = testdataPath + "struct.go" 13 | const testdataForMultipleStructPath = testdataPath + "struct_for_multiple_type.go" 14 | 15 | func Test_parseASTFile(t *testing.T) { 16 | type args struct { 17 | filePath structure.Path 18 | } 19 | tests := []struct { 20 | name string 21 | args args 22 | wantNil bool 23 | }{ 24 | { 25 | name: "Successfully parse AST file.", 26 | args: args{ 27 | filePath: testdataStructPath, 28 | }, 29 | wantNil: false, 30 | }, 31 | } 32 | for _, tt := range tests { 33 | t.Run(tt.name, func(t *testing.T) { 34 | if got := parseASTFile(tt.args.filePath); (got == nil) != tt.wantNil { 35 | t.Errorf("parseASTFile() = %v, want %v", got, tt.wantNil) 36 | } 37 | }) 38 | } 39 | } 40 | 41 | func Test_parseASTStructs(t *testing.T) { 42 | type args struct { 43 | file *ast.File 44 | } 45 | tests := []struct { 46 | name string 47 | args args 48 | wantStructsCount int 49 | }{ 50 | // reference: https://play.golang.org/p/BMcvmVmSgtM 51 | { 52 | name: "Successfully parse AST file.", 53 | args: args{ 54 | file: parseASTFile(testdataStructPath), 55 | }, 56 | wantStructsCount: 1, 57 | }, 58 | } 59 | for _, tt := range tests { 60 | t.Run(tt.name, func(t *testing.T) { 61 | if gotStructs := parseASTStructs(tt.args.file); len(gotStructs) != tt.wantStructsCount { 62 | t.Errorf("parseASTStructs() = %v, wantStructsCount %v", gotStructs, tt.wantStructsCount) 63 | } 64 | }) 65 | } 66 | } 67 | 68 | func TestCode_Read(t *testing.T) { 69 | type args struct { 70 | filePath string 71 | ignoreFieldNames []string 72 | } 73 | tests := []struct { 74 | name string 75 | args args 76 | want structure.Code 77 | }{ 78 | { 79 | name: "Successfully read go file.", 80 | args: args{ 81 | filePath: testdataStructPath, 82 | ignoreFieldNames: []string{"I"}, 83 | }, 84 | want: structure.Code{ 85 | FilePath: testdataStructPath, 86 | Structs: []structure.Struct{ 87 | structure.Struct{ 88 | Name: "Struct", 89 | Fields: []structure.Field{ 90 | structure.Field{ 91 | Name: "A", 92 | Type: "Alias", 93 | }, 94 | structure.Field{ 95 | Name: "F", 96 | Type: "func(aaa int, bbb bool) string", 97 | }, 98 | structure.Field{ 99 | Name: "L", 100 | Type: "[]int", 101 | }, 102 | structure.Field{ 103 | Name: "LO", 104 | Type: "[]io.Writer", 105 | }, 106 | structure.Field{ 107 | Name: "M", 108 | Type: "map[string]bool", 109 | }, 110 | structure.Field{ 111 | Name: "MKO", 112 | Type: "map[io.Writer]bool", 113 | }, 114 | structure.Field{ 115 | Name: "MKVO", 116 | Type: "map[io.Writer]io.Writer", 117 | }, 118 | structure.Field{ 119 | Name: "MVO", 120 | Type: "map[string]io.Writer", 121 | }, 122 | structure.Field{ 123 | Name: "O", 124 | Type: "io.Writer", 125 | }, 126 | structure.Field{ 127 | Name: "P", 128 | Type: "string", 129 | }, 130 | structure.Field{ 131 | Name: "XXX", 132 | Type: "XXX", 133 | }, 134 | }, 135 | }, 136 | }, 137 | }, 138 | }, 139 | } 140 | for _, tt := range tests { 141 | t.Run(tt.name, func(t *testing.T) { 142 | impl := Code{} 143 | got := impl.Read(tt.args.filePath, tt.args.ignoreFieldNames) 144 | if !reflect.DeepEqual(got, tt.want) { 145 | t.Errorf("Read() = %v,\n want %v", got, tt.want) 146 | } 147 | }) 148 | } 149 | } 150 | 151 | func TestCode_ReadWithType(t *testing.T) { 152 | type args struct { 153 | filePath structure.Path 154 | generatedTypeName string 155 | ignoreFieldNames []string 156 | } 157 | tests := []struct { 158 | name string 159 | impl Code 160 | args args 161 | want structure.Code 162 | }{ 163 | { 164 | name: "Successfully read go file with specify type X", 165 | args: args{ 166 | filePath: testdataForMultipleStructPath, 167 | generatedTypeName: "X", 168 | ignoreFieldNames: []string{}, 169 | }, 170 | want: structure.Code{ 171 | FilePath: testdataForMultipleStructPath, 172 | Structs: []structure.Struct{ 173 | structure.Struct{ 174 | Name: "X", 175 | Fields: []structure.Field{ 176 | structure.Field{ 177 | Name: "A", 178 | Type: "int", 179 | }, 180 | }, 181 | }, 182 | }, 183 | }, 184 | }, 185 | } 186 | for _, tt := range tests { 187 | t.Run(tt.name, func(t *testing.T) { 188 | impl := Code{} 189 | if gotCode := impl.ReadWithType(tt.args.filePath, tt.args.generatedTypeName, tt.args.ignoreFieldNames); !reflect.DeepEqual(gotCode, tt.want) { 190 | t.Errorf("Code.ReadWithType() = %v, want %v", gotCode, tt.want) 191 | } 192 | }) 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /reader/sort.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/bannzai/constructor/structure" 7 | ) 8 | 9 | func sortedStructs(structs []structure.Struct) []structure.Struct { 10 | sort.SliceStable(structs, func(l, r int) bool { 11 | return sort.StringsAreSorted( 12 | []string{ 13 | structs[l].Name, 14 | structs[r].Name, 15 | }, 16 | ) 17 | }) 18 | return structs 19 | } 20 | 21 | func sortedFields(fields []structure.Field) []structure.Field { 22 | sort.SliceStable(fields, func(l, r int) bool { 23 | return sort.StringsAreSorted( 24 | []string{ 25 | fields[l].Name, 26 | fields[r].Name, 27 | }, 28 | ) 29 | }) 30 | return fields 31 | } 32 | -------------------------------------------------------------------------------- /reader/template.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "html/template" 5 | "path/filepath" 6 | "strings" 7 | 8 | "github.com/bannzai/constructor/structure" 9 | ) 10 | 11 | type Template struct{} 12 | 13 | var functions = template.FuncMap{ 14 | "upperCamelCase": upperCamelCase, 15 | "parameterCase": lowerCamelCase, 16 | "argumentCase": lowerCamelCase, 17 | "escapeReservedWord": escapeReservedWord, 18 | } 19 | 20 | func escapeReservedWord(target string) string { 21 | for _, reservedWord := range structure.ReservedWords { 22 | if reservedWord == target { 23 | return "_" + target 24 | } 25 | } 26 | 27 | return target 28 | } 29 | 30 | func lowerCamelCase(target string) string { 31 | firstString := strings.ToLower(target[:1]) 32 | dropedFirstString := target[1:] 33 | return escapeReservedWord(firstString + dropedFirstString) 34 | } 35 | 36 | func upperCamelCase(target string) string { 37 | firstString := strings.ToUpper(target[:1]) 38 | dropedFirstString := target[1:] 39 | return escapeReservedWord(firstString + dropedFirstString) 40 | } 41 | 42 | func (impl Template) Read(filePath structure.Path) *template.Template { 43 | return template.Must(template.New(filepath.Base(filePath)).Funcs(functions).ParseFiles(filePath)) 44 | } 45 | -------------------------------------------------------------------------------- /reader/template_test.go: -------------------------------------------------------------------------------- 1 | package reader 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/bannzai/constructor/structure" 8 | ) 9 | 10 | func TestTemplateImpl_Read(t *testing.T) { 11 | type args struct { 12 | FilePath structure.Path 13 | } 14 | tests := []struct { 15 | name string 16 | args args 17 | }{ 18 | { 19 | name: "Successfully read template file.", 20 | args: args{ 21 | FilePath: "../constructor.tpl", 22 | }, 23 | }, 24 | } 25 | for _, tt := range tests { 26 | t.Run(tt.name, func(t *testing.T) { 27 | impl := Template{} 28 | got := impl.Read(tt.args.FilePath) 29 | fmt.Printf("Successfully got: %v ", got) 30 | }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /reader/testdata/struct.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import "io" 4 | 5 | // referenced playground: https://play.golang.org/p/NROZdl65DlM 6 | type XXX interface{} 7 | type Alias string 8 | type Struct struct { 9 | P string 10 | F func(aaa int, bbb bool) string 11 | A Alias 12 | O io.Writer 13 | L []int 14 | LO []io.Writer 15 | M map[string]bool 16 | MKO map[io.Writer]bool 17 | MVO map[string]io.Writer 18 | MKVO map[io.Writer]io.Writer 19 | I string 20 | XXX 21 | } 22 | -------------------------------------------------------------------------------- /reader/testdata/struct_for_multiple_type.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | type X struct { 4 | A int 5 | } 6 | 7 | type Y struct { 8 | B bool 9 | } 10 | -------------------------------------------------------------------------------- /structure/alias.go: -------------------------------------------------------------------------------- 1 | package structure 2 | 3 | // Path for file path 4 | type Path = string 5 | -------------------------------------------------------------------------------- /structure/code.constructor.go: -------------------------------------------------------------------------------- 1 | // DO NOT EDIT THIS FILE. 2 | // File generated by constructor. 3 | // https://github.com/bannzai/constructor 4 | package structure 5 | 6 | // NewCodeStructure insitanciate Code 7 | func NewCodeStructure( 8 | filePath Path, 9 | structs []Struct, 10 | ) Code { 11 | return Code{ 12 | FilePath: filePath, 13 | Structs: structs, 14 | } 15 | } 16 | 17 | // NewFieldStructure insitanciate Field 18 | func NewFieldStructure( 19 | name string, 20 | _type string, 21 | ) Field { 22 | return Field{ 23 | Name: name, 24 | Type: _type, 25 | } 26 | } 27 | 28 | // NewStructStructure insitanciate Struct 29 | func NewStructStructure( 30 | fields []Field, 31 | name string, 32 | ) Struct { 33 | return Struct{ 34 | Fields: fields, 35 | Name: name, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /structure/code.go: -------------------------------------------------------------------------------- 1 | package structure 2 | 3 | type ( 4 | // Code is presentation of .go file content. 5 | Code struct { 6 | FilePath Path 7 | Structs []Struct 8 | } 9 | Struct struct { 10 | Name string 11 | Fields []Field 12 | } 13 | Field struct { 14 | Name string 15 | Type string 16 | } 17 | ) 18 | -------------------------------------------------------------------------------- /structure/const.go: -------------------------------------------------------------------------------- 1 | package structure 2 | 3 | const TemplateFileName = "constructor.tpl" 4 | const TagKeyword = "constructor" 5 | -------------------------------------------------------------------------------- /structure/reserved_words.go: -------------------------------------------------------------------------------- 1 | package structure 2 | 3 | // ReservedWords reference from: https://golang.org/ref/spec#Keywords 4 | var ReservedWords = []string{ 5 | "break", 6 | "default", 7 | "func", 8 | "interface", 9 | "select", 10 | "case", 11 | "defer", 12 | "go", 13 | "map", 14 | "struct", 15 | "chan", 16 | "else", 17 | "goto", 18 | "package", 19 | "switch", 20 | "const", 21 | "fallthrough", 22 | "if", 23 | "range", 24 | "type", 25 | "continue", 26 | "for", 27 | "import", 28 | "return", 29 | "var", 30 | } 31 | --------------------------------------------------------------------------------