├── go.mod ├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── .gitignore ├── main.go ├── .golangci.yml ├── testdata ├── usercases │ └── usecase.tmpl └── controllers │ └── controller.tmpl ├── LICENSE ├── .goreleaser.yml ├── lgen.go └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/budougumi0617/lgen 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | 4 | # Changes 5 | 6 | 7 | # Appendix 8 | 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: budougumi0617 7 | 8 | --- 9 | 10 | # Summary 11 | 12 | # Expects details 13 | 14 | # Proposals 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .DS_Store 15 | 16 | dist/ 17 | 18 | # Executable 19 | /lgen 20 | /main 21 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "os" 7 | ) 8 | 9 | var ( 10 | version = "dev" 11 | commit = "none" 12 | date = "unknown" 13 | ) 14 | 15 | func main() { 16 | log.SetFlags(0) 17 | err := Run(os.Args, os.Stdout, os.Stderr) 18 | if err != nil && err != flag.ErrHelp && err != ErrShowVersion { 19 | log.Println(err) 20 | os.Exit(2) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: budougumi0617 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Templates files and directories are below structures: 16 | 1. Execute command with options is below: 17 | 1. Result is below: 18 | 19 | **Expected behavior** 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Additional context** 23 | Add any other context about the problem here. 24 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | govet: 3 | check-shadowing: false 4 | golint: 5 | min-confidence: 0 6 | gocyclo: 7 | min-complexity: 30 8 | maligned: 9 | suggest-new: true 10 | misspell: 11 | locale: US 12 | ignore-words: 13 | - cancelled 14 | - Cancelled 15 | 16 | linters: 17 | disable-all: true 18 | enable: 19 | - goimports 20 | - bodyclose 21 | - deadcode 22 | - errcheck 23 | - gochecknoinits 24 | - gocognit 25 | - gocritic 26 | - gocyclo 27 | - gofmt 28 | - golint 29 | - govet 30 | - ineffassign 31 | - interfacer 32 | - maligned 33 | - misspell 34 | - nakedret 35 | - prealloc 36 | - staticcheck 37 | - structcheck 38 | - stylecheck 39 | - typecheck 40 | - unconvert 41 | - unparam 42 | - unused 43 | - varcheck 44 | - whitespace 45 | -------------------------------------------------------------------------------- /testdata/usercases/usecase.tmpl: -------------------------------------------------------------------------------- 1 | package usecase 2 | 3 | type {{ .Action | title}}{{ .Model | title }}Input struct{} 4 | 5 | type {{ .Action | title}}{{ .Model | title }}Result struct{} 6 | 7 | type {{ .Action | title}}{{ .Model | title }}Usecase interface { 8 | Run({{ .Action | title}}{{ .Model | title }}Input) ({{ .Action | title}}{{ .Model | title }}Result, error) 9 | } 10 | 11 | func New{{ .Action | title}}{{ .Model | title }}Usecase() {{ .Action | title}}{{ .Model | title }}Usecase { 12 | return &{{ .Action }}{{ .Model | title }}Usecase{ 13 | } 14 | } 15 | 16 | type {{ .Action }}{{ .Model | title }}Usecase struct {} 17 | 18 | func (u *{{ .Action }}{{ .Model | title }}Usecase) Run( 19 | in {{ .Action | title}}{{ .Model | title }}Input, 20 | ) ({{ .Action | title}}{{ .Model | title }}Result, error){ 21 | // Need to implement usercase logic 22 | return {{ .Action | title}}{{ .Model | title }}Result{ 23 | // Need to build result 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Yoichiro Shimizu 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 | -------------------------------------------------------------------------------- /testdata/controllers/controller.tmpl: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | // New{{ .Action | title }}{{ .Model | title }}Controller create a new instance of {{ .Action | title }}{{ .Model | title }}Controller. 8 | func New{{ .Action | title }}{{ .Model | title }}Controller( 9 | u usecase.{{ .Action | title }}{{ .Model | title }}UseCase, 10 | ) {{ .Action | title }}{{ .Model | title }}Controller { 11 | return {{ .Action }}{{ .Model | title }}Controller{ 12 | usercase: u, 13 | } 14 | } 15 | 16 | // {{ .Action | title }}{{ .Model | title }}Controller handle the request to {{ .Action | title }} {{ .Model | title }} records. 17 | type {{ .Action | title }}{{ .Model | title }}Controller struct { 18 | usercase usercase.{{ .Action | title }}{{ .Model | title }}Usecase 19 | } 20 | 21 | // Handler is the http handler to handle request to GET adjustment records. 22 | func (c *{{ .Action | title }}{{ .Model | title }}Controller) Handler(w http.ResponseWriter, r *http.Request) { 23 | // Need to implement handle request manualy 24 | in := usercase.{{ .Action | title }}{{ .Model | title }}Input{ 25 | // Need to implement handle request manualy 26 | } 27 | result, err := c.usecase.Run(in) 28 | if err != nil{ 29 | http.Error(w, http.StatusText(http.StatusInternalServerError), 30 | http.StatusInternalServerError) 31 | } 32 | // Need to implement send response used by result manualy 33 | } 34 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # This is an example goreleaser.yaml file with some sane defaults. 2 | # Make sure to check the documentation at http://goreleaser.com 3 | before: 4 | hooks: 5 | # you may remove this if you don't use vgo 6 | - go mod tidy 7 | # you may remove this if you don't need go generate 8 | #- go generate ./... 9 | builds: 10 | - 11 | main: . 12 | env: 13 | - CGO_ENABLED=0 14 | archives: 15 | - replacements: 16 | darwin: Darwin 17 | linux: Linux 18 | windows: Windows 19 | 386: i386 20 | amd64: x86_64 21 | checksum: 22 | name_template: 'checksums.txt' 23 | snapshot: 24 | name_template: "{{ .Tag }}-next" 25 | changelog: 26 | sort: asc 27 | filters: 28 | exclude: 29 | - '^docs:' 30 | - '^test:' 31 | brews: 32 | - 33 | # Name template of the recipe 34 | # Default to project name 35 | name: lgen 36 | 37 | # Github repository to push the tap to. 38 | github: 39 | owner: budougumi0617 40 | name: homebrew-tap 41 | 42 | # Template for the url which is determined by the given Token (github or gitlab) 43 | # Default for github is "https://github.com///releases/download/{{ .Tag }}/{{ .ArtifactName }}" 44 | # Default for gitlab is "https://gitlab.com///uploads/{{ .ArtifactUploadHash }}/{{ .ArtifactName }}" 45 | url_template: "https://github.com/budougumi0617/lgen/releases/download/{{ .Tag }}/{{ .ArtifactName }}" 46 | 47 | # Git author used to commit to the repository. 48 | # Defaults are shown. 49 | commit_author: 50 | name: goreleaserbot 51 | email: goreleaser@carlosbecker.com 52 | 53 | # Your app's homepage. 54 | # Default is empty. 55 | homepage: "https://budougumi0617.github.io/" 56 | 57 | # Your app's description. 58 | # Default is empty. 59 | description: "lgen generates boilerplates for layered architecture" 60 | 61 | # So you can `brew test` your formula. 62 | # Default is empty. 63 | test: | 64 | system "#{bin}/lgen --version" 65 | 66 | # Custom install script for brew. 67 | # Default is 'bin.install "program"'. 68 | install: | 69 | bin.install "lgen" 70 | -------------------------------------------------------------------------------- /lgen.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "flag" 7 | "fmt" 8 | "go/format" 9 | "html/template" 10 | "io" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "strings" 15 | ) 16 | 17 | var ( 18 | // ErrShowVersion returns when set version flag. 19 | ErrShowVersion = errors.New("show version") 20 | ) 21 | 22 | type lgen struct { 23 | params Params 24 | template, dist string 25 | outStream, errStream io.Writer 26 | } 27 | 28 | // Params includes template parameters. 29 | type Params struct { 30 | Action string 31 | Model string 32 | } 33 | 34 | func fill(args []string, outStream, errStream io.Writer) (*lgen, error) { 35 | var v bool 36 | var a, m, t, d string 37 | cn := args[0] 38 | flags := flag.NewFlagSet(cn, flag.ContinueOnError) 39 | flags.SetOutput(errStream) 40 | 41 | vdesc := "print version information and quit." 42 | flags.BoolVar(&v, "version", false, vdesc) 43 | flags.BoolVar(&v, "v", false, vdesc) 44 | 45 | adesc := "action name" 46 | flags.StringVar(&a, "action", "", adesc) 47 | flags.StringVar(&a, "a", "", adesc) 48 | 49 | mdesc := "model name" 50 | flags.StringVar(&m, "model", "", mdesc) 51 | flags.StringVar(&m, "m", "", mdesc) 52 | 53 | tdesc := "templates directory" 54 | flags.StringVar(&t, "template", "", tdesc) 55 | flags.StringVar(&t, "t", "./templates", tdesc) 56 | 57 | ddesc := "output directory" 58 | flags.StringVar(&d, "dist", "", ddesc) 59 | flags.StringVar(&d, "d", "./", ddesc) 60 | 61 | if err := flags.Parse(args[1:]); err != nil { 62 | return nil, err 63 | } 64 | 65 | if v { 66 | fmt.Fprintf(errStream, "%s version %s\n", cn, version) 67 | return nil, ErrShowVersion 68 | } 69 | if len(a) == 0 || len(m) == 0 { 70 | msg := "need to set action name and model name" 71 | return nil, fmt.Errorf(msg) 72 | } 73 | a = strings.ToLower(a) 74 | // FIXME: not support compound word. 75 | m = strings.ToLower(m) 76 | 77 | nargs := flags.Args() 78 | if len(nargs) > 0 { 79 | msg := "non-flag option must be zero." 80 | return nil, fmt.Errorf(msg) 81 | } 82 | var err error 83 | t, err = filepath.Abs(t) 84 | if err != nil { 85 | return nil, err 86 | } 87 | d, err = filepath.Abs(d) 88 | if err != nil { 89 | return nil, err 90 | } 91 | return &lgen{ 92 | params: Params{ 93 | Action: a, 94 | Model: m, 95 | }, 96 | template: t, 97 | dist: d, 98 | outStream: outStream, 99 | errStream: errStream, 100 | }, nil 101 | } 102 | 103 | func (l *lgen) run() error { 104 | return filepath.Walk(l.template, l.walk) 105 | } 106 | 107 | var fmap = template.FuncMap{ 108 | "title": strings.Title, 109 | } 110 | 111 | // make file name with action and model. 112 | func (l *lgen) buildFileName(base string) string { 113 | base = strings.Replace(base, ".tmpl", ".go", 1) 114 | return strings.ToLower(strings.Join([]string{l.params.Action, l.params.Model, base}, "_")) 115 | } 116 | 117 | func (l *lgen) walk(path string, info os.FileInfo, err error) error { 118 | p, err := filepath.Rel(l.template, path) 119 | if err != nil { 120 | return err 121 | } 122 | fp := filepath.Join(l.dist, p) 123 | 124 | if info.IsDir() { 125 | // make same directory structure in distribution. 126 | if err := os.MkdirAll(fp, 0777); err != nil { 127 | return err 128 | } 129 | return nil 130 | } 131 | if filepath.Ext(path) != ".tmpl" { 132 | return nil 133 | } 134 | 135 | dn, fn := filepath.Split(fp) 136 | sp := filepath.Join(dn, l.buildFileName(fn)) 137 | 138 | buf := bytes.Buffer{} 139 | b, err := ioutil.ReadFile(path) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | dtmpl := string(b) 145 | if err := template.Must(template.New(sp).Funcs(fmap).Parse(dtmpl)).Execute(&buf, l.params); err != nil { 146 | return err 147 | } 148 | 149 | // execute gofmt 150 | codes, err := format.Source(buf.Bytes()) 151 | if err != nil { 152 | return err 153 | } 154 | 155 | // TODO: Need warning if overwrite file? 156 | f, err := os.Create(sp) 157 | if err != nil { 158 | return err 159 | } 160 | defer func() { _ = f.Close() }() 161 | 162 | if _, err = f.Write(codes); err != nil { 163 | return err 164 | } 165 | return nil 166 | } 167 | 168 | // Run is entry point. 169 | func Run(args []string, outStream, errStream io.Writer) error { 170 | lgen, err := fill(args, outStream, errStream) 171 | if err != nil { 172 | return err 173 | } 174 | return lgen.run() 175 | } 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | lgen 2 | =================== 3 | 4 | [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)][license] 5 | [![GolangCI](https://golangci.com/badges/github.com/budougumi0617/lgen.svg)][golangci] 6 | 7 | [license]: https://github.com/budougumi0617/lgen/blob/master/LICENSE 8 | [golangci]:https://golangci.com/r/github.com/budougumi0617/lgen 9 | 10 | ## Description 11 | 12 | Generate boilerplates for layered architecture by your templates. 13 | 14 | At first, we prepare layered structure and templates. 15 | 16 | ```bash 17 | $ tree templates 18 | templates 19 | ├── repositories 20 | │   └── repository.tmpl 21 | ├── controllers 22 | │   └── controller.tmpl 23 | └── usercases 24 | └── usecase.tmpl 25 | ``` 26 | 27 | For instance, the usecase template is below. templates are written with `text/template`. 28 | 29 | ```go 30 | // templates/usercase/usecase.tmpl 31 | package usecase 32 | 33 | type {{ .Action | title}}{{ .Model | title }}Input struct{} 34 | 35 | type {{ .Action | title}}{{ .Model | title }}Result struct{} 36 | 37 | type {{ .Action | title}}{{ .Model | title }}Usecase interface { 38 | Run({{ .Action | title}}{{ .Model | title }}Input) ({{ .Action | title}}{{ .Model | title }}Result, error) 39 | } 40 | 41 | func New{{ .Action | title}}{{ .Model | title }}Usecase() {{ .Action | title}}{{ .Model | title }}Usecase { 42 | return &{{ .Action }}{{ .Model | title }}Usecase{ 43 | } 44 | } 45 | 46 | type {{ .Action }}{{ .Model | title }}Usecase struct {} 47 | 48 | func (u *{{ .Action }}{{ .Model | title }}Usecase) Run( 49 | in {{ .Action | title}}{{ .Model | title }}Input, 50 | ) ({{ .Action | title}}{{ .Model | title }}Result, error){ 51 | // Need to implement usercase logic 52 | return {{ .Action | title}}{{ .Model | title }}Result{ 53 | // Need to build result 54 | } 55 | } 56 | ``` 57 | 58 | Execute `lgen` with `Action` and `Model` strings. 59 | 60 | ```bash 61 | $ lgen -action Get -model User -template ./testdata -dist myproduct 62 | ``` 63 | 64 | The generated directories and files are below. 65 | 66 | 67 | ```bash 68 | $ tree myproduct 69 | myproduct 70 | ├── repositories 71 | │   └── get_user_repository.go 72 | ├── controllers 73 | │   └── get_user_controller.go 74 | └── usercases 75 | └── get_user_usecase.go 76 | 77 | 2 directories, 2 files 78 | ``` 79 | 80 | The get_user_usercase.go is below. We are enabled to write miltiple files by a command. 81 | 82 | ```go 83 | package usecase 84 | 85 | type GetUserInput struct{} 86 | 87 | type GetUserResult struct{} 88 | 89 | type GetUserUsecase interface { 90 | Run(GetUserInput) (GetUserResult, error) 91 | } 92 | 93 | func NewGetUserUsecase() GetUserUsecase { 94 | return &getUserUsecase{} 95 | } 96 | 97 | type getUserUsecase struct{} 98 | 99 | func (u *getUserUsecase) Run( 100 | in GetUserInput, 101 | ) (GetUserResult, error) { 102 | // Need to implement usercase logic 103 | return GetUserResult{ 104 | // Need to build result 105 | } 106 | } 107 | ``` 108 | 109 | 110 | ## Synopsis 111 | ``` 112 | $ lgen -a get -m user -template /your/templates/directory -dist /your/project/root/directory 113 | ``` 114 | 115 | ## Options 116 | 117 | ``` 118 | $ lgen -h 119 | Usage of /var/folders/sy/ls4cfp216x774g54brzl67yw0000gn/T/go-build403725816/b001/exe/lgen: 120 | -a string 121 | action name 122 | -action string 123 | action name 124 | -d string 125 | output directory (default "./") 126 | -dist string 127 | output directory 128 | -m string 129 | model name 130 | -model string 131 | model name 132 | -t string 133 | templates directory (default "./templates") 134 | -template string 135 | templates directory 136 | -v print version information and quit. 137 | -version 138 | print version information and quit. 139 | ``` 140 | 141 | ## Installation 142 | 143 | ``` 144 | $ go get github.com/budougumi0617/lgen 145 | ``` 146 | 147 | Built binaries are available on gihub releases. https://github.com/budougumi0617/lgen/releases 148 | 149 | ### MacOS 150 | If you want to install on MacOS, you can use Homebrew. 151 | ``` 152 | brew install budougumi0617/tap/lgen 153 | ``` 154 | 155 | ## Template rules 156 | The template must be created according to the following rules. 157 | 158 | 1. Base on `text/template` package. 159 | 1. template file extension is only `.tmpl`. 160 | 1. Varialbes are `Action`, and `Model` only. 161 | 1. `Functions` are supported below: 162 | 1. basic functions https://golang.org/pkg/text/template/#hdr-Functions 163 | 1. `title` - [`strings.ToTitle`](https://golang.org/pkg/strings/#ToTitle) 164 | 165 | ## Contribution 166 | 1. Fork ([https://github.com/budougumi0617/lgen/fork](https://github.com/budougumi0617/lgen/fork)) 167 | 2. Create a feature branch 168 | 3. Commit your changes 169 | 4. Rebase your local changes against the master branch 170 | 5. Run test suite with the `go test ./...` command and confirm that it passes 171 | 6. Run `gofmt -s` 172 | 7. Create new Pull Request 173 | 174 | 175 | ## License 176 | 177 | [MIT](https://github.com/budougumi0617/lgen/blob/master/LICENSE) 178 | 179 | ## Author 180 | [budougumi0617](https://github.com/budougumi0617) 181 | 182 | --------------------------------------------------------------------------------