├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── interact.go ├── main.go ├── structure.go └── templates ├── Dockerfile.tmpl ├── Makefile.tmpl ├── app.go.tmpl ├── config.yml.tmpl ├── docker-compose.yml.tmpl ├── go.mod.tmpl ├── hello.go.tmpl ├── log.go.tmpl └── ping.go.tmpl /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea 18 | 19 | go-wagen 20 | packrd 21 | *packr.go -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Max Ivanov 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 | install: 2 | go get -u github.com/gobuffalo/packr/v2/packr2 3 | 4 | build: 5 | ${GOBIN}/packr2 build 6 | 7 | clean: 8 | ${GOBIN}/packr2 clean 9 | 10 | build-linux: 11 | GOOS=linux GOARCH=amd64 ${GOBIN}/packr2 build 12 | 13 | build-win: 14 | GOOS=windows GOARCH=amd64 ${GOBIN}/packr2 build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-wagen 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/groovili/go-wagen)](https://goreportcard.com/report/github.com/groovili/go-wagen) 3 | 4 | Single binary **web application generator** for Go. 5 | 6 | **Creates boilerplate** and does routine on a project start. 7 | 8 | Less than in a minute you've got simple, clean and dockerized app: 9 | 10 | [![asciicast](https://asciinema.org/a/334826.svg)](https://asciinema.org/a/334826) 11 | 12 | #### Generates: 13 | * project layout, according to community best practices 14 | * Makefile for wrapping project related routines 15 | * Dockerfile and docker-compose for local development 16 | * containers for code, tests and [golangci-lint](https://github.com/golangci/golangci-lint) 17 | * configuration management with [viper](https://github.com/spf13/viper) 18 | * logging with [logrus](http://github.com/sirupsen/logrus) or [zap](https://github.com/uber-go/zap) 19 | * routing with [gorilla/mux](https://github.com/gorilla/mux) or [chi](github.com/go-chi/chi) 20 | * default health check, http handler and logger middleware 21 | 22 | **go-wagen is a starter pack** for typical web application in Golang. 23 | 24 | It doesn't aim to generalize the whole project workflow, push framework, or architecture. 25 | Consists of framework-agnostic components that are very common. 26 | 27 | All contributions, issues, requests or feedback are warmly welcome. 28 | 29 | ## Installation 30 | 31 | Install pre-built binary on [releases page](https://github.com/groovili/go-wagen/releases): 32 | 33 | 1. `curl -LJO https://github.com/groovili/go-wagen/releases/download/v1.0.0/go-wagen-osx.tar.gz` 34 | 2. `tar -f go-wagen-osx.tar.gz -x` 35 | 36 | or build from source code: 37 | 38 | 1. `git clone https://github.com/groovili/go-wagen && cd go-wagen` 39 | 2. `make install` - will install [packr](https://github.com/gobuffalo/packr) to wrap templates to binary 40 | 3. `make build` 41 | 42 | Binary doesn't need to be in `$GOPATH` and works without any dependencies. 43 | 44 | ## Usage 45 | 46 | 1. `./go-wagen --path=/absoule/path/to/project` and select dependencies 47 | 2. `cd /absoule/path/to/project` 48 | 3. `go mod vendor` 49 | 4. `make run` - will build and run container with code 50 | 5. `make test` - to run container with tests 51 | 6. `make lint` - to run linter for source code 52 | 53 | #### Structure 54 | 55 | Application generated with **go-wagen** will have following structure: 56 | ``` 57 | ├── Makefile 58 | ├── cmd 59 | │   └── example 60 | │   └── example.go - app entrypoint 61 | ├── config - config presets for each env of default pipline 62 | │   ├── app.dev.yml 63 | │   ├── app.local.yml 64 | │   └── app.yml 65 | ├── deploy 66 | │   ├── Dockerfile 67 | │   └── docker-compose.yml 68 | ├── go.mod 69 | ├── internal - for shared internal tools 70 | ├── server 71 | │   ├── handlers 72 | │   │   ├── hello.go 73 | │   │   └── ping.go 74 | │   └── middleware 75 | │   └── log.go 76 | ├── storage - db/storages 77 | └── vendor - downloaded modules 78 | ``` -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/groovili/go-wagen 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/fatih/color v1.9.0 7 | github.com/gobuffalo/packr/v2 v2.8.0 8 | github.com/karrick/godirwalk v1.15.6 // indirect 9 | github.com/rogpeppe/go-internal v1.6.0 // indirect 10 | github.com/sirupsen/logrus v1.6.0 11 | github.com/spf13/cobra v1.0.0 // indirect 12 | github.com/spf13/pflag v1.0.5 // indirect 13 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 // indirect 14 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a // indirect 15 | golang.org/x/sys v0.0.0-20200523222454-059865788121 // indirect 16 | golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= 4 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 5 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 6 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= 7 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 8 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 9 | github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= 10 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 11 | github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 12 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 13 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 14 | github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 15 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 16 | github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 17 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 19 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 20 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 21 | github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= 22 | github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 23 | github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= 24 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 25 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 26 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 27 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 28 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 29 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 30 | github.com/gobuffalo/logger v1.0.3 h1:YaXOTHNPCvkqqA7w05A4v0k2tCdpr+sgFlgINbQ6gqc= 31 | github.com/gobuffalo/logger v1.0.3/go.mod h1:SoeejUwldiS7ZsyCBphOGURmWdwUFXs0J7TCjEhjKxM= 32 | github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= 33 | github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= 34 | github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= 35 | github.com/gobuffalo/packr/v2 v2.8.0 h1:IULGd15bQL59ijXLxEvA5wlMxsmx/ZkQv9T282zNVIY= 36 | github.com/gobuffalo/packr/v2 v2.8.0/go.mod h1:PDk2k3vGevNE3SwVyVRgQCCXETC9SaONCNSXT1Q8M1g= 37 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 38 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 39 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 40 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 41 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 42 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 43 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 44 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 45 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 46 | github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 47 | github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= 48 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 49 | github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= 50 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 51 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 52 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 53 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 54 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 55 | github.com/karrick/godirwalk v1.15.3 h1:0a2pXOgtB16CqIqXTiT7+K9L73f74n/aNQUnH6Ortew= 56 | github.com/karrick/godirwalk v1.15.3/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= 57 | github.com/karrick/godirwalk v1.15.6 h1:Yf2mmR8TJy+8Fa0SuQVto5SYap6IF7lNVX4Jdl8G1qA= 58 | github.com/karrick/godirwalk v1.15.6/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk= 59 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 60 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 61 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 62 | github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= 63 | github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 64 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= 65 | github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 66 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 67 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 68 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 69 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 70 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 71 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 72 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 73 | github.com/markbates/errx v1.1.0 h1:QDFeR+UP95dO12JgW+tgi2UVfo0V8YBHiUIOaeBPiEI= 74 | github.com/markbates/errx v1.1.0/go.mod h1:PLa46Oex9KNbVDZhKel8v1OT7hD5JZ2eI7AHhA0wswc= 75 | github.com/markbates/oncer v1.0.0 h1:E83IaVAHygyndzPimgUYJjbshhDTALZyXxvk9FOlQRY= 76 | github.com/markbates/oncer v1.0.0/go.mod h1:Z59JA581E9GP6w96jai+TGqafHPW+cPfRxz2aSZ0mcI= 77 | github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= 78 | github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= 79 | github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= 80 | github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 81 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 82 | github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= 83 | github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= 84 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 85 | github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 86 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 87 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 88 | github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= 89 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 90 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 91 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 92 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 93 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 94 | github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= 95 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 96 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 97 | github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 98 | github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 99 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 100 | github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 101 | github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= 102 | github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= 103 | github.com/rogpeppe/go-internal v1.5.2 h1:qLvObTrvO/XRCqmkKxUlOBc48bI3efyDuAZe25QiF0w= 104 | github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 105 | github.com/rogpeppe/go-internal v1.6.0 h1:IZRgg4sfrDH7nsAD1Y/Nwj+GzIfEwpJSLjCaNC3SbsI= 106 | github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 107 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 108 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 109 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 110 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 111 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 112 | github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= 113 | github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= 114 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 115 | github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= 116 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 117 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= 118 | github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs= 119 | github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 120 | github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= 121 | github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= 122 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 123 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 124 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 125 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 126 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 127 | github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= 128 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 129 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 130 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 131 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 132 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 133 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 134 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 135 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 136 | github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= 137 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 138 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= 139 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 140 | go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 141 | go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= 142 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 143 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 144 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 145 | go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 146 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 147 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 148 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 149 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 150 | golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c h1:/nJuwDLoL/zrqY6gf57vxC+Pi+pZ8bfhpPkicO5H7W4= 151 | golang.org/x/crypto v0.0.0-20191122220453-ac88ee75c92c/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 152 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= 153 | golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 154 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 155 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 156 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 157 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 158 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 159 | golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 160 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 161 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 162 | golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 163 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 164 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 165 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 166 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 167 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 168 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 169 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 170 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 171 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 172 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= 173 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 174 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 175 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 176 | golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 177 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 178 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 179 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 180 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 181 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= 182 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 183 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 184 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299 h1:DYfZAGf2WMFjMxbgTjaC+2HC7NkNAQs+6Q8b9WEB/F4= 185 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 186 | golang.org/x/sys v0.0.0-20200523222454-059865788121 h1:rITEj+UZHYC927n8GT97eC3zrpzXdb/voyeOuVKS46o= 187 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 188 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 189 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 190 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 191 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 192 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 193 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 194 | golang.org/x/tools v0.0.0-20200308013534-11ec41452d41 h1:9Di9iYgOt9ThCipBxChBVhgNipDoE5mxO84rQV7D0FE= 195 | golang.org/x/tools v0.0.0-20200308013534-11ec41452d41/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 196 | golang.org/x/tools v0.0.0-20200519205726-57a9e4404bf7 h1:nm4zDh9WvH4jiuUpMY5RUsvOwrtTVVAsUaCdLW71hfY= 197 | golang.org/x/tools v0.0.0-20200519205726-57a9e4404bf7/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 198 | golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375 h1:SjQ2+AKWgZLc1xej6WSzL+Dfs5Uyd5xcZH1mGC411IA= 199 | golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 200 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 201 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 202 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 203 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 204 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 205 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 206 | google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 207 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 208 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 209 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 210 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 211 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 212 | gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= 213 | gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 214 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 215 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 216 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 217 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 218 | -------------------------------------------------------------------------------- /interact.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/fatih/color" 9 | ) 10 | 11 | type actionFunc func(answer string) error 12 | 13 | type action struct { 14 | Question string 15 | Validate actionFunc 16 | Action actionFunc 17 | } 18 | 19 | func userAction(a *action) error { 20 | printMsg(a.Question) 21 | 22 | scn := bufio.NewScanner(os.Stdin) 23 | for scn.Scan() { 24 | inp := scn.Text() 25 | if err := a.Validate(inp); err != nil { 26 | printErr(err.Error()) 27 | continue 28 | } 29 | 30 | if err := a.Action(inp); err != nil { 31 | return err 32 | } 33 | 34 | break 35 | } 36 | 37 | return nil 38 | } 39 | 40 | func printErr(msg string) { 41 | fmt.Fprint(os.Stderr, color.RedString("%s\r\n", msg)) 42 | } 43 | 44 | func printMsg(msg string) { 45 | fmt.Fprint(os.Stdin, fmt.Sprintf("%s\r\n", msg)) 46 | } 47 | 48 | func printSuccess(msg string) { 49 | fmt.Fprint(os.Stdin, color.GreenString("%s\r\n", msg)) 50 | } 51 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "path" 9 | "regexp" 10 | "strconv" 11 | "strings" 12 | 13 | packr "github.com/gobuffalo/packr/v2" 14 | ) 15 | 16 | func createStructure(app *application) { 17 | s := structure{ 18 | Box: packr.New("tplBox", "./templates"), 19 | App: app, 20 | Files: map[string]string{ 21 | fmt.Sprintf("%s%s", app.Path, "go.mod"): "go.mod.tmpl", 22 | fmt.Sprintf("%s%s", app.Path, "Makefile"): "Makefile.tmpl", 23 | }, 24 | Directories: make([]*dir, 0), 25 | } 26 | 27 | sep := string(os.PathSeparator) 28 | 29 | s.Directories = append(s.Directories, &dir{ 30 | Name: fmt.Sprintf("%s%s%s%s", app.Path, "cmd", sep, app), 31 | Files: map[string]string{ 32 | fmt.Sprintf("%s%s%s%s%s%s", app.Path, "cmd", sep, app, sep, fmt.Sprintf("%s.go", app)): "app.go.tmpl", 33 | }, 34 | }) 35 | 36 | s.Directories = append(s.Directories, &dir{ 37 | Name: fmt.Sprintf("%s%s", app.Path, "config"), 38 | Files: map[string]string{ 39 | fmt.Sprintf("%s%s%s%s", app.Path, "config", sep, "app.local.yml"): "config.yml.tmpl", 40 | fmt.Sprintf("%s%s%s%s", app.Path, "config", sep, "app.dev.yml"): "config.yml.tmpl", 41 | fmt.Sprintf("%s%s%s%s", app.Path, "config", sep, "app.yml"): "config.yml.tmpl", 42 | }, 43 | }) 44 | 45 | s.Directories = append(s.Directories, &dir{ 46 | Name: fmt.Sprintf("%s%s", app.Path, "deploy"), 47 | Files: map[string]string{ 48 | fmt.Sprintf("%s%s%s%s", app.Path, "deploy", sep, "Dockerfile"): "Dockerfile.tmpl", 49 | fmt.Sprintf("%s%s%s%s", app.Path, "deploy", sep, "docker-compose.yml"): "docker-compose.yml.tmpl", 50 | }, 51 | }) 52 | 53 | s.Directories = append(s.Directories, &dir{ 54 | Name: fmt.Sprintf("%s%s", app.Path, "internal"), 55 | }) 56 | 57 | s.Directories = append(s.Directories, &dir{ 58 | Name: fmt.Sprintf("%s%s", app.Path, "vendor"), 59 | }) 60 | 61 | s.Directories = append(s.Directories, &dir{ 62 | Name: fmt.Sprintf("%s%s", app.Path, "storage"), 63 | }) 64 | 65 | s.Directories = append(s.Directories, &dir{ 66 | Name: fmt.Sprintf("%s%s%s%s", app.Path, "server", sep, "handlers"), 67 | Files: map[string]string{ 68 | fmt.Sprintf("%s%s%s%s%s%s", app.Path, "server", sep, "handlers", sep, "ping.go"): "ping.go.tmpl", 69 | fmt.Sprintf("%s%s%s%s%s%s", app.Path, "server", sep, "handlers", sep, "hello.go"): "hello.go.tmpl", 70 | }, 71 | }) 72 | 73 | s.Directories = append(s.Directories, &dir{ 74 | Name: fmt.Sprintf("%s%s%s%s", app.Path, "server", sep, "middleware"), 75 | Files: map[string]string{ 76 | fmt.Sprintf("%s%s%s%s%s%s", app.Path, "server", sep, "middleware", sep, "log.go"): "log.go.tmpl", 77 | }, 78 | }) 79 | 80 | s.create() 81 | } 82 | 83 | func main() { 84 | var pathToApp string 85 | 86 | flag.StringVar(&pathToApp, "path", "", "Specify absolute path to app") 87 | flag.Parse() 88 | 89 | if len(pathToApp) == 0 { 90 | printErr("--path is required") 91 | os.Exit(1) 92 | } 93 | 94 | if !path.IsAbs(pathToApp) { 95 | printErr("--path should be absolute") 96 | os.Exit(1) 97 | } 98 | 99 | pathToApp = path.Clean(pathToApp) 100 | 101 | stat, err := os.Stat(pathToApp) 102 | if err != nil && !os.IsNotExist(err) { 103 | printErr(err.Error()) 104 | os.Exit(1) 105 | } else if os.IsNotExist(err) { 106 | err := userAction(&action{ 107 | Question: fmt.Sprintf("Dir %s doesn't exist. Create? [y/N]:", pathToApp), 108 | Validate: func(answer string) error { 109 | a := strings.ToLower(answer) 110 | if a != "y" && a != "n" { 111 | return errors.New("Invalid option. Only [y/N] available:") 112 | } 113 | 114 | return nil 115 | }, 116 | Action: func(answer string) error { 117 | if strings.ToLower(answer) == "n" { 118 | return errors.New("Can't continue without app dir") 119 | } 120 | 121 | return os.Mkdir(pathToApp, permMode) 122 | }, 123 | }) 124 | 125 | if err != nil { 126 | printErr(err.Error()) 127 | os.Exit(0) 128 | } 129 | 130 | printSuccess(fmt.Sprintf("Created %s", pathToApp)) 131 | 132 | stat, err = os.Stat(pathToApp) 133 | if err != nil { 134 | printErr(err.Error()) 135 | os.Exit(1) 136 | } 137 | } 138 | 139 | if !stat.IsDir() { 140 | printErr("--path should be a directory") 141 | os.Exit(1) 142 | } 143 | 144 | app := new(application) 145 | app.Path = fmt.Sprintf("%s%s", pathToApp, string(os.PathSeparator)) 146 | 147 | err = userAction(&action{ 148 | Question: "Enter app name [a-z0-9_]:", 149 | Validate: func(answer string) error { 150 | err := errors.New("App name should be in lower snake case [a-z0-9_]") 151 | if len(answer) == 0 { 152 | return err 153 | } 154 | r := regexp.MustCompile("^[a-z0-9_]*$") 155 | if !r.MatchString(answer) { 156 | return err 157 | } 158 | 159 | return nil 160 | }, 161 | Action: func(answer string) error { 162 | app.Name = answer 163 | 164 | return nil 165 | }, 166 | }) 167 | if err != nil { 168 | printErr(err.Error()) 169 | os.Exit(1) 170 | } 171 | 172 | err = userAction(&action{ 173 | Question: "Select logger:\r\n[1]: github.com/Sirupsen/logrus\r\n[2]: github.com/uber-go/zap", 174 | Validate: func(answer string) error { 175 | i, err := strconv.Atoi(answer) 176 | if err != nil { 177 | return err 178 | } 179 | 180 | if i != 1 && i != 2 { 181 | return errors.New("Invalid choice") 182 | } 183 | 184 | return nil 185 | }, 186 | Action: func(answer string) error { 187 | i, err := strconv.Atoi(answer) 188 | if err != nil { 189 | return err 190 | } 191 | 192 | switch i { 193 | case 1: 194 | app.Logger = "logrus" 195 | app.LoggerPackage = "github.com/sirupsen/logrus" 196 | case 2: 197 | app.Logger = "zap" 198 | app.LoggerPackage = "go.uber.org/zap" 199 | } 200 | 201 | return nil 202 | }, 203 | }) 204 | 205 | err = userAction(&action{ 206 | Question: "Select router:\r\n[1]: github.com/gorilla/mux\r\n[2]: github.com/go-chi/chi", 207 | Validate: func(answer string) error { 208 | i, err := strconv.Atoi(answer) 209 | if err != nil { 210 | return err 211 | } 212 | 213 | if i != 1 && i != 2 { 214 | return errors.New("Invalid choice") 215 | } 216 | 217 | return nil 218 | }, 219 | Action: func(answer string) error { 220 | i, err := strconv.Atoi(answer) 221 | if err != nil { 222 | return err 223 | } 224 | 225 | switch i { 226 | case 1: 227 | app.Router = "mux" 228 | app.RouterPackage = "github.com/gorilla/mux" 229 | case 2: 230 | app.Router = "chi" 231 | app.RouterPackage = "github.com/go-chi/chi" 232 | } 233 | 234 | return nil 235 | }, 236 | }) 237 | 238 | printMsg(fmt.Sprintf("Creating %s application..", app)) 239 | 240 | createStructure(app) 241 | 242 | printSuccess("Success!") 243 | } 244 | -------------------------------------------------------------------------------- /structure.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path" 7 | "text/template" 8 | 9 | "github.com/gobuffalo/packr/v2" 10 | ) 11 | 12 | const ( 13 | permMode = 0764 14 | ) 15 | 16 | type application struct { 17 | Path string 18 | Name string 19 | Logger string 20 | LoggerPackage string 21 | Router string 22 | RouterPackage string 23 | } 24 | 25 | func (a *application) String() string { 26 | return a.Name 27 | } 28 | 29 | type dir struct { 30 | Name string 31 | Files map[string]string // [name]template 32 | } 33 | 34 | type structure struct { 35 | Box *packr.Box 36 | App *application 37 | Files map[string]string // [name]template 38 | Directories []*dir 39 | } 40 | 41 | func (s *structure) create() { 42 | for file, temp := range s.Files { 43 | s.fileFromTemplate(file, temp, s.App) 44 | } 45 | 46 | for _, d := range s.Directories { 47 | s.makeDir(d.Name) 48 | 49 | for file, temp := range d.Files { 50 | s.fileFromTemplate(file, temp, s.App) 51 | } 52 | } 53 | } 54 | 55 | func (s *structure) makeDir(path string) { 56 | err := os.MkdirAll(path, permMode) 57 | if err != nil { 58 | printErr(err.Error()) 59 | os.Exit(1) 60 | } 61 | 62 | printSuccess(fmt.Sprintf("Created %s", path)) 63 | } 64 | 65 | func (s *structure) fileFromTemplate(filePath, templatePath string, args interface{}) { 66 | f, err := os.Create(filePath) 67 | if err != nil { 68 | printErr(err.Error()) 69 | os.Exit(1) 70 | } 71 | defer f.Close() 72 | 73 | txt, err := s.Box.FindString(templatePath) 74 | if err != nil { 75 | printErr(err.Error()) 76 | os.Exit(1) 77 | } 78 | 79 | tpl, err := template.New(path.Base(templatePath)).Parse(txt) 80 | if err != nil { 81 | printErr(err.Error()) 82 | os.Exit(1) 83 | } 84 | 85 | if err = tpl.Execute(f, args); err != nil { 86 | printErr(err.Error()) 87 | os.Exit(1) 88 | } 89 | 90 | printSuccess(fmt.Sprintf("Created %s", filePath)) 91 | } 92 | -------------------------------------------------------------------------------- /templates/Dockerfile.tmpl: -------------------------------------------------------------------------------- 1 | FROM golang:alpine 2 | 3 | RUN mkdir /go/src/{{.Name}} 4 | 5 | WORKDIR /go/src/{{.Name}} 6 | 7 | COPY . ./ 8 | 9 | RUN go env 10 | 11 | RUN go build -o ./cmd/{{.Name}}/{{.Name}} ./cmd/{{.Name}}/{{.Name}}.go 12 | 13 | CMD ./cmd/{{.Name}}/{{.Name}} -------------------------------------------------------------------------------- /templates/Makefile.tmpl: -------------------------------------------------------------------------------- 1 | build: 2 | docker-compose -f ./deploy/docker-compose.yml build --no-cache {{.Name}} 3 | 4 | run: 5 | docker-compose -f ./deploy/docker-compose.yml up --remove-orphans {{.Name}} 6 | 7 | test: 8 | docker-compose -f ./deploy/docker-compose.yml up tests 9 | 10 | lint: 11 | docker-compose -f ./deploy/docker-compose.yml up linter 12 | 13 | down: 14 | docker-compose -f deploy/docker-compose.yml down -------------------------------------------------------------------------------- /templates/app.go.tmpl: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "os/signal" 8 | "syscall" 9 | 10 | "github.com/spf13/viper" 11 | log "{{.LoggerPackage}}" 12 | router "{{.RouterPackage}}" 13 | 14 | "{{.Name}}/server/handlers" 15 | "{{.Name}}/server/middleware" 16 | ) 17 | 18 | const keyENV = "APP_ENV" 19 | 20 | var version = "dev" 21 | 22 | func readConfig(env string) { 23 | if len(env) > 0 { 24 | env = fmt.Sprintf(".%s", env) 25 | } 26 | 27 | viper.SetConfigFile(fmt.Sprintf("./config/app%s.yml", env)) 28 | viper.SetConfigType("yaml") 29 | 30 | if err := viper.ReadInConfig(); err != nil { 31 | panic(err) 32 | } 33 | } 34 | 35 | func main() { 36 | app := "{{.Name}}" 37 | env := os.Getenv(keyENV) 38 | 39 | readConfig(env) 40 | 41 | {{if eq .Logger "logrus" }}logger := log.New(){{else}}logger, err := log.NewDevelopment() 42 | if err != nil { 43 | panic(err) 44 | }{{end}} 45 | 46 | logger.Info(fmt.Sprintf("Starting %s on %s env..", app, env)) 47 | 48 | host, port := viper.GetString("host"), viper.GetString("port") 49 | 50 | stop := make(chan os.Signal, 1) 51 | signal.Notify(stop, os.Interrupt, syscall.SIGINT) 52 | 53 | r := router.NewRouter() 54 | 55 | lm := middleware.NewLog(logger, true) 56 | r.Use(lm.Handler) 57 | 58 | ping := handlers.NewPing(logger, version) 59 | r.{{ if eq .Router "mux"}}HandleFunc{{else}}Get{{end}}("/ping", ping.Handler) 60 | 61 | hello := handlers.NewHello(logger) 62 | r.{{ if eq .Router "mux"}}HandleFunc{{else}}Get{{end}}("/", hello.Handler) 63 | 64 | httpErr := make(chan error, 1) 65 | go func() { 66 | logger.Info(fmt.Sprintf("Started server on %s:%s..", host, port)) 67 | httpErr <- http.ListenAndServe(fmt.Sprintf("%s:%s", host, port), r) 68 | }() 69 | 70 | select { 71 | case err := <-httpErr: 72 | logger.Error(err.Error()) 73 | case <-stop: 74 | logger.Info("Stopped via signal") 75 | } 76 | 77 | logger.Info(fmt.Sprintf("Stopping %s..", app)) 78 | } 79 | -------------------------------------------------------------------------------- /templates/config.yml.tmpl: -------------------------------------------------------------------------------- 1 | host: 0.0.0.0 2 | port: 8080 -------------------------------------------------------------------------------- /templates/docker-compose.yml.tmpl: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | 3 | services: 4 | {{.Name}}: 5 | container_name: {{.Name}} 6 | build: 7 | context: .. 8 | dockerfile: ./deploy/Dockerfile 9 | working_dir: /go/src/{{.Name}} 10 | entrypoint: go run ./cmd/{{.Name}}/{{.Name}}.go 11 | environment: 12 | - APP_ENV=local 13 | stop_signal: SIGINT 14 | volumes: 15 | - ./..:/go/src/{{.Name}} 16 | ports: 17 | - 8080:8080 18 | networks: 19 | - {{.Name}}-net 20 | 21 | tests: 22 | build: 23 | context: .. 24 | dockerfile: ./deploy/Dockerfile 25 | working_dir: /var/www/{{.Name}} 26 | command: go test -v ./... 27 | volumes: 28 | - ./..:/var/www/{{.Name}} 29 | 30 | linter: 31 | image: golangci/golangci-lint:latest 32 | working_dir: /app 33 | command: golangci-lint run -v 34 | volumes: 35 | - ./..:/app 36 | 37 | networks: 38 | {{.Name}}-net: 39 | driver: bridge -------------------------------------------------------------------------------- /templates/go.mod.tmpl: -------------------------------------------------------------------------------- 1 | module {{.Name}} 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/spf13/viper latest 7 | {{.LoggerPackage}} latest 8 | {{.RouterPackage}} latest 9 | ) -------------------------------------------------------------------------------- /templates/hello.go.tmpl: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "net/http" 5 | 6 | log "{{.LoggerPackage}}" 7 | ) 8 | 9 | type Hello struct { 10 | l *log.Logger 11 | } 12 | 13 | func NewHello(l *log.Logger) *Hello{ 14 | return &Hello{ 15 | l: l, 16 | } 17 | } 18 | 19 | func (h *Hello) Handler(w http.ResponseWriter, r *http.Request) { 20 | if r.Method != http.MethodGet { 21 | http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 22 | return 23 | } 24 | 25 | _, err := w.Write([]byte("Hello World!")) 26 | if err != nil { 27 | h.l.Warn(err.Error()) 28 | http.Error(w, err.Error(), http.StatusInternalServerError) 29 | return 30 | } 31 | } -------------------------------------------------------------------------------- /templates/log.go.tmpl: -------------------------------------------------------------------------------- 1 | package middleware 2 | 3 | import( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | 9 | log "{{.LoggerPackage}}" 10 | ) 11 | 12 | type Log struct { 13 | l *log.Logger 14 | printBody bool 15 | } 16 | 17 | func NewLog(l *log.Logger, printBody bool) *Log{ 18 | return &Log{ 19 | l:l, 20 | printBody: printBody, 21 | } 22 | } 23 | 24 | func (l *Log) Handler (next http.Handler) http.Handler { 25 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 | entry := fmt.Sprintf("%s %s", r.Method, r.URL.RequestURI()) 27 | if l.printBody { 28 | b, err := ioutil.ReadAll(r.Body) 29 | if err != nil { 30 | http.Error(w, err.Error(), http.StatusInternalServerError) 31 | return 32 | } 33 | r.Body.Close() 34 | 35 | r.Body = ioutil.NopCloser(bytes.NewReader(b)) 36 | 37 | entry = fmt.Sprintf("%s %s", entry, string(b)) 38 | } 39 | 40 | l.l.Info(entry) 41 | 42 | next.ServeHTTP(w, r) 43 | }) 44 | } -------------------------------------------------------------------------------- /templates/ping.go.tmpl: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | log "{{.LoggerPackage}}" 8 | ) 9 | 10 | type Ping struct { 11 | l *log.Logger 12 | semVer string 13 | } 14 | 15 | func NewPing(l *log.Logger, version string) *Ping{ 16 | return &Ping{ 17 | l: l, 18 | semVer: version, 19 | } 20 | } 21 | 22 | func (p *Ping) Handler(w http.ResponseWriter, r *http.Request) { 23 | if r.Method != http.MethodGet { 24 | http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) 25 | return 26 | } 27 | 28 | err := json.NewEncoder(w).Encode(map[string]string{ 29 | "version": p.semVer, 30 | }) 31 | if err != nil { 32 | p.l.Warn(err.Error()) 33 | 34 | http.Error(w, "Internal Server Error", http.StatusInternalServerError) 35 | return 36 | } 37 | } --------------------------------------------------------------------------------