From 6de6e4ef7088ada05ed5cab1da4c5251b86d4e4e Mon Sep 17 00:00:00 2001 From: jolheiser Date: Mon, 16 Nov 2020 23:41:34 -0600 Subject: [PATCH] Initial Commit Signed-off-by: jolheiser --- .gitignore | 6 ++ LICENSE | 7 ++ Makefile | 18 ++++ cmd/app.go | 76 ++++++++++++++++ cmd/download.go | 69 +++++++++++++++ cmd/flags/flags.go | 7 ++ cmd/init.go | 55 ++++++++++++ cmd/list.go | 37 ++++++++ cmd/remove.go | 36 ++++++++ cmd/save.go | 58 ++++++++++++ cmd/source.go | 104 ++++++++++++++++++++++ cmd/test.go | 39 +++++++++ cmd/update.go | 45 ++++++++++ cmd/use.go | 41 +++++++++ docs.go | 32 +++++++ go.mod | 12 +++ go.sum | 126 ++++++++++++++++++++++++++ main.go | 17 ++++ registry/error.go | 29 ++++++ registry/registry.go | 204 +++++++++++++++++++++++++++++++++++++++++++ registry/source.go | 15 ++++ registry/template.go | 141 ++++++++++++++++++++++++++++++ 22 files changed, 1174 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 cmd/app.go create mode 100644 cmd/download.go create mode 100644 cmd/flags/flags.go create mode 100644 cmd/init.go create mode 100644 cmd/list.go create mode 100644 cmd/remove.go create mode 100644 cmd/save.go create mode 100644 cmd/source.go create mode 100644 cmd/test.go create mode 100644 cmd/update.go create mode 100644 cmd/use.go create mode 100644 docs.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 registry/error.go create mode 100644 registry/registry.go create mode 100644 registry/source.go create mode 100644 registry/template.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afb63d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# GoLand +.idea/ + +# Binaries +/tmpl +/tmpl.exe \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0f02641 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2020 John Olheiser + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..13f5a8d --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +GO ?= go +VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') + +.PHONY: build +build: + $(GO) build -ldflags '-s -w -X "go.jolheiser.com/tmpl/cmd.Version=$(VERSION)"' + +.PHONY: vet +vet: + $(GO) vet ./... + +.PHONY: fmt +fmt: + $(GO) fmt ./... + +.PHONY: test +test: + $(GO) test -race ./... diff --git a/cmd/app.go b/cmd/app.go new file mode 100644 index 0000000..ba08026 --- /dev/null +++ b/cmd/app.go @@ -0,0 +1,76 @@ +package cmd + +import ( + "os" + "path/filepath" + + "go.jolheiser.com/tmpl/cmd/flags" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var ( + Version = "develop" + defaultDir string +) + +func init() { + home, err := os.UserHomeDir() + if err != nil { + beaver.Error("could not locate user's home directory, tmpl will use temp dir for registry") + return + } + defaultDir = filepath.Join(home, ".tmpl") +} + +func NewApp() *cli.App { + app := cli.NewApp() + app.Name = "tmpl" + app.Usage = "Template automation" + app.Description = "Template automation" + app.Version = Version + app.Flags = []cli.Flag{ + &cli.BoolFlag{ + Name: "debug", + Aliases: []string{"d"}, + Usage: "Debug mode", + Destination: &flags.Debug, + }, + &cli.StringFlag{ + Name: "registry", + Aliases: []string{"r"}, + Usage: "Registry directory of tmpl", + Value: defaultDir, + Destination: &flags.Registry, + }, + &cli.StringFlag{ + Name: "source", + Aliases: []string{"s"}, + Usage: "Short-name source to use", + Destination: &flags.Source, + }, + } + app.Before = before + + app.Commands = []*cli.Command{ + Download, + Init, + List, + Remove, + Save, + Source, + Test, + Update, + Use, + } + + return app +} + +func before(ctx *cli.Context) error { + if ctx.Bool("debug") { + beaver.Console.Level = beaver.DEBUG + } + return nil +} diff --git a/cmd/download.go b/cmd/download.go new file mode 100644 index 0000000..2421ae8 --- /dev/null +++ b/cmd/download.go @@ -0,0 +1,69 @@ +package cmd + +import ( + "errors" + "fmt" + "strings" + + "go.jolheiser.com/tmpl/cmd/flags" + "go.jolheiser.com/tmpl/registry" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Download = &cli.Command{ + Name: "download", + Usage: "Download a template", + Description: "Download a template and save it to the local registry", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "branch", + Aliases: []string{"b"}, + Usage: "Branch to clone", + Value: "main", + EnvVars: []string{"TMPL_BRANCH"}, + }, + }, + Action: runDownload, +} + +func runDownload(ctx *cli.Context) error { + if ctx.NArg() < 2 { + return errors.New(" ") + } + + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + var source *registry.Source + if flags.Source != "" { + for _, s := range reg.Sources { + if strings.EqualFold(s.Name, flags.Source) { + source = s + break + } + } + if source == nil { + return fmt.Errorf("could not find source for %s", flags.Source) + } + } + + cloneURL := ctx.Args().First() + if !strings.HasSuffix(cloneURL, ".git") { + cloneURL += ".git" + } + if source != nil { + cloneURL = source.CloneURL(cloneURL) + } + + t, err := reg.AddTemplate(ctx.Args().Get(1), cloneURL, ctx.String("branch")) + if err != nil { + return err + } + + beaver.Infof("Added new template %s", t.Name) + return nil +} diff --git a/cmd/flags/flags.go b/cmd/flags/flags.go new file mode 100644 index 0000000..fc13bfa --- /dev/null +++ b/cmd/flags/flags.go @@ -0,0 +1,7 @@ +package flags + +var ( + Debug bool + Registry string + Source string +) diff --git a/cmd/init.go b/cmd/init.go new file mode 100644 index 0000000..b5e4e0b --- /dev/null +++ b/cmd/init.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "errors" + "os" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Init = &cli.Command{ + Name: "init", + Usage: "Initialize a template", + Description: "Initializes a template structure for creating a new tmpl template", + Action: runInit, +} + +func runInit(_ *cli.Context) error { + if _, err := os.Lstat("template.toml"); !os.IsNotExist(err) { + if err != nil { + return err + } + return errors.New("template.toml already detected, aborting initialization") + } + if fi, err := os.Lstat("template"); !os.IsNotExist(err) { + if err != nil { + return err + } + if !fi.IsDir() { + return errors.New("template file found instead of directory, aborting initialization") + } + return errors.New("template directory already detected, aborting initialization") + } + + fi, err := os.Create("template.toml") + if err != nil { + return err + } + if _, err := fi.WriteString(comments); err != nil { + return err + } + if err := os.Mkdir("template", os.ModePerm); err != nil { + return err + } + beaver.Info("Template initialized!") + return fi.Close() +} + +var comments = `# template.toml +# Write any template args here to prompt the user for, giving any defaults/options as applicable + +name = "MyProject" + +lang = ["Go", "Rust", "Python"] +` diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..40b4ced --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "fmt" + "os" + "text/tabwriter" + + "go.jolheiser.com/tmpl/cmd/flags" + "go.jolheiser.com/tmpl/registry" + + "github.com/urfave/cli/v2" +) + +var List = &cli.Command{ + Name: "list", + Usage: "List templates in the registry", + Description: "List all usable templates currently downloaded in the registry", + Action: runList, +} + +func runList(_ *cli.Context) error { + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + wr := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0) + + fmt.Println("Templates") + for _, t := range reg.Templates { + if _, err := fmt.Fprintf(wr, "%s\t%s@%s\t%s", t.Name, t.Repository, t.Branch, t.Created); err != nil { + return err + } + } + fmt.Println() + return nil +} diff --git a/cmd/remove.go b/cmd/remove.go new file mode 100644 index 0000000..e753fb4 --- /dev/null +++ b/cmd/remove.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "errors" + + "go.jolheiser.com/tmpl/cmd/flags" + "go.jolheiser.com/tmpl/registry" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Remove = &cli.Command{ + Name: "remove", + Usage: "Remove a template", + Description: "Remove a template from the registry", + Action: runRemove, +} + +func runRemove(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("") + } + + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + if err := reg.RemoveTemplate(ctx.Args().First()); err != nil { + return err + } + + beaver.Infof("Successfully removed %s", ctx.Args().First()) + return nil +} diff --git a/cmd/save.go b/cmd/save.go new file mode 100644 index 0000000..0d4c4c1 --- /dev/null +++ b/cmd/save.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "errors" + "path/filepath" + "strings" + + "go.jolheiser.com/tmpl/cmd/flags" + "go.jolheiser.com/tmpl/registry" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Save = &cli.Command{ + Name: "save", + Usage: "Save a local template", + Description: "Save a local template to the registry", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "branch", + Aliases: []string{"b"}, + Usage: "Branch to clone", + Value: "main", + EnvVars: []string{"TMPL_BRANCH"}, + }, + }, + Action: runSave, +} + +func runSave(ctx *cli.Context) error { + if ctx.NArg() < 2 { + return errors.New(" ") + } + + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + // Did the user give us the root path, or the .git directory? + localPath := ctx.Args().First() + if !strings.HasSuffix(localPath, ".git") { + localPath = filepath.Join(localPath, ".git") + } + localPath, err = filepath.Abs(localPath) + if err != nil { + return err + } + + t, err := reg.AddTemplate(ctx.Args().Get(1), localPath, ctx.String("branch")) + if err != nil { + return err + } + + beaver.Infof("Added new template %s", t.Name) + return nil +} diff --git a/cmd/source.go b/cmd/source.go new file mode 100644 index 0000000..ac52533 --- /dev/null +++ b/cmd/source.go @@ -0,0 +1,104 @@ +package cmd + +import ( + "errors" + "fmt" + "os" + "text/tabwriter" + + "go.jolheiser.com/tmpl/cmd/flags" + "go.jolheiser.com/tmpl/registry" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var ( + Source = &cli.Command{ + Name: "source", + Usage: "Commands for working with sources", + Description: "Commands for working with sources, short-hand flags for easier downloads", + Action: SourceList.Action, + Subcommands: []*cli.Command{ + SourceList, + SourceAdd, + SourceRemove, + }, + } + + SourceList = &cli.Command{ + Name: "list", + Usage: "List available sources", + Description: "List all available sources in the registry", + Action: runSourceList, + } + + SourceAdd = &cli.Command{ + Name: "add", + Usage: "AddTemplate a source", + Description: "AddTemplate a new source to the registry", + Action: runSourceAdd, + } + + SourceRemove = &cli.Command{ + Name: "remove", + Usage: "RemoveTemplate a source", + Description: "RemoveTemplate a source from the registry", + Action: runSourceRemove, + } +) + +func runSourceList(_ *cli.Context) error { + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + wr := tabwriter.NewWriter(os.Stdout, 0, 8, 0, '\t', 0) + + fmt.Println("Sources") + for _, s := range reg.Sources { + if _, err := fmt.Fprintf(wr, "%s\t%s", s.Name, s.URL); err != nil { + return err + } + } + fmt.Println() + return nil +} + +func runSourceAdd(ctx *cli.Context) error { + if ctx.NArg() < 2 { + return errors.New(" ") + } + + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + s, err := reg.AddSource(ctx.Args().First(), ctx.Args().Get(1)) + if err != nil { + return err + } + + beaver.Infof("Added new source %s", s.Name) + return nil +} + +func runSourceRemove(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("") + } + + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + if err := reg.RemoveSource(ctx.Args().First()); err != nil { + return err + } + + beaver.Infof("Successfully removed source for %s", ctx.Args().First()) + return nil +} diff --git a/cmd/test.go b/cmd/test.go new file mode 100644 index 0000000..be6b258 --- /dev/null +++ b/cmd/test.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "os" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Test = &cli.Command{ + Name: "test", + Usage: "Test if a directory is a valid template", + Description: "Test whether the current directory is valid for use with tmpl", + Action: runTest, +} + +func runTest(_ *cli.Context) error { + var errs []string + if _, err := os.Lstat("template.toml"); err != nil { + errs = append(errs, "could not find template.toml") + } + + fi, err := os.Lstat("template") + if err != nil { + errs = append(errs, "no template directory found") + } + if err == nil && !fi.IsDir() { + errs = append(errs, "template path is a file, not a directory") + } + + if len(errs) > 0 { + for _, err := range errs { + beaver.Error(err) + } + return nil + } + beaver.Info("this is a valid tmpl template") + return nil +} diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 0000000..03cdcba --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "errors" + + "go.jolheiser.com/tmpl/cmd/flags" + "go.jolheiser.com/tmpl/registry" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Update = &cli.Command{ + Name: "update", + Usage: "Update a template", + Description: "Update a template in the registry from the original source", + Action: runUpdate, +} + +func runUpdate(ctx *cli.Context) error { + if ctx.NArg() < 1 { + return errors.New("") + } + + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + tmpl, err := reg.GetTemplate(ctx.Args().First()) + if err != nil { + return err + } + + if err := reg.RemoveTemplate(tmpl.Name); err != nil { + return err + } + + if _, err := reg.AddTemplate(tmpl.Name, tmpl.Repository, tmpl.Branch); err != nil { + return err + } + + beaver.Infof("Successfully updated %s", tmpl.Name) + return nil +} diff --git a/cmd/use.go b/cmd/use.go new file mode 100644 index 0000000..3024f85 --- /dev/null +++ b/cmd/use.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "errors" + + "go.jolheiser.com/tmpl/cmd/flags" + "go.jolheiser.com/tmpl/registry" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Use = &cli.Command{ + Name: "use", + Usage: "Use a template", + Description: "Use (execute) a template from the registry", + Action: runUse, +} + +func runUse(ctx *cli.Context) error { + if ctx.NArg() < 2 { + return errors.New(" ") + } + + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + tmpl, err := reg.GetTemplate(ctx.Args().First()) + if err != nil { + return err + } + + if err := tmpl.Execute(ctx.Args().Get(1)); err != nil { + return err + } + + beaver.Infof("Successfully executed %s", tmpl.Name) + return nil +} diff --git a/docs.go b/docs.go new file mode 100644 index 0000000..3f5bd46 --- /dev/null +++ b/docs.go @@ -0,0 +1,32 @@ +// +build docs + +package main + +import ( + "os" + "strings" + + "go.jolheiser.com/tmpl/cmd" +) + +func main() { + app := cmd.NewApp() + + fi, err := os.Create("DOCS.md") + if err != nil { + panic(err) + } + defer fi.Close() + + md, err := app.ToMarkdown() + if err != nil { + panic(err) + } + + // CLI ToMarkdown issue related to man-pages + md = md[strings.Index(md, "#"):] + + if _, err := fi.WriteString(md); err != nil { + panic(err) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..0c6ccbf --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module go.jolheiser.com/tmpl + +go 1.15 + +require ( + github.com/AlecAivazis/survey/v2 v2.2.2 + github.com/go-git/go-git/v5 v5.2.0 + github.com/mholt/archiver/v3 v3.5.0 + github.com/pelletier/go-toml v1.8.1 + github.com/urfave/cli/v2 v2.3.0 + go.jolheiser.com/beaver v1.0.2 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..31ff46e --- /dev/null +++ b/go.sum @@ -0,0 +1,126 @@ +github.com/AlecAivazis/survey v1.8.8 h1:Y4yypp763E8cbqb5RBqZhGgkCFLRFnbRBHrxnpMMsgQ= +github.com/AlecAivazis/survey/v2 v2.2.2 h1:1I4qBrNsHQE+91tQCqVlfrKe9DEL65949d1oKZWVELY= +github.com/AlecAivazis/survey/v2 v2.2.2/go.mod h1:9FJRdMdDm8rnT+zHVbvQT2RTSTLq0Ttd6q3Vl2fahjk= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +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/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.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +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/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM= +github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git v1.0.0 h1:YcN9iDGDoXuIw0vHls6rINwV416HYa0EB2X+RBsyYp4= +github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA= +github.com/go-git/go-git-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw= +github.com/go-git/go-git/v5 v5.2.0 h1:YPBLG/3UK1we1ohRkncLjaXWLW+HKp5QNM/jTli2JgI= +github.com/go-git/go-git/v5 v5.2.0/go.mod h1:kh02eMX+wdqqxgNMEyq8YgwlIOsDOa9homkUq1PoTMs= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A= +github.com/imdario/mergo v0.3.9 h1:UauaLniWCFHWd+Jp9oCEkTBj8VO/9DKg3PV3VCNMDIg= +github.com/imdario/mergo v0.3.9/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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +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/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I= +github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.4 h1:TQ7CNpYKovDOmqzRHKxJh0BeaBI7UdQZYc6p7pMQh1A= +github.com/klauspost/pgzip v1.2.4/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +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.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= +github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= +github.com/mholt/archiver v1.1.2 h1:xukR55YIrnhDHp10lrNtRSsAK5THpWrOCuviweNSBw4= +github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= +github.com/mholt/archiver/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= +github.com/mholt/archiver/v3 v3.5.0/go.mod h1:qqTTPUK/HZPFgFQ/TJ3BzvTpF/dPtFVJXdQbCmeMxwc= +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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= +github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pierrec/lz4/v4 v4.0.3 h1:vNQKSVZNYUEAvRY9FaUXAF1XPbSOHJtDTiP41kzDz2E= +github.com/pierrec/lz4/v4 v4.0.3/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.8.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.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +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= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= +go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= +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-20190530122614-20be4c3c3ed5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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-20190222072716-a9d3bda3a223/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-20190530182044-ad28b68e88f1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f07d7c2 --- /dev/null +++ b/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "os" + + "go.jolheiser.com/tmpl/cmd" + + "go.jolheiser.com/beaver" +) + +func main() { + app := cmd.NewApp() + + if err := app.Run(os.Args); err != nil { + beaver.Fatal(err) + } +} diff --git a/registry/error.go b/registry/error.go new file mode 100644 index 0000000..d85f168 --- /dev/null +++ b/registry/error.go @@ -0,0 +1,29 @@ +package registry + +import "fmt" + +type ErrTemplateNotFound struct { + Name string +} + +func (e ErrTemplateNotFound) Error() string { + return fmt.Sprintf("template not found for %s", e.Name) +} + +func IsErrTemplateNotFound(err error) bool { + _, ok := err.(ErrTemplateNotFound) + return ok +} + +type ErrSourceNotFound struct { + Name string +} + +func (e ErrSourceNotFound) Error() string { + return fmt.Sprintf("Source not found for %s", e.Name) +} + +func IsErrSourceNotFound(err error) bool { + _, ok := err.(ErrSourceNotFound) + return ok +} diff --git a/registry/registry.go b/registry/registry.go new file mode 100644 index 0000000..32c2e7c --- /dev/null +++ b/registry/registry.go @@ -0,0 +1,204 @@ +package registry + +import ( + "errors" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/mholt/archiver/v3" + "github.com/pelletier/go-toml" +) + +// Registry is a collection of Template +type Registry struct { + dir string + Sources []*Source + Templates []*Template +} + +func (r *Registry) save() error { + fi, err := os.Create(r.MetaFilePath()) + if err != nil { + return err + } + if err := toml.NewEncoder(fi).Encode(r); err != nil { + return err + } + return fi.Close() +} + +// MetaFilePath is the path to the Registry meta-file +func (r *Registry) MetaFilePath() string { + return filepath.Join(r.dir, "registry.toml") +} + +// GetTemplate retrieves a Template from the Registry +func (r *Registry) GetTemplate(name string) (*Template, error) { + for _, t := range r.Templates { + if strings.EqualFold(name, t.Name) { + t.reg = r + return t, nil + } + } + return nil, ErrTemplateNotFound{Name: name} +} + +// AddTemplate downloads and adds a new Template to the Registry +func (r *Registry) AddTemplate(name, repo, branch string) (*Template, error) { + t := &Template{ + reg: r, + Name: name, + Repository: repo, + Branch: branch, + Created: time.Now(), + } + r.Templates = append(r.Templates, t) + + if err := download(repo, branch, t.ArchivePath()); err != nil { + return nil, err + } + + return t, r.save() +} + +// RemoveTemplate removes the Template from disk and meta +func (r *Registry) RemoveTemplate(name string) error { + _, err := r.GetTemplate(name) + if err != nil { + return err + } + for idx, t := range r.Templates { + if strings.EqualFold(name, t.Name) { + r.Templates = append(r.Templates[:idx], r.Templates[idx+1:]...) + if err := os.Remove(t.ArchivePath()); err != nil { + return err + } + } + } + + return r.save() +} + +// GetSource retrieves a Source from the Registry +func (r *Registry) GetSource(name string) (*Source, error) { + for _, s := range r.Sources { + if strings.EqualFold(name, s.Name) { + return s, nil + } + } + return nil, ErrSourceNotFound{Name: name} +} + +// AddSource adds a new Source to the Registry +func (r *Registry) AddSource(url, name string) (*Source, error) { + url = strings.TrimSuffix(url, "/") + "/" + s := &Source{ + Name: name, + URL: url, + } + r.Sources = append(r.Sources, s) + + return s, r.save() +} + +// RemoveSource removes the Source from the registry meta +func (r *Registry) RemoveSource(name string) error { + _, err := r.GetSource(name) + if err != nil { + return err + } + for idx, s := range r.Sources { + if strings.EqualFold(name, s.Name) { + r.Sources = append(r.Sources[:idx], r.Sources[idx+1:]...) + } + } + + return r.save() +} + +// Open opens a Registry, creating one if none exists at dir +func Open(dir string) (*Registry, error) { + reg := Registry{ + dir: dir, + } + + _, err := os.Lstat(reg.MetaFilePath()) + if err != nil { + if os.IsNotExist(err) { + if err := create(reg.MetaFilePath()); err != nil { + return nil, err + } + } else { + return nil, err + } + } + + tree, err := toml.LoadFile(reg.MetaFilePath()) + if err != nil { + return nil, err + } + return ®, tree.Unmarshal(®) +} + +func create(regFile string) error { + if err := os.MkdirAll(filepath.Dir(regFile), os.ModePerm); err != nil { + return err + } + fi, err := os.Create(regFile) + if err != nil { + return err + } + return fi.Close() +} + +func download(cloneURL, branch, dest string) error { + tmp, err := ioutil.TempDir(os.TempDir(), "tmpl") + if err != nil { + return err + } + defer os.RemoveAll(tmp) + + // Clone the repo + if _, err := git.PlainClone(tmp, false, &git.CloneOptions{ + URL: cloneURL, + ReferenceName: plumbing.NewBranchReferenceName(branch), + SingleBranch: true, + Depth: 1, + }); err != nil { + return err + } + + // RemoveTemplate .git + if err := os.RemoveAll(filepath.Join(tmp, ".git")); err != nil { + return err + } + + // Make sure it's a valid template + if _, err := os.Lstat(filepath.Join(tmp, "template.toml")); err != nil { + return err + } + fi, err := os.Lstat(filepath.Join(tmp, "template")) + if err != nil { + return err + } + if !fi.IsDir() { + return errors.New("template found, expected directory") + } + + // Create archive + glob, err := filepath.Glob(filepath.Join(tmp, "*")) + if err != nil { + return err + } + + if err := archiver.Archive(glob, dest); err != nil { + return err + } + + return nil +} diff --git a/registry/source.go b/registry/source.go new file mode 100644 index 0000000..b42a792 --- /dev/null +++ b/registry/source.go @@ -0,0 +1,15 @@ +package registry + +import "fmt" + +// Source is a quick way to specify a git source +// e.g. Gitea, GitHub, etc. +type Source struct { + Name string `toml:"name"` + URL string `toml:"url"` +} + +// CloneURL constructs a URL suitable for cloning a repository +func (s *Source) CloneURL(namespace string) string { + return fmt.Sprintf("%s%s", s.URL, namespace) +} diff --git a/registry/template.go b/registry/template.go new file mode 100644 index 0000000..8d00c1a --- /dev/null +++ b/registry/template.go @@ -0,0 +1,141 @@ +package registry + +import ( + "fmt" + "html/template" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" + + "github.com/AlecAivazis/survey/v2" + "github.com/mholt/archiver/v3" + "github.com/pelletier/go-toml" +) + +// Template is a tmpl project +type Template struct { + reg *Registry `toml:"-"` + Name string `toml:"name"` + Repository string `toml:"repository"` + Branch string `toml:"branch"` + Created time.Time `toml:"created"` +} + +// ArchiveName is the name given to the archive for this Template +func (t *Template) ArchiveName() string { + return fmt.Sprintf("%s.tar.gz", t.Name) +} + +// ArchivePath is the full path to the archive for this Template within the Registry +func (t *Template) ArchivePath() string { + return filepath.Join(t.reg.dir, t.ArchiveName()) +} + +// Execute runs the Template and copies to dest +func (t *Template) Execute(dest string) error { + tmp, err := ioutil.TempDir(os.TempDir(), "tmpl") + if err != nil { + return err + } + defer os.RemoveAll(tmp) + + if err := archiver.Unarchive(t.ArchivePath(), tmp); err != nil { + return err + } + + vars, err := prompt(tmp) + if err != nil { + return err + } + + base := filepath.Join(tmp, "template") + return filepath.Walk(base, func(walkPath string, walkInfo os.FileInfo, walkErr error) error { + if walkErr != nil { + return walkErr + } + + if walkInfo.IsDir() { + return nil + } + + contents, err := ioutil.ReadFile(walkPath) + if err != nil { + return err + } + + tmpl, err := template.New("tmpl").Parse(string(contents)) + if err != nil { + return err + } + + newDest := strings.TrimPrefix(walkPath, base+"/") + newDest = filepath.Join(dest, newDest) + + if err := os.MkdirAll(filepath.Dir(newDest), os.ModePerm); err != nil { + return err + } + + oldFi, err := os.Lstat(walkPath) + if err != nil { + return err + } + newFi, err := os.OpenFile(newDest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, oldFi.Mode()) + if err != nil { + return err + } + + if err := tmpl.Execute(newFi, vars); err != nil { + return err + } + + return newFi.Close() + }) +} + +func prompt(dir string) (map[string]interface{}, error) { + templatePath := filepath.Join(dir, "template.toml") + if _, err := os.Lstat(templatePath); err != nil { + return nil, err + } + + tree, err := toml.LoadFile(templatePath) + if err != nil { + return nil, err + } + + vars := tree.ToMap() + + for k, v := range vars { + var p survey.Prompt + switch t := v.(type) { + case []string: + p = &survey.Select{ + Message: k, + Options: t, + } + default: + p = &survey.Input{ + Message: k, + Default: fmt.Sprintf("%v", t), + } + } + q := []*survey.Question{ + { + Name: "response", + Prompt: p, + Validate: survey.Required, + }, + } + a := struct { + Response string + }{} + if err := survey.Ask(q, &a); err != nil { + return nil, err + } + vars[k] = a.Response + } + + return vars, nil +}