├── .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 |
--------------------------------------------------------------------------------