From d9e446553c11ac18f4cad36d68594e6edac1a68e Mon Sep 17 00:00:00 2001 From: William Petit Date: Fri, 21 Feb 2020 14:29:26 +0100 Subject: [PATCH] Prompt for template variables values based on the available manifest --- cmd/scaffold/main.go | 9 +- go.mod | 4 +- go.sum | 31 ++++++- internal/command/new_project.go | 89 ++++++------------- internal/template/copy.go | 4 +- internal/template/manifest.go | 151 +++++++++++++++++++++++++++++++- internal/template/option.go | 2 +- 7 files changed, 217 insertions(+), 73 deletions(-) diff --git a/cmd/scaffold/main.go b/cmd/scaffold/main.go index 7a87c3b..b1ca64d 100644 --- a/cmd/scaffold/main.go +++ b/cmd/scaffold/main.go @@ -1,21 +1,22 @@ package main import ( - "log" + "fmt" "os" - "gitlab.com/wpetit/scaffold/internal/command" + "forge.cadoles.com/wpetit/scaffold/internal/command" "github.com/urfave/cli/v2" ) func main() { app := &cli.App{ - Usage: "generate source code for goweb based projects", + Usage: "generate/update directory tree from template", Commands: command.All(), } if err := app.Run(os.Args); err != nil { - log.Fatal(err) + fmt.Printf("%+v", err) + os.Exit(1) } } diff --git a/go.mod b/go.mod index dbf3ff0..4b36cce 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module gitlab.com/wpetit/scaffold +module forge.cadoles.com/wpetit/scaffold go 1.13 @@ -6,6 +6,7 @@ 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/antonmedv/expr v1.4.5 github.com/google/uuid v1.1.1 // indirect github.com/huandu/xstrings v1.3.0 // indirect github.com/imdario/mergo v0.3.8 // indirect @@ -16,4 +17,5 @@ require ( 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 + gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index 29557d2..c683454 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,42 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= 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 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= 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 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antlr/antlr4 v0.0.0-20191011202612-ad2bd05285ca h1:QHbltbNkVcw97h4zA/L8gA4o3dJiFvBZ0gyZHrYXHbs= +github.com/antlr/antlr4 v0.0.0-20191011202612-ad2bd05285ca/go.mod h1:T7PbCXFs94rrTttyxjbyT5+/1V8T2TYDejxUfHJjw1Y= +github.com/antonmedv/expr v1.4.5 h1:yYjQAps1CZTBJBKntVnSEWYp15ML9IWnlrKuFHQ6/HY= +github.com/antonmedv/expr v1.4.5/go.mod h1:xesgliOuukGf21740qhh8PvFdN66yZ9lJJ/PzSFAmzI= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= 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 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 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 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg= +github.com/gdamore/tcell v1.1.2/go.mod h1:h3kq4HO9l2On+V9ed8w8ewqQEmGCSSHOgQ+2h8uzurE= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= 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= @@ -35,10 +51,13 @@ github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU 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 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= 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= @@ -47,6 +66,7 @@ github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRU 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/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 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= @@ -57,9 +77,14 @@ github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtb 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/tview v0.0.0-20190515161233-bd836ef13b4b/go.mod h1:+rKjP5+h9HMwWRpAfhIkkQ9KE3m3Nz5rwn7YtUpwgqk= +github.com/rivo/uniseg v0.0.0-20190513083848-b9f5b9457d44/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 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/sanity-io/litter v1.1.0 h1:BllcKWa3VbZmOZbDCoszYLk7zCsKHz5Beossi8SUcTc= +github.com/sanity-io/litter v1.1.0/go.mod h1:CJ0VCw2q4qKU7LaQr3n7UOSHzgEMgcGco7N/SkZQPjw= 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= @@ -68,8 +93,8 @@ 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 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 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= @@ -91,16 +116,20 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w 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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 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 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= 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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/command/new_project.go b/internal/command/new_project.go index 8e2f44e..892180f 100644 --- a/internal/command/new_project.go +++ b/internal/command/new_project.go @@ -5,10 +5,9 @@ import ( "net/url" "os" - "gitlab.com/wpetit/scaffold/internal/template" + "forge.cadoles.com/wpetit/scaffold/internal/template" - "github.com/manifoldco/promptui" - "gitlab.com/wpetit/scaffold/internal/project" + "forge.cadoles.com/wpetit/scaffold/internal/project" "github.com/pkg/errors" "github.com/urfave/cli/v2" @@ -65,23 +64,36 @@ func newProjectAction(c *cli.Context) error { return errors.Wrap(err, "could not fetch project") } - manifestFile := c.String("manifest") + templateData := template.Data{} - manifestStat, err := vfs.Stat(manifestFile) - if err != nil && !os.IsNotExist(err) { - return errors.Wrap(err, "could not stat manifest file") - } + manifestPath := c.String("manifest") - 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") + manifestFile, err := vfs.Open(manifestPath) + if err != nil { + if !os.IsNotExist(err) { + return errors.Wrapf(err, "could not read manifest '%s'", manifestPath) } + log.Println("Could not find scaffold manifest.") + } + + if manifestFile != nil { + defer manifestFile.Close() + log.Println("Loading template scaffold manifest...") + + manifest, err := template.Load(manifestFile) + if err != nil { + return errors.Wrapf(err, "could not load manifest '%s'", manifestPath) + } + + if err := manifest.Validate(); err != nil { + return errors.Wrapf(err, "could not validate manifest '%s'", manifestPath) + } + + if err := template.Prompt(manifest, templateData); err != nil { + return errors.Wrap(err, "could not prompt for template data") + } } directory := c.String("directory") @@ -89,51 +101,6 @@ func newProjectAction(c *cli.Context) error { return template.CopyDir(vfs, ".", directory, &template.Option{ TemplateData: templateData, TemplateExt: ".gotpl", - IgnorePatterns: []string{manifestFile}, + IgnorePatterns: []string{manifestPath}, }) } - -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 -} diff --git a/internal/template/copy.go b/internal/template/copy.go index 49437d1..dae09bf 100644 --- a/internal/template/copy.go +++ b/internal/template/copy.go @@ -9,7 +9,7 @@ import ( "strings" "text/template" - "gitlab.com/wpetit/scaffold/internal/fs" + "forge.cadoles.com/wpetit/scaffold/internal/fs" "gopkg.in/src-d/go-billy.v4" "github.com/Masterminds/sprig" @@ -60,8 +60,6 @@ func CopyDir(vfs billy.Filesystem, baseDir string, dst string, opts *Option) 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 { diff --git a/internal/template/manifest.go b/internal/template/manifest.go index 03fa24a..e07608b 100644 --- a/internal/template/manifest.go +++ b/internal/template/manifest.go @@ -1,11 +1,158 @@ package template +import ( + "io" + "io/ioutil" + + "github.com/antonmedv/expr" + "github.com/antonmedv/expr/vm" + "github.com/manifoldco/promptui" + "github.com/pkg/errors" + "gopkg.in/yaml.v2" +) + +const ( + ManifestVersion1 = "1" + VarTypeString = "string" + VarTypeSelect = "select" +) + +type Data map[string]interface{} + type Manifest struct { Version string `yaml:"version"` Vars []Var `yaml:"vars"` } type Var struct { - Type string `yaml:"type"` - Name string `yaml:"name"` + Type string `yaml:"type"` + Name string `yaml:"name"` + Description string `yaml:"description"` + Default string `yaml:"default"` + Constraints []Constraint `yaml:"constraints"` + Items []string `yaml:"items"` +} + +type Constraint struct { + Rule string `yaml:"rule"` + Message string `yaml:"message"` +} + +func (m *Manifest) Validate() error { + if m.Version != ManifestVersion1 { + return errors.Errorf("unexpected manifest version: '%s'", m.Version) + } + + return nil +} + +func Load(r io.Reader) (*Manifest, error) { + data, err := ioutil.ReadAll(r) + if err != nil { + return nil, errors.Wrap(err, "could not read manifest") + } + + manifest := &Manifest{} + + if err := yaml.Unmarshal(data, manifest); err != nil { + return nil, errors.Wrap(err, "could not unmarshal manifest") + } + + return manifest, nil +} + +func Prompt(manifest *Manifest, data Data) error { + for _, v := range manifest.Vars { + var ( + value string + err error + ) + + switch v.Type { + case VarTypeString: + value, err = promptString(v) + + case VarTypeSelect: + value, err = promptSelect(v) + + default: + err = errors.Errorf("unexpected variable type '%s'", v.Type) + } + + if err != nil { + return err + } + + data[v.Name] = value + } + + return nil +} + +func promptString(v Var) (string, error) { + var validate func(string) error + + if len(v.Constraints) > 0 { + rules := make([]*vm.Program, 0, len(v.Constraints)) + + for _, c := range v.Constraints { + r, err := expr.Compile(c.Rule) + if err != nil { + return "", errors.Wrapf(err, "invalid constraint syntax: '%s'", c.Rule) + } + + rules = append(rules, r) + } + + validate = func(input string) error { + env := struct { + Input string + }{input} + + for i, r := range rules { + result, err := expr.Run(r, env) + if err != nil { + return errors.Wrapf(err, "could not run constraint rule (constraint #%d)", i) + } + + match, ok := result.(bool) + if !ok { + return errors.Errorf("unexpected constraint rule result '%v' (constraint #%d)", result, i) + } + + if match { + return errors.New(v.Constraints[i].Message) + } + } + + return nil + } + } + + prompt := promptui.Prompt{ + Label: v.Description, + Validate: validate, + Default: v.Default, + } + + value, err := prompt.Run() + if err != nil { + return "", errors.Wrapf(err, "could not prompt for variable '%s'", v.Name) + } + + return value, nil +} + +func promptSelect(v Var) (string, error) { + prompt := promptui.Select{ + Label: v.Description, + Items: v.Items, + } + + _, value, err := prompt.Run() + if err != nil { + return "", errors.Wrapf(err, "could not prompt for variable '%s'", v.Name) + } + + return value, nil } diff --git a/internal/template/option.go b/internal/template/option.go index cb24c3f..5e02013 100644 --- a/internal/template/option.go +++ b/internal/template/option.go @@ -2,6 +2,6 @@ package template type Option struct { IgnorePatterns []string - TemplateData map[string]interface{} + TemplateData Data TemplateExt string }