Initial commit
This commit is contained in:
commit
b7bdd7bbea
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/coverage
|
||||
/bin
|
||||
rice-box.go
|
25
Makefile
Normal file
25
Makefile
Normal file
@ -0,0 +1,25 @@
|
||||
build:
|
||||
CGO_ENABLED=0 go build -o ./bin/scaffold ./cmd/scaffold
|
||||
|
||||
test:
|
||||
go clean -testcache
|
||||
go test -v -race ./...
|
||||
|
||||
watch:
|
||||
modd
|
||||
|
||||
deps:
|
||||
go get -u golang.org/x/tools/cmd/godoc
|
||||
go get -u github.com/cortesi/modd/cmd/modd
|
||||
go get -u github.com/golangci/golangci-lint/cmd/golangci-lint
|
||||
|
||||
coverage:
|
||||
@script/coverage
|
||||
|
||||
lint:
|
||||
golangci-lint run --tests=false --enable-all
|
||||
|
||||
clean:
|
||||
rm -rf ./bin ./release ./coverage
|
||||
|
||||
.PHONY: test clean lint coverage doc
|
21
cmd/scaffold/main.go
Normal file
21
cmd/scaffold/main.go
Normal file
@ -0,0 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gitlab.com/wpetit/scaffold/internal/command"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Usage: "generate source code for goweb based projects",
|
||||
Commands: command.All(),
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
19
go.mod
Normal file
19
go.mod
Normal file
@ -0,0 +1,19 @@
|
||||
module gitlab.com/wpetit/scaffold
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/google/uuid v1.1.1 // indirect
|
||||
github.com/huandu/xstrings v1.3.0 // indirect
|
||||
github.com/imdario/mergo v0.3.8 // indirect
|
||||
github.com/manifoldco/promptui v0.7.0
|
||||
github.com/mitchellh/copystructure v1.0.0 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/urfave/cli/v2 v2.1.1
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 // indirect
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1
|
||||
)
|
106
go.sum
Normal file
106
go.sum
Normal file
@ -0,0 +1,106 @@
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg=
|
||||
github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
|
||||
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
|
||||
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/huandu/xstrings v1.3.0 h1:gvV6jG9dTgFEncxo+AF7PH6MZXi/vZl25owA/8Dg8Wo=
|
||||
github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ=
|
||||
github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4=
|
||||
github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
|
||||
github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
|
||||
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
|
||||
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
|
||||
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6 h1:Sy5bstxEqwwbYs6n0/pBuxKENqOeZUgD45Gp3Q3pqLg=
|
||||
golang.org/x/crypto v0.0.0-20200214034016-1d94cc7ab1c6/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80 h1:Ao/3l156eZf2AW5wK8a7/smtodRU+gha3+BeqJ69lRk=
|
||||
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e h1:D5TXcfTk7xF7hvieo4QErS3qqCB4teTffacDWr7CI+0=
|
||||
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg=
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
|
||||
gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE=
|
||||
gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
|
||||
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
|
||||
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
9
internal/command/all.go
Normal file
9
internal/command/all.go
Normal file
@ -0,0 +1,9 @@
|
||||
package command
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
func All() []*cli.Command {
|
||||
return []*cli.Command{
|
||||
newProjectCommand(),
|
||||
}
|
||||
}
|
139
internal/command/new_project.go
Normal file
139
internal/command/new_project.go
Normal file
@ -0,0 +1,139 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"gitlab.com/wpetit/scaffold/internal/template"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"gitlab.com/wpetit/scaffold/internal/project"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newProjectCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "new",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "generate a new project from a given template url",
|
||||
ArgsUsage: "<URL>",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "directory",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Set destination to `DIR`",
|
||||
Value: "./",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "The scaffold manifest `FILE`",
|
||||
Value: "scaffold.yml",
|
||||
},
|
||||
},
|
||||
Action: newProjectAction,
|
||||
}
|
||||
}
|
||||
|
||||
func newProjectAction(c *cli.Context) error {
|
||||
rawURL := c.Args().First()
|
||||
|
||||
projectURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse url")
|
||||
}
|
||||
|
||||
availableFetchers := []project.Fetcher{
|
||||
project.NewGitFetcher(),
|
||||
project.NewLocalFetcher(),
|
||||
}
|
||||
|
||||
var fetcher project.Fetcher
|
||||
|
||||
for _, f := range availableFetchers {
|
||||
if f.Match(projectURL) {
|
||||
fetcher = f
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
vfs, err := fetcher.Fetch(projectURL)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not fetch project")
|
||||
}
|
||||
|
||||
manifestFile := c.String("manifest")
|
||||
|
||||
manifestStat, err := vfs.Stat(manifestFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return errors.Wrap(err, "could not stat manifest file")
|
||||
}
|
||||
|
||||
templateData := make(map[string]interface{})
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
log.Println("Could not find scaffold manifest.")
|
||||
} else {
|
||||
if manifestStat.IsDir() {
|
||||
return errors.New("scaffold manifest is not a file")
|
||||
}
|
||||
|
||||
log.Println("Loading template scaffold manifest...")
|
||||
}
|
||||
|
||||
directory := c.String("directory")
|
||||
|
||||
return template.CopyDir(vfs, ".", directory, &template.Option{
|
||||
TemplateData: templateData,
|
||||
TemplateExt: ".gotpl",
|
||||
IgnorePatterns: []string{manifestFile},
|
||||
})
|
||||
}
|
||||
|
||||
func promptForProjectName() (string, error) {
|
||||
validate := func(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("Project name cannot be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Project Name",
|
||||
Validate: validate,
|
||||
}
|
||||
|
||||
return prompt.Run()
|
||||
}
|
||||
|
||||
func promptForProjectNamespace() (string, error) {
|
||||
validate := func(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("Project namespace cannot be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Project namespace",
|
||||
Validate: validate,
|
||||
}
|
||||
|
||||
return prompt.Run()
|
||||
}
|
||||
|
||||
func promptForProjectType() (string, error) {
|
||||
prompt := promptui.Select{
|
||||
Label: "Project Type",
|
||||
Items: []string{"web"},
|
||||
}
|
||||
|
||||
_, result, err := prompt.Run()
|
||||
|
||||
return result, err
|
||||
}
|
82
internal/fs/walk.go
Normal file
82
internal/fs/walk.go
Normal file
@ -0,0 +1,82 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
vfs "gopkg.in/src-d/go-billy.v4"
|
||||
)
|
||||
|
||||
// Walk walks the file tree rooted at root, calling walkFunc for each file or
|
||||
// directory in the tree, including root. All errors that arise visiting files
|
||||
// and directories are filtered by walkFn. The files are walked in lexical
|
||||
// order, which makes the output deterministic but means that for very
|
||||
// large directories Walk can be inefficient.
|
||||
// Walk does not follow symbolic links.
|
||||
func Walk(fs vfs.Filesystem, root string, walkFunc filepath.WalkFunc) error {
|
||||
info, err := fs.Lstat(root)
|
||||
if err != nil {
|
||||
err = walkFunc(root, nil, err)
|
||||
} else {
|
||||
err = walk(fs, root, info, walkFunc)
|
||||
}
|
||||
if err == filepath.SkipDir {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// readDirNames reads the directory named by dirname and returns
|
||||
// a sorted list of directory entries.
|
||||
func readDirNames(fs vfs.Filesystem, dirname string) ([]string, error) {
|
||||
infos, err := fs.ReadDir(dirname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names := make([]string, 0, len(infos))
|
||||
for _, info := range infos {
|
||||
names = append(names, info.Name())
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// walk recursively descends path, calling walkFunc.
|
||||
func walk(fs vfs.Filesystem, path string, info os.FileInfo, walkFunc filepath.WalkFunc) error {
|
||||
if !info.IsDir() {
|
||||
return walkFunc(path, info, nil)
|
||||
}
|
||||
|
||||
names, err := readDirNames(fs, path)
|
||||
err1 := walkFunc(path, info, err)
|
||||
// If err != nil, walk can't walk into this directory.
|
||||
// err1 != nil means walkFn want walk to skip this directory or stop walking.
|
||||
// Therefore, if one of err and err1 isn't nil, walk will return.
|
||||
if err != nil || err1 != nil {
|
||||
// The caller's behavior is controlled by the return value, which is decided
|
||||
// by walkFn. walkFn may ignore err and return nil.
|
||||
// If walkFn returns SkipDir, it will be handled by the caller.
|
||||
// So walk should return whatever walkFn returns.
|
||||
return err1
|
||||
}
|
||||
|
||||
for _, name := range names {
|
||||
filename := strings.Join([]string{path, name}, string(os.PathSeparator))
|
||||
fileInfo, err := fs.Lstat(filename)
|
||||
if err != nil {
|
||||
if err := walkFunc(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err = walk(fs, filename, fileInfo, walkFunc)
|
||||
if err != nil {
|
||||
if !fileInfo.IsDir() || err != filepath.SkipDir {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
12
internal/project/fetcher.go
Normal file
12
internal/project/fetcher.go
Normal file
@ -0,0 +1,12 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"gopkg.in/src-d/go-billy.v4"
|
||||
)
|
||||
|
||||
type Fetcher interface {
|
||||
Match(*url.URL) bool
|
||||
Fetch(*url.URL) (billy.Filesystem, error)
|
||||
}
|
98
internal/project/git.go
Normal file
98
internal/project/git.go
Normal file
@ -0,0 +1,98 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/src-d/go-billy.v4"
|
||||
"gopkg.in/src-d/go-billy.v4/memfs"
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing/transport"
|
||||
"gopkg.in/src-d/go-git.v4/plumbing/transport/http"
|
||||
"gopkg.in/src-d/go-git.v4/storage/memory"
|
||||
)
|
||||
|
||||
const GitScheme = "git"
|
||||
|
||||
type GitFetcher struct{}
|
||||
|
||||
func (f *GitFetcher) Fetch(url *url.URL) (billy.Filesystem, error) {
|
||||
fs := memfs.New()
|
||||
|
||||
var auth transport.AuthMethod
|
||||
|
||||
if user := url.User; user != nil {
|
||||
user := url.User
|
||||
basicAuth := &http.BasicAuth{
|
||||
Username: user.Username(),
|
||||
}
|
||||
|
||||
password, exists := user.Password()
|
||||
if exists {
|
||||
basicAuth.Password = password
|
||||
}
|
||||
|
||||
auth = basicAuth
|
||||
}
|
||||
|
||||
if url.Scheme == "" {
|
||||
url.Scheme = "https"
|
||||
}
|
||||
|
||||
branchName := plumbing.NewBranchReferenceName("master")
|
||||
if url.Fragment != "" {
|
||||
branchName = plumbing.NewBranchReferenceName(url.Fragment)
|
||||
url.Fragment = ""
|
||||
}
|
||||
|
||||
log.Printf("Cloning repository '%s'...", url.String())
|
||||
|
||||
repo, err := git.Clone(memory.NewStorage(), fs, &git.CloneOptions{
|
||||
URL: url.String(),
|
||||
Auth: auth,
|
||||
ReferenceName: branchName,
|
||||
})
|
||||
if err != nil {
|
||||
if err == transport.ErrRepositoryNotFound {
|
||||
return nil, errors.Wrapf(err, "could not find repository")
|
||||
}
|
||||
|
||||
return nil, errors.Wrap(err, "could not clone repository")
|
||||
}
|
||||
|
||||
worktree, err := repo.Worktree()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "could not retrieve worktree")
|
||||
}
|
||||
|
||||
log.Printf("Checking out branch '%s'...", branchName)
|
||||
|
||||
err = worktree.Checkout(&git.CheckoutOptions{
|
||||
Force: true,
|
||||
Branch: branchName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "could not checkout branch '%s'", branchName)
|
||||
}
|
||||
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
func (f *GitFetcher) Match(url *url.URL) bool {
|
||||
if url.Scheme == GitScheme {
|
||||
return true
|
||||
}
|
||||
|
||||
isFilesystemPath := isFilesystemPath(url.Path)
|
||||
if url.Scheme == "" && !isFilesystemPath {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func NewGitFetcher() *GitFetcher {
|
||||
return &GitFetcher{}
|
||||
}
|
51
internal/project/git_test.go
Normal file
51
internal/project/git_test.go
Normal file
@ -0,0 +1,51 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGitFetcher(t *testing.T) {
|
||||
git := NewGitFetcher()
|
||||
|
||||
projectURL, err := url.Parse("forge.cadoles.com/wpetit/goweb")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fs, err := git.Fetch(projectURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if fs == nil {
|
||||
t.Fatal("fs should not be nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitMatch(t *testing.T) {
|
||||
testCases := []struct {
|
||||
RawURL string
|
||||
ShouldMatch bool
|
||||
}{
|
||||
{"git://wpetit/scaffold", true},
|
||||
{"forge.cadoles.com/wpetit/scaffold", true},
|
||||
}
|
||||
|
||||
git := NewGitFetcher()
|
||||
|
||||
for _, tc := range testCases {
|
||||
func(rawURL string, shouldMatch bool) {
|
||||
t.Run(rawURL, func(t *testing.T) {
|
||||
projectURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if e, g := shouldMatch, git.Match(projectURL); g != e {
|
||||
t.Errorf("git.Match(url): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
})
|
||||
}(tc.RawURL, tc.ShouldMatch)
|
||||
}
|
||||
}
|
33
internal/project/local.go
Normal file
33
internal/project/local.go
Normal file
@ -0,0 +1,33 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/src-d/go-billy.v4"
|
||||
)
|
||||
|
||||
const LocalScheme = "local"
|
||||
const FileScheme = "file"
|
||||
|
||||
type LocalFetcher struct{}
|
||||
|
||||
func (f *LocalFetcher) Fetch(url *url.URL) (billy.Filesystem, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *LocalFetcher) Match(url *url.URL) bool {
|
||||
if url.Scheme == LocalScheme || url.Scheme == FileScheme {
|
||||
return true
|
||||
}
|
||||
|
||||
return isFilesystemPath(url.Path)
|
||||
}
|
||||
|
||||
func NewLocalFetcher() *LocalFetcher {
|
||||
return &LocalFetcher{}
|
||||
}
|
||||
|
||||
func isFilesystemPath(path string) bool {
|
||||
return strings.HasPrefix(path, "./") || strings.HasPrefix(path, "/")
|
||||
}
|
33
internal/project/local_test.go
Normal file
33
internal/project/local_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLocalMatch(t *testing.T) {
|
||||
testCases := []struct {
|
||||
RawURL string
|
||||
ShouldMatch bool
|
||||
}{
|
||||
{"local://wpetit/scaffold", true},
|
||||
{"./forge.cadoles.com/wpetit/scaffold", true},
|
||||
}
|
||||
|
||||
local := NewLocalFetcher()
|
||||
|
||||
for _, tc := range testCases {
|
||||
func(rawURL string, shouldMatch bool) {
|
||||
t.Run(rawURL, func(t *testing.T) {
|
||||
projectURL, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if e, g := shouldMatch, local.Match(projectURL); g != e {
|
||||
t.Errorf("local.Match(url): expected '%v', got '%v'", e, g)
|
||||
}
|
||||
})
|
||||
}(tc.RawURL, tc.ShouldMatch)
|
||||
}
|
||||
}
|
170
internal/template/copy.go
Normal file
170
internal/template/copy.go
Normal file
@ -0,0 +1,170 @@
|
||||
package template
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"gitlab.com/wpetit/scaffold/internal/fs"
|
||||
"gopkg.in/src-d/go-billy.v4"
|
||||
|
||||
"github.com/Masterminds/sprig"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func CopyDir(vfs billy.Filesystem, baseDir string, dst string, opts *Option) error {
|
||||
if opts == nil {
|
||||
opts = &Option{}
|
||||
}
|
||||
|
||||
baseDir = filepath.Clean(baseDir)
|
||||
dst = filepath.Clean(dst)
|
||||
|
||||
_, err := os.Stat(dst)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(dst, 0755); err != nil {
|
||||
return errors.Wrapf(err, "could not create directory '%s'", dst)
|
||||
}
|
||||
|
||||
err = fs.Walk(vfs, baseDir, func(srcPath string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if srcPath == baseDir {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, p := range opts.IgnorePatterns {
|
||||
match, err := filepath.Match(p, srcPath)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not match ignored file")
|
||||
}
|
||||
if match {
|
||||
log.Printf("Ignoring %s.", srcPath)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
relSrcPath, err := filepath.Rel(baseDir, srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dstPath := filepath.Join(dst, relSrcPath)
|
||||
|
||||
log.Printf("relSrcPath: %s, dstPath: %s", relSrcPath, dstPath)
|
||||
|
||||
if info.IsDir() {
|
||||
log.Printf("creating dir '%s'", dstPath)
|
||||
if err := os.MkdirAll(dstPath, 0755); err != nil {
|
||||
return errors.Wrapf(err, "could not create directory '%s'", dstPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
err = CopyFile(vfs, srcPath, dstPath, opts)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not copy file '%s'", srcPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not walk source directory '%s'", baseDir)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CopyFile(vfs billy.Filesystem, src, dst string, opts *Option) (err error) {
|
||||
if opts == nil {
|
||||
opts = &Option{}
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(src, opts.TemplateExt) {
|
||||
return copyFile(vfs, src, dst)
|
||||
}
|
||||
|
||||
in, err := vfs.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
templateData, err := ioutil.ReadAll(in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New(filepath.Base(src)).Funcs(sprig.TxtFuncMap()).Parse(string(templateData))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dst = strings.TrimSuffix(dst, opts.TemplateExt)
|
||||
|
||||
log.Printf("templating file from '%s' to '%s'", src, dst)
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if e := out.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
|
||||
opts.TemplateData["SourceFile"] = src
|
||||
opts.TemplateData["DestFile"] = dst
|
||||
|
||||
if err := tmpl.Execute(out, opts.TemplateData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFile(vfs billy.Filesystem, src, dst string) (err error) {
|
||||
log.Printf("copying file '%s' to '%s'", src, dst)
|
||||
|
||||
in, err := vfs.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer in.Close()
|
||||
|
||||
out, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if e := out.Close(); e != nil {
|
||||
err = e
|
||||
}
|
||||
}()
|
||||
|
||||
_, err = io.Copy(out, in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = out.Sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
11
internal/template/manifest.go
Normal file
11
internal/template/manifest.go
Normal file
@ -0,0 +1,11 @@
|
||||
package template
|
||||
|
||||
type Manifest struct {
|
||||
Version string `yaml:"version"`
|
||||
Vars []Var `yaml:"vars"`
|
||||
}
|
||||
|
||||
type Var struct {
|
||||
Type string `yaml:"type"`
|
||||
Name string `yaml:"name"`
|
||||
}
|
7
internal/template/option.go
Normal file
7
internal/template/option.go
Normal file
@ -0,0 +1,7 @@
|
||||
package template
|
||||
|
||||
type Option struct {
|
||||
IgnorePatterns []string
|
||||
TemplateData map[string]interface{}
|
||||
TemplateExt string
|
||||
}
|
Loading…
Reference in New Issue
Block a user