From 0aa3a3af934a36e782cec0ac11677228ba01bf4f Mon Sep 17 00:00:00 2001 From: jolheiser Date: Sun, 22 Nov 2020 22:59:56 -0600 Subject: [PATCH 1/9] Expand the template.toml file Signed-off-by: jolheiser --- DOCS.md | 6 +++++- registry/prompt.go | 11 ++++++++++- registry/template_test.go | 8 +++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/DOCS.md b/DOCS.md index 889b7f0..57ffe31 100644 --- a/DOCS.md +++ b/DOCS.md @@ -11,6 +11,10 @@ A "valid" tmpl template only requires two things ## template.toml +**NOTE:** The template.toml file will be expanded, though not with the full power of the template itself. +The template.toml file will only expand environment variables with syntax `$USER` or `${USER}`. +For full documentation on the syntax, see [os.ExpandEnv](https://golang.org/pkg/os/#ExpandEnv). + ```toml # Key-value pairs can be simple # The user will receive a basic prompt asking them to fill out the variable @@ -25,7 +29,7 @@ prompt = "The name of the author of this project" # help would be extra information (generally seen by giving '?' to a prompt) help = "Who will be primarily writing this project" # default is the "value" part of the simple pair. This could be a suggested value -default = "me" +default = "$USER" ``` ## template directory diff --git a/registry/prompt.go b/registry/prompt.go index b683205..dfa47ce 100644 --- a/registry/prompt.go +++ b/registry/prompt.go @@ -2,6 +2,7 @@ package registry import ( "fmt" + "io/ioutil" "os" "path/filepath" "sort" @@ -25,7 +26,15 @@ func prompt(dir string, defaults bool) (templatePrompts, error) { return nil, err } - tree, err := toml.LoadFile(templatePath) + templateBytes, err := ioutil.ReadFile(templatePath) + if err != nil { + return nil, err + } + + // Expand the template with environment variables + templateContents := os.ExpandEnv(string(templateBytes)) + + tree, err := toml.Load(templateContents) if err != nil { return nil, err } diff --git a/registry/template_test.go b/registry/template_test.go index be32ced..11f2d0f 100644 --- a/registry/template_test.go +++ b/registry/template_test.go @@ -12,7 +12,7 @@ var ( tmplTemplate = `name = "john olheiser" [year] -default = 2020 +default = ${TMPL_TEST} [package] default = "pkg"` @@ -21,6 +21,12 @@ default = "pkg"` ) func testExecute(t *testing.T) { + // Set environment variable + if err := os.Setenv("TMPL_TEST", "2020"); err != nil { + t.Logf("could not set environment: %v", err) + t.FailNow() + } + // Get template tmpl, err := reg.GetTemplate("test") if err != nil { -- 2.41.0 From 6ffc50dc06e46255334863e33156b8e0e82fafcb Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Mon, 23 Nov 2020 13:02:16 +0800 Subject: [PATCH 2/9] Expand the template.toml file (#7) Expand the template.toml file Signed-off-by: jolheiser Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/7 Co-Authored-By: John Olheiser Co-Committed-By: John Olheiser --- DOCS.md | 6 +++++- registry/prompt.go | 11 ++++++++++- registry/template_test.go | 8 +++++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/DOCS.md b/DOCS.md index 889b7f0..57ffe31 100644 --- a/DOCS.md +++ b/DOCS.md @@ -11,6 +11,10 @@ A "valid" tmpl template only requires two things ## template.toml +**NOTE:** The template.toml file will be expanded, though not with the full power of the template itself. +The template.toml file will only expand environment variables with syntax `$USER` or `${USER}`. +For full documentation on the syntax, see [os.ExpandEnv](https://golang.org/pkg/os/#ExpandEnv). + ```toml # Key-value pairs can be simple # The user will receive a basic prompt asking them to fill out the variable @@ -25,7 +29,7 @@ prompt = "The name of the author of this project" # help would be extra information (generally seen by giving '?' to a prompt) help = "Who will be primarily writing this project" # default is the "value" part of the simple pair. This could be a suggested value -default = "me" +default = "$USER" ``` ## template directory diff --git a/registry/prompt.go b/registry/prompt.go index b683205..dfa47ce 100644 --- a/registry/prompt.go +++ b/registry/prompt.go @@ -2,6 +2,7 @@ package registry import ( "fmt" + "io/ioutil" "os" "path/filepath" "sort" @@ -25,7 +26,15 @@ func prompt(dir string, defaults bool) (templatePrompts, error) { return nil, err } - tree, err := toml.LoadFile(templatePath) + templateBytes, err := ioutil.ReadFile(templatePath) + if err != nil { + return nil, err + } + + // Expand the template with environment variables + templateContents := os.ExpandEnv(string(templateBytes)) + + tree, err := toml.Load(templateContents) if err != nil { return nil, err } diff --git a/registry/template_test.go b/registry/template_test.go index be32ced..11f2d0f 100644 --- a/registry/template_test.go +++ b/registry/template_test.go @@ -12,7 +12,7 @@ var ( tmplTemplate = `name = "john olheiser" [year] -default = 2020 +default = ${TMPL_TEST} [package] default = "pkg"` @@ -21,6 +21,12 @@ default = "pkg"` ) func testExecute(t *testing.T) { + // Set environment variable + if err := os.Setenv("TMPL_TEST", "2020"); err != nil { + t.Logf("could not set environment: %v", err) + t.FailNow() + } + // Get template tmpl, err := reg.GetTemplate("test") if err != nil { -- 2.41.0 From 47ef9ea0be51e0b392957154ce958434fffff171 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Tue, 24 Nov 2020 07:28:10 +0800 Subject: [PATCH 3/9] Fix CI (#8) Update '.drone.yml' Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/8 Co-Authored-By: John Olheiser Co-Committed-By: John Olheiser --- .drone.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index a72e606..7849981 100644 --- a/.drone.yml +++ b/.drone.yml @@ -96,8 +96,11 @@ platform: arch: amd64 trigger: - event: - - tag + ref: + include: + - refs/tags/* + exclude: + - refs/tags/latest steps: - name: build @@ -119,13 +122,13 @@ steps: - make build - name: gitea-release pull: always - image: jolheiser/drone-gitea-main:latest + image: plugins/gitea-release:1 environment: GOPROXY: https://goproxy.cn settings: - token: + api_key: from_secret: gitea_token - base: https://gitea.com + base_url: https://gitea.com files: - "tmpl" - "tmpl.exe" -- 2.41.0 From 36832f93de9e457e833c15089ae26512d75aaae3 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Tue, 24 Nov 2020 14:10:46 +0800 Subject: [PATCH 4/9] Better defaults and prompts (#9) Add true bools and better defaults, fix map/funcmap differences Signed-off-by: jolheiser Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/9 Co-Authored-By: John Olheiser Co-Committed-By: John Olheiser --- registry/prompt.go | 21 +++++++++++++++++++-- registry/template_test.go | 13 +++++++++---- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/registry/prompt.go b/registry/prompt.go index dfa47ce..62317c2 100644 --- a/registry/prompt.go +++ b/registry/prompt.go @@ -84,6 +84,18 @@ func prompt(dir string, defaults bool) (templatePrompts, error) { Options: t, Help: prompt.Help, } + case bool: + p = &survey.Confirm{ + Message: prompt.Message, + Default: t, + Help: prompt.Help, + } + case string: + p = &survey.Input{ + Message: prompt.Message, + Default: t, + Help: prompt.Help, + } default: p = &survey.Input{ Message: prompt.Message, @@ -103,6 +115,7 @@ func prompt(dir string, defaults bool) (templatePrompts, error) { type templatePrompts []templatePrompt +// ToMap converts a slice to templatePrompt into a suitable template context func (t templatePrompts) ToMap() map[string]interface{} { m := make(map[string]interface{}) for _, p := range t { @@ -115,25 +128,29 @@ func (t templatePrompts) ToMap() map[string]interface{} { return m } +// ToFuncMap converts a slice of templatePrompt into a suitable template.FuncMap func (t templatePrompts) ToFuncMap() template.FuncMap { m := make(map[string]interface{}) for k, v := range t.ToMap() { vv := v // Enclosure - m[k] = func() string { - return fmt.Sprintf("%v", vv) + m[k] = func() interface{} { + return vv } } return m } +// Len is for sort.Sort func (t templatePrompts) Len() int { return len(t) } +// Less is for sort.Sort func (t templatePrompts) Less(i, j int) bool { return t[i].Key > t[j].Key } +// Swap is for sort.Sort func (t templatePrompts) Swap(i, j int) { t[i], t[j] = t[j], t[i] } diff --git a/registry/template_test.go b/registry/template_test.go index 11f2d0f..019d3b1 100644 --- a/registry/template_test.go +++ b/registry/template_test.go @@ -8,14 +8,19 @@ import ( ) var ( - tmplContents = `{{title name}} {{.year}}` - tmplTemplate = `name = "john olheiser" + tmplContents = `{{title name}} {{if .bool}}{{.year}}{{end}}` + tmplTemplate = ` +name = "john olheiser" [year] -default = ${TMPL_TEST} +default = ${TMPL_TEST} # 2020 [package] -default = "pkg"` +default = "pkg" + +[bool] +default = true +` tmplGold = "John Olheiser 2020" tmplNewGold = "DO NOT OVERWRITE!" ) -- 2.41.0 From 71213332903e2651e8aaefae6b8c34909cc79519 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Sun, 29 Nov 2020 07:07:18 +0800 Subject: [PATCH 5/9] Small tweaks (#10) Small tweaks Signed-off-by: jolheiser Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/10 Co-Authored-By: John Olheiser Co-Committed-By: John Olheiser --- Makefile | 4 ++++ go.mod | 3 +++ go.sum | 7 +++++++ registry/registry.go | 1 + 4 files changed, 15 insertions(+) diff --git a/Makefile b/Makefile index 6023d00..9a94c1d 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,10 @@ fmt: test: $(GO) test -race ./... +.PHONY: lint +lint: + earth +lint + .PHONY: docs docs: $(GO) run docs.go diff --git a/go.mod b/go.mod index 44fa718..2aca801 100644 --- a/go.mod +++ b/go.mod @@ -6,8 +6,11 @@ require ( github.com/AlecAivazis/survey/v2 v2.2.2 github.com/go-git/go-git/v5 v5.2.0 github.com/huandu/xstrings v1.3.2 + github.com/mattn/go-isatty v0.0.12 // indirect 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 + golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect + golang.org/x/sys v0.0.0-20201113135734-0a15ea8d9b02 // indirect ) diff --git a/go.sum b/go.sum index 701e1fa..08f2928 100644 --- a/go.sum +++ b/go.sum @@ -68,6 +68,8 @@ github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx 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/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= 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/v3 v3.5.0 h1:nE8gZIrw66cu4osS/U7UW7YDuGMHssxKutU8IfWxwWE= @@ -112,6 +114,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk 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/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/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= @@ -122,8 +126,11 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w 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-20200116001909-b77594299b42/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/sys v0.0.0-20201113135734-0a15ea8d9b02 h1:5Ftd3YbC/kANXWCBjvppvUmv1BMakgFcBKA7MpYYp4M= +golang.org/x/sys v0.0.0-20201113135734-0a15ea8d9b02/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= diff --git a/registry/registry.go b/registry/registry.go index f685756..79eafa9 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -186,6 +186,7 @@ func download(cloneURL, branch, dest string) error { ReferenceName: plumbing.NewBranchReferenceName(branch), SingleBranch: true, Depth: 1, + Progress: os.Stdout, }); err != nil { return err } -- 2.41.0 From 19edb3a5809db35bd420536d2bf7f17352b0619e Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Mon, 30 Nov 2020 13:16:25 +0800 Subject: [PATCH 6/9] Add env and restore commands (#11) Add env and restore commands Signed-off-by: jolheiser Check for existance Signed-off-by: jolheiser Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/11 Co-Authored-By: John Olheiser Co-Committed-By: John Olheiser --- CLI.md | 8 ++++++ DOCS.md | 9 +++++- cmd/app.go | 2 ++ cmd/env.go | 34 +++++++++++++++++++++++ cmd/list.go | 4 +-- cmd/restore.go | 39 ++++++++++++++++++++++++++ cmd/update.go | 11 +------- registry/error.go | 13 +++++++++ registry/registry.go | 65 ++++++++++++++++++++++++++++++++++++++------ registry/template.go | 2 +- 10 files changed, 165 insertions(+), 22 deletions(-) create mode 100644 cmd/env.go create mode 100644 cmd/restore.go diff --git a/CLI.md b/CLI.md index 0850bb6..c7c5a8d 100644 --- a/CLI.md +++ b/CLI.md @@ -32,6 +32,10 @@ Download a template **--branch, -b**="": Branch to clone (default: main) +## env + +Show tmpl environment variables + ## init Initialize a template @@ -44,6 +48,10 @@ List templates in the registry Remove a template +## restore + +Restore missing templates + ## save Save a local template diff --git a/DOCS.md b/DOCS.md index 57ffe31..5d70838 100644 --- a/DOCS.md +++ b/DOCS.md @@ -108,4 +108,11 @@ I realize that many users will be using GitHub, and most will likely still be us 1. `tmpl source add https://github.com github` 2. Set the env variable `TMPL_SOURCE` to `github` 2. Set the env variable `TMPL_BRANCH` to `master` -3. Happy templating! `tmpl download user/repo repo` \ No newline at end of file +3. Happy templating! `tmpl download user/repo repo` + +## Backup and Restore + +1. The simplest solution is to make a copy of your `registry.toml` (default: `~/.tmpl/registry.toml`). + * Once in the new location, you will need to use `tmpl restore`. + +2. Alternatively, you can copy/paste the entire registry (default: `~/.tmpl`) and skip the restore step. \ No newline at end of file diff --git a/cmd/app.go b/cmd/app.go index 2c414c1..71c130c 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -51,9 +51,11 @@ func NewApp() *cli.App { app.Commands = []*cli.Command{ Download, + Env, Init, List, Remove, + Restore, Save, Source, Test, diff --git a/cmd/env.go b/cmd/env.go new file mode 100644 index 0000000..3ecc12d --- /dev/null +++ b/cmd/env.go @@ -0,0 +1,34 @@ +package cmd + +import ( + "os" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" + "go.jolheiser.com/beaver/color" +) + +var Env = &cli.Command{ + Name: "env", + Usage: "Show tmpl environment variables", + Description: "Show tmpl environment variables and their configuration", + Action: runEnv, +} + +func runEnv(_ *cli.Context) error { + + // Source + beaver.Infof("TMPL_SOURCE: %s", getEnv("TMPL_SOURCE")) + + // Registry Path + beaver.Infof("TMPL_REGISTRY: %s", getEnv("TMPL_REGISTRY")) + + // Branch + beaver.Infof("TMPL_BRANCH: %s", getEnv("TMPL_BRANCH")) + + return nil +} + +func getEnv(key string) string { + return color.FgHiBlue.Format(os.Getenv(key)) +} diff --git a/cmd/list.go b/cmd/list.go index ec4f5ed..4f7143d 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -25,7 +25,7 @@ func runList(_ *cli.Context) error { } wr := tabwriter.NewWriter(os.Stdout, 0, 8, 1, '\t', 0) - if _, err := fmt.Fprintf(wr, "NAME\tURL\tLOCAL\tUPDATED\n"); err != nil { + if _, err := fmt.Fprintf(wr, "NAME\tURL\tLOCAL\tLAST UPDATED\n"); err != nil { return err } for _, t := range reg.Templates { @@ -35,7 +35,7 @@ func runList(_ *cli.Context) error { u = t.Path local = true } - if _, err := fmt.Fprintf(wr, "%s\t%s\t%t\t%s\n", t.Name, u, local, t.Created.Format("01/02/2006")); err != nil { + if _, err := fmt.Fprintf(wr, "%s\t%s\t%t\t%s\n", t.Name, u, local, t.LastUpdate.Format("01/02/2006")); err != nil { return err } } diff --git a/cmd/restore.go b/cmd/restore.go new file mode 100644 index 0000000..b6a1ecc --- /dev/null +++ b/cmd/restore.go @@ -0,0 +1,39 @@ +package cmd + +import ( + "os" + + "go.jolheiser.com/tmpl/cmd/flags" + "go.jolheiser.com/tmpl/registry" + + "github.com/urfave/cli/v2" + "go.jolheiser.com/beaver" +) + +var Restore = &cli.Command{ + Name: "restore", + Usage: "Restore missing templates", + Description: "Restore templates that are listed in the registry, but are missing archives", + Action: runRestore, +} + +func runRestore(_ *cli.Context) error { + reg, err := registry.Open(flags.Registry) + if err != nil { + return err + } + + var num int + for _, tmpl := range reg.Templates { + if _, err := os.Lstat(tmpl.ArchivePath()); os.IsNotExist(err) { + beaver.Infof("Restoring %s...", tmpl.Name) + if err := reg.UpdateTemplate(tmpl.Name); err != nil { + return err + } + num++ + } + } + + beaver.Infof("Restored %d templates.", num) + return nil +} diff --git a/cmd/update.go b/cmd/update.go index 38cff98..f20ae94 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -31,16 +31,7 @@ func runUpdate(ctx *cli.Context) error { return err } - if err := reg.RemoveTemplate(tmpl.Name); err != nil { - return err - } - - if tmpl.Path != "" { - _, err = reg.SaveTemplate(tmpl.Name, tmpl.Path) - } else { - _, err = reg.DownloadTemplate(tmpl.Name, tmpl.Repository, tmpl.Branch) - } - if err != nil { + if err := reg.UpdateTemplate(tmpl.Name); err != nil { return err } diff --git a/registry/error.go b/registry/error.go index d85f168..f7950f4 100644 --- a/registry/error.go +++ b/registry/error.go @@ -2,6 +2,19 @@ package registry import "fmt" +type ErrTemplateExists struct { + Name string +} + +func (e ErrTemplateExists) Error() string { + return fmt.Sprintf("template %s already exists", e.Name) +} + +func IsErrTemplateExists(err error) bool { + _, ok := err.(ErrTemplateExists) + return ok +} + type ErrTemplateNotFound struct { Name string } diff --git a/registry/registry.go b/registry/registry.go index 79eafa9..d2ddf01 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -50,12 +50,16 @@ func (r *Registry) GetTemplate(name string) (*Template, error) { // DownloadTemplate downloads and adds a new Template to the Registry func (r *Registry) DownloadTemplate(name, repo, branch string) (*Template, error) { + if _, err := r.GetTemplate(name); err == nil { + return nil, ErrTemplateExists{Name: name} + } + t := &Template{ reg: r, Name: name, Repository: repo, Branch: branch, - Created: time.Now(), + LastUpdate: time.Now(), } r.Templates = append(r.Templates, t) @@ -68,11 +72,15 @@ func (r *Registry) DownloadTemplate(name, repo, branch string) (*Template, error // SaveTemplate saves a local Template to the Registry func (r *Registry) SaveTemplate(name, path string) (*Template, error) { + if _, err := r.GetTemplate(name); err == nil { + return nil, ErrTemplateExists{Name: name} + } + t := &Template{ - reg: r, - Name: name, - Path: path, - Created: time.Now(), + reg: r, + Name: name, + Path: path, + LastUpdate: time.Now(), } r.Templates = append(r.Templates, t) @@ -89,16 +97,48 @@ func (r *Registry) RemoveTemplate(name string) error { 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() } } - return r.save() + return nil +} + +// RemoveTemplate updates the Template on disk and in meta +func (r *Registry) UpdateTemplate(name string) error { + _, err := r.GetTemplate(name) + if err != nil { + return err + } + + for idx, t := range r.Templates { + if strings.EqualFold(name, t.Name) { + // If the path doesn't exist, we are replacing it regardless + if err := os.Remove(t.ArchivePath()); err != nil && !os.IsNotExist(err) { + return err + } + + // Cut it out of the template list so we don't get a duplicate + r.Templates = append(r.Templates[:idx], r.Templates[idx+1:]...) + + // If path exists, it is local + if t.Path != "" { + _, err = r.SaveTemplate(t.Name, t.Path) + } else { + _, err = r.DownloadTemplate(t.Name, t.Repository, t.Branch) + } + return err + } + } + + return nil } // GetSource retrieves a Source from the Registry @@ -159,7 +199,16 @@ func Open(dir string) (*Registry, error) { if err != nil { return nil, err } - return ®, tree.Unmarshal(®) + + if err := tree.Unmarshal(®); err != nil { + return nil, err + } + + for _, tmpl := range reg.Templates { + tmpl.reg = ® + } + + return ®, nil } func create(regFile string) error { @@ -191,7 +240,7 @@ func download(cloneURL, branch, dest string) error { return err } - // RemoveTemplate .git + // Remove .git if err := os.RemoveAll(filepath.Join(tmp, ".git")); err != nil { return err } diff --git a/registry/template.go b/registry/template.go index 6703f14..9f37184 100644 --- a/registry/template.go +++ b/registry/template.go @@ -20,7 +20,7 @@ type Template struct { Path string `toml:"path"` Repository string `toml:"repository"` Branch string `toml:"branch"` - Created time.Time `toml:"created"` + LastUpdate time.Time `toml:"last_update"` } // ArchiveName is the name given to the archive for this Template -- 2.41.0 From d4c47101ab93639c8fca37aad97250dbeae577a6 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Tue, 29 Dec 2020 11:21:41 +0800 Subject: [PATCH 7/9] Allow sourcing prompt answers from env variables (#14) Fixes #12 Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/14 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- DOCS.md | 4 ++++ registry/prompt.go | 24 ++++++++++++++---------- registry/registry_test.go | 6 +++--- registry/template.go | 2 +- registry/template_test.go | 11 +++++++++-- 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/DOCS.md b/DOCS.md index 5d70838..638b13b 100644 --- a/DOCS.md +++ b/DOCS.md @@ -15,6 +15,10 @@ A "valid" tmpl template only requires two things The template.toml file will only expand environment variables with syntax `$USER` or `${USER}`. For full documentation on the syntax, see [os.ExpandEnv](https://golang.org/pkg/os/#ExpandEnv). +When using the `--defaults` flag, no prompts will be shown and only default values will be used. +As another alternative, any environment variable that matches a key will bypass the prompt. +For example, `author` would have the corresponding environment variable `TMPL_VAR_AUTHOR`. + ```toml # Key-value pairs can be simple # The user will receive a basic prompt asking them to fill out the variable diff --git a/registry/prompt.go b/registry/prompt.go index 62317c2..d0d34cf 100644 --- a/registry/prompt.go +++ b/registry/prompt.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "sort" + "strings" "text/template" "github.com/AlecAivazis/survey/v2" @@ -67,15 +68,22 @@ func prompt(dir string, defaults bool) (templatePrompts, error) { prompts[idx] = p } - // Return early if we only want defaults - if defaults { - return prompts, nil - } - // Sort the prompts so they are consistent sort.Sort(prompts) for idx, prompt := range prompts { + // Check for env variable + if e, ok := os.LookupEnv(fmt.Sprintf("TMPL_VAR_%s", strings.ToUpper(prompt.Key))); ok { + prompts[idx].Value = e + continue + } + + // Check if we are using defaults + if defaults { + prompts[idx].Value = prompt.Default + continue + } + var p survey.Prompt switch t := prompt.Default.(type) { case []string: @@ -119,11 +127,7 @@ type templatePrompts []templatePrompt func (t templatePrompts) ToMap() map[string]interface{} { m := make(map[string]interface{}) for _, p := range t { - if p.Value != nil { - m[p.Key] = p.Value - continue - } - m[p.Key] = p.Default + m[p.Key] = p.Value } return m } diff --git a/registry/registry_test.go b/registry/registry_test.go index 5b3e217..8adc7ca 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -17,7 +17,7 @@ var ( func TestMain(m *testing.M) { var err error - destDir, err = ioutil.TempDir(os.TempDir(), "tmpl") + destDir, err = ioutil.TempDir(os.TempDir(), "tmpl-dest") if err != nil { panic(err) } @@ -75,7 +75,7 @@ func testGetFail(t *testing.T) { func setupTemplate() { var err error - tmplDir, err = ioutil.TempDir(os.TempDir(), "tmpl") + tmplDir, err = ioutil.TempDir(os.TempDir(), "tmpl-setup") if err != nil { panic(err) } @@ -122,7 +122,7 @@ func setupTemplate() { func setupRegistry() { var err error - regDir, err = ioutil.TempDir(os.TempDir(), "tmpl") + regDir, err = ioutil.TempDir(os.TempDir(), "tmpl-reg") if err != nil { panic(err) } diff --git a/registry/template.go b/registry/template.go index 9f37184..938eff0 100644 --- a/registry/template.go +++ b/registry/template.go @@ -67,7 +67,7 @@ func (t *Template) Execute(dest string, defaults, overwrite bool) error { return err } - newDest := strings.TrimPrefix(walkPath, base+"/") + newDest := strings.TrimPrefix(walkPath, base+string(filepath.Separator)) newDest = filepath.Join(dest, newDest) tmplDest, err := template.New("dest").Funcs(funcs).Parse(newDest) diff --git a/registry/template_test.go b/registry/template_test.go index 019d3b1..dd8c317 100644 --- a/registry/template_test.go +++ b/registry/template_test.go @@ -8,7 +8,7 @@ import ( ) var ( - tmplContents = `{{title name}} {{if .bool}}{{.year}}{{end}}` + tmplContents = `{{title name}} (@{{username}}) {{if .bool}}{{.year}}{{end}}` tmplTemplate = ` name = "john olheiser" @@ -20,8 +20,11 @@ default = "pkg" [bool] default = true + +[username] +default = "username" ` - tmplGold = "John Olheiser 2020" + tmplGold = "John Olheiser (@jolheiser) 2020" tmplNewGold = "DO NOT OVERWRITE!" ) @@ -31,6 +34,10 @@ func testExecute(t *testing.T) { t.Logf("could not set environment: %v", err) t.FailNow() } + if err := os.Setenv("TMPL_VAR_USERNAME", "jolheiser"); err != nil { + t.Logf("could not set environment: %v", err) + t.FailNow() + } // Get template tmpl, err := reg.GetTemplate("test") -- 2.41.0 From c497431a5205cddb6e151f9b6c0e11280b2a7519 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Sun, 3 Jan 2021 11:06:32 +0800 Subject: [PATCH 8/9] Add .tmplkeep (#16) Resolves #15 Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/16 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- registry/registry_test.go | 3 ++- registry/template.go | 5 +++++ registry/template_test.go | 7 +++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/registry/registry_test.go b/registry/registry_test.go index 8adc7ca..628e29d 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -98,7 +98,8 @@ func setupTemplate() { if err := os.MkdirAll(pkgPath, os.ModePerm); err != nil { panic(err) } - fi, err = os.Create(filepath.Join(pkgPath, ".keep")) + // .tmplkeep file + fi, err = os.Create(filepath.Join(pkgPath, ".tmplkeep")) if err != nil { panic(err) } diff --git a/registry/template.go b/registry/template.go index 938eff0..76bf84c 100644 --- a/registry/template.go +++ b/registry/template.go @@ -85,6 +85,11 @@ func (t *Template) Execute(dest string, defaults, overwrite bool) error { return err } + // Skip .tmplkeep files, after creating the directory structure + if strings.EqualFold(walkInfo.Name(), ".tmplkeep") { + return nil + } + oldFi, err := os.Lstat(walkPath) if err != nil { return err diff --git a/registry/template_test.go b/registry/template_test.go index dd8c317..a51717b 100644 --- a/registry/template_test.go +++ b/registry/template_test.go @@ -72,6 +72,13 @@ func testExecute(t *testing.T) { t.FailNow() } + // Check for .tmplkeep + tmplKeep := filepath.Join(pkgPath, ".tmplkeep") + if _, err := os.Lstat(tmplKeep); err == nil { + t.Logf(".tmplkeep files should NOT be retained upon execution: %s\n", tmplKeep) + t.FailNow() + } + // Change file to test non-overwrite if err := ioutil.WriteFile(testPath, []byte(tmplNewGold), os.ModePerm); err != nil { t.Logf("could not write file: %v\n", err) -- 2.41.0 From c173eee38c48424abe470b8ed2b9ef0dd48f65f8 Mon Sep 17 00:00:00 2001 From: John Olheiser Date: Sun, 3 Jan 2021 11:29:37 +0800 Subject: [PATCH 9/9] Explain .tmplkeep (#17) Co-authored-by: jolheiser Reviewed-on: https://gitea.com/jolheiser/tmpl/pulls/17 Co-authored-by: John Olheiser Co-committed-by: John Olheiser --- .gitea/pull_request_template.md | 6 ++++++ DOCS.md | 11 ++++++++++- docs.go | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 .gitea/pull_request_template.md diff --git a/.gitea/pull_request_template.md b/.gitea/pull_request_template.md new file mode 100644 index 0000000..f4def42 --- /dev/null +++ b/.gitea/pull_request_template.md @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/DOCS.md b/DOCS.md index 638b13b..7a3f2b9 100644 --- a/DOCS.md +++ b/DOCS.md @@ -119,4 +119,13 @@ I realize that many users will be using GitHub, and most will likely still be us 1. The simplest solution is to make a copy of your `registry.toml` (default: `~/.tmpl/registry.toml`). * Once in the new location, you will need to use `tmpl restore`. -2. Alternatively, you can copy/paste the entire registry (default: `~/.tmpl`) and skip the restore step. \ No newline at end of file +2. Alternatively, you can copy/paste the entire registry (default: `~/.tmpl`) and skip the restore step. + +## `.tmplkeep` + +Perhaps you are familiar with `.gitkeep` and its unofficial status in git. Git does not like empty directories, so usually +a `.gitkeep` (or just `.keep`) file is added to retain the directory while keeping it effectively empty. + +tmpl instead uses `.tmplkeep` files for this purpose. The difference is, tmpl will **not** create the `.tmplkeep` file +when the template is executed. This allows you to set up directory structures (for staging, examples, etc.) that +will *actually* be empty after execution. \ No newline at end of file diff --git a/docs.go b/docs.go index 3ca29f5..aa13bf6 100644 --- a/docs.go +++ b/docs.go @@ -28,7 +28,7 @@ func main() { md = md[strings.Index(md, "#"):] // CLI is using real default instead of DefaultText - md = regexp.MustCompile(`[\/\w]+\.tmpl`).ReplaceAllString(md, "~/.tmpl") + md = regexp.MustCompile(`[\/\\:\w]+\.tmpl`).ReplaceAllString(md, "~/.tmpl") if _, err := fi.WriteString(md); err != nil { panic(err) -- 2.41.0