tmpl @main -
refs -
log -
-
https://git.jolheiser.com/tmpl.git
Template automation
Docs overhaul, path expansion, more tests, better prompts
Signed-off-by: jolheiser <john.olheiser@gmail.com>
Signature
-----BEGIN PGP SIGNATURE-----
iQIzBAABCgAdFiEEgqEQpE3xoo1QwJO/uFOtpdp7v3oFAl+59kQACgkQuFOtpdp7
v3rGbxAAhFBl6gk3BAvh2Cj7Y5sEFiSoSL9UEtLgL+VVe8xw3oCFM3RbfSXqk6SK
4jTUQqRyyKxi/bsa02HeElvSgEWHkPyvvF3frksS/ME1vCadzIRBtUtNzv/nUcxM
6ZUzumXZ/nk28e8UWQT0BMs5kab963cQH7xRpKLt3jJ8QO+zeZ2FHt340r/0Endk
gjKHMVbIsLP/evmwYd9SLtMc1LiLquFcWv8zA807e2MrM4+ooCkI2HQ6rf4YhgJh
dDilM9JEO5tYpUv40j9JvQuCWa/rAH84U/SUliMWxNjc4jEAK192qNfKHBrn6i4j
ZF/MfD20pwmHL9MSBgwpO0tkwVjC3Mdywf/V74zXd6vswLIzF763RzuK7aGJQC71
R1wzuy3Oy63+amLd+kOXuTj23TOwSyLKO+yG2SwUQ1d/RbT0ZpG9D3aAaxNMFx2k
J0nrGpIzCZr0XhR6kJcJnjV7oIGQr4xcx6tQ1ZdkGDNNfA50zlcbNsbCQB+wtbyo
KEvSF3QnZZixv6emyHZIvxa/GMs8CEB4+yRsBFQEKaO3wnz+p2qqcpgwZXISL8fh
dOkzkLUCYVuW29mtCCmDEYSHCWgY5z5tQCbih/iXtfwa+l/ocMNSy4OGR+BXViae
26HmDTuuhQEqEkT9+kW9sfNfrJpL3p4yW3+P5obV7PhJ9GF/fcE=
=Ghoz
-----END PGP SIGNATURE-----
16 changed files, 446 additions(+), 175 deletions(-)
diff --git a/CLI.md b/CLI.md
new file mode 100644
index 0000000000000000000000000000000000000000..0850bb67c30dabc8434ef792b46ac65b0dde3e2a
--- /dev/null
+++ b/CLI.md
@@ -0,0 +1,81 @@
+# NAME
+
+tmpl - Template automation
+
+# SYNOPSIS
+
+tmpl
+
+```
+[--registry|-r]=[value]
+[--source|-s]=[value]
+```
+
+**Usage**:
+
+```
+tmpl [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
+```
+
+# GLOBAL OPTIONS
+
+**--registry, -r**="": Registry directory of tmpl (default: ~/.tmpl)
+
+**--source, -s**="": Short-name source to use
+
+
+# COMMANDS
+
+## download
+
+Download a template
+
+**--branch, -b**="": Branch to clone (default: main)
+
+## init
+
+Initialize a template
+
+## list
+
+List templates in the registry
+
+## remove
+
+Remove a template
+
+## save
+
+Save a local template
+
+## source
+
+Commands for working with sources
+
+### list
+
+List available sources
+
+### add
+
+Add a source
+
+### remove
+
+Remove a source
+
+## test
+
+Test if a directory is a valid template
+
+## update
+
+Update a template
+
+## use
+
+Use a template
+
+**--defaults**: Use template defaults
+
+**--force**: Overwrite existing files
diff --git a/DOCS.md b/DOCS.md
index 189a2c4b3b4c76c234df85debf04edb3e20e3b3a..889b7f0c14d2300a38eda9b9e92d2970e668f0de 100644
--- a/DOCS.md
+++ b/DOCS.md
@@ -1,79 +1,107 @@
-# NAME
-
-tmpl - Template automation
-
-# SYNOPSIS
+# tmpl templates
-tmpl
-
-```
-[--registry|-r]=[value]
-[--source|-s]=[value]
-```
-
-**Usage**:
+This documentation aims to cover FAQs and setup.
-```
-tmpl [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...]
-```
+## Setting up a template
-# GLOBAL OPTIONS
+A "valid" tmpl template only requires two things
-**--registry, -r**="": Registry directory of tmpl (default: ~/.tmpl)
+1. A `template.toml` file in the root directory.
+2. A `template` directory that serves as the "root" of the template.
-**--source, -s**="": Short-name source to use
+## template.toml
+```toml
+# Key-value pairs can be simple
+# The user will receive a basic prompt asking them to fill out the variable
+project = "my-project"
-# COMMANDS
+# Extended properties MUST be added after any simple key-value pairs (due to how TOML works)
-## download
+# The "key" is enclosed in braces
+[author]
+# prompt is what will be shown to prompt the user
+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"
+```
-Download a template
+## template directory
-**--branch, -b**="": Branch to clone (default: main)
+This directory contains any and all files that are part of the template.
-## init
+Everything in this directory (including paths and file names!) will be executed as a [Go template](https://golang.org/pkg/text/template/).
-Initialize a template
+See the [documentation](https://golang.org/pkg/text/template/) for every available possibility, but some basic examples are...
-## list
+* A variable defined in template.toml (tmpl allows for keys to be called as a func or variable, whichever you prefer!)
+ * `{{project}}` or `{{.project}}`
+ * `{{author}}` or `{{.author}}`
+* Conditionally including something
+ * `{{if eq project ""}} something... {{end}}`
-List templates in the registry
+### template helpers
-## remove
+For a full list, see [helper.go](registry/helper.go)
-Remove a template
+|Helper|Example|Output|
+|-----|-----|-----|
+|upper|`{{upper project}}`|`MY-PROJECT`|
+|lower|`{{lower project}}`|`my-project`|
+|title|`{{title project}}`|`My-Project`|
+|snake|`{{snake project}}`|`my_project`|
+|kebab|`{{kebab project}}`|`my-project`|
+|pascal|`{{pascal project}}`|`MyProject`|
+|camel|`{{camel project}}`|`myProject`|
+|env|`{{env "USER"}}`|The current user|
+|sep|`{{sep}}`|Filepath separator for current OS|
+|time}|`{{time "01/02/2006"}}`|`11/21/2020` - The time according to the given [format](https://flaviocopes.com/go-date-time-format/)|
-## save
+## Sources
-Save a local template
+tmpl was designed to work with any local or git-based template. Unfortunately, in contrast to boilr, this means
+it cannot be used with `user/repo` notation out of the box.
-## source
+However, you _can_ set up a source (and subsequent env variable) to make it easier to use your preferred source while
+still allowing for others.
-Commands for working with sources
+### Setting up a source
-### list
+Let's set up a source for [Gitea](https://gitea.com)
-List available sources
+```
+tmpl source add https://gitea.com gitea
+```
-### add
+To use it, either pass it in with the `--source` flag
-Add a source
+```
+tmpl --source gitea download jolheiser/tmpls tmpls
+```
-### remove
+Or set it as the env variable `TMPL_SOURCE`
-Remove a source
+## Using a different branch
-## test
+By default, tmpl will want to use a branch called `main` in your repository.
-Test if a directory is a valid template
+If you are using another branch as your default, you can set it as the env variable `TMPL_BRANCH`
-## update
+Alternatively, you can specify on the command-line with the `--branch` flag of the `download` command
-Update a template
+```
+tmpl --source gitea download --branch license jolheiser/tmpls license
+```
+The above command would download the [license](https://gitea.com/jolheiser/tmpls/src/branch/license) template from `jolheiser/tmpls`
-## use
+## Putting it all together
-Use a template
+I realize that many users will be using GitHub, and most will likely still be using the `master` branch.
-**--defaults**: Use template defaults
+1. Set up a source for GitHub
+ 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
diff --git a/README.md b/README.md
index 3853e8279d04d75936a9a2966657649ce8322104..ea9fca4db42d71a35902008512fe1f393775e029 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,13 @@ Heavily inspired by [boilr](https://github.com/tmrts/boilr).
The two projects share many similarities, however other than general layout/structure the implementation is entirely my own.
-[CLI Docs](DOCS.md)
+[CLI Docs](CLI.md)
+
+[Project Docs/FAQs](DOCS.md)
## Examples
-Checkout the [license](https://gitea.com/jolheiser/tmpls/src/branch/license) and [makefile](https://gitea.com/jolheiser/tmpls/src/branch/makefile) branch of my [template repository](https://gitea.com/jolheiser/tmpls).
+Check out the [license](https://gitea.com/jolheiser/tmpls/src/branch/license) and [makefile](https://gitea.com/jolheiser/tmpls/src/branch/makefile) branch of my [template repository](https://gitea.com/jolheiser/tmpls).
## License
diff --git a/cmd/download.go b/cmd/download.go
index f88fd7cde5342e9b67dccd104ad47344427699c3..76983a57df3814713e5929dc8d7841167405b70b 100644
--- a/cmd/download.go
+++ b/cmd/download.go
@@ -1,7 +1,6 @@
package cmd
import (
- "errors"
"fmt"
"strings"
@@ -16,6 +15,7 @@ var Download = &cli.Command{
Name: "download",
Usage: "Download a template",
Description: "Download a template and save it to the local registry",
+ ArgsUsage: "[repository URL] [name]",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "branch",
@@ -30,7 +30,7 @@ }
func runDownload(ctx *cli.Context) error {
if ctx.NArg() < 2 {
- return errors.New("<repo> <name>")
+ return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
diff --git a/cmd/init.go b/cmd/init.go
index b5e4e0b83c9e5cec30386c68065c89697a7b0ae3..9f2f5cc21ecc3d9e3ce39a5d1bccc5934ac272e9 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -49,7 +49,8 @@
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"]
+[name]
+prompt = "Project Name"
+help = "The name to use in the project"
+default = "tmpl"
`
diff --git a/cmd/remove.go b/cmd/remove.go
index e753fb4d4106a21cbbde71a2f427c3417d8dc42c..3521a29b7be9000718f399653f8793af6004c105 100644
--- a/cmd/remove.go
+++ b/cmd/remove.go
@@ -1,8 +1,6 @@
package cmd
import (
- "errors"
-
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/registry"
@@ -14,12 +12,13 @@ var Remove = &cli.Command{
Name: "remove",
Usage: "Remove a template",
Description: "Remove a template from the registry",
+ ArgsUsage: "[name]",
Action: runRemove,
}
func runRemove(ctx *cli.Context) error {
if ctx.NArg() < 1 {
- return errors.New("<name>")
+ return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
diff --git a/cmd/save.go b/cmd/save.go
index c7e50dbd99084298695434c6f7d67c9f8c33b53f..cd647ccc57793fd0a675cecb04d4b8e0baffd940 100644
--- a/cmd/save.go
+++ b/cmd/save.go
@@ -1,7 +1,6 @@
package cmd
import (
- "errors"
"path/filepath"
"go.jolheiser.com/tmpl/cmd/flags"
@@ -15,12 +14,13 @@ var Save = &cli.Command{
Name: "save",
Usage: "Save a local template",
Description: "Save a local template to the registry",
+ ArgsUsage: "[path] [name]",
Action: runSave,
}
func runSave(ctx *cli.Context) error {
if ctx.NArg() < 2 {
- return errors.New("<path> <name>")
+ return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
diff --git a/cmd/source.go b/cmd/source.go
index 9514091ca3ff1cb5f9da40e5eed6d2372dace980..e14530e429abd2249e3dd14472bdfd6c00623ade 100644
--- a/cmd/source.go
+++ b/cmd/source.go
@@ -1,7 +1,6 @@
package cmd
import (
- "errors"
"fmt"
"os"
"text/tabwriter"
@@ -37,6 +36,7 @@ SourceAdd = &cli.Command{
Name: "add",
Usage: "Add a source",
Description: "Add a new source to the registry",
+ ArgsUsage: "[base URL] [name]",
Action: runSourceAdd,
}
@@ -44,6 +44,7 @@ SourceRemove = &cli.Command{
Name: "remove",
Usage: "Remove a source",
Description: "Remove a source from the registry",
+ ArgsUsage: "[name]",
Action: runSourceRemove,
}
)
@@ -68,7 +69,7 @@ }
func runSourceAdd(ctx *cli.Context) error {
if ctx.NArg() < 2 {
- return errors.New("<repo> <name>")
+ return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
@@ -87,7 +88,7 @@ }
func runSourceRemove(ctx *cli.Context) error {
if ctx.NArg() < 1 {
- return errors.New("<name>")
+ return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
diff --git a/cmd/test.go b/cmd/test.go
index be6b2585c6c6c53c90e19cd73a3b063b0f72760e..cecaf1193836ee2be8c237ef4972e1140b5ac9dc 100644
--- a/cmd/test.go
+++ b/cmd/test.go
@@ -2,6 +2,7 @@ package cmd
import (
"os"
+ "path/filepath"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
@@ -10,17 +11,23 @@
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",
+ Description: "Test whether a directory is valid for use with tmpl",
+ ArgsUsage: "[path (default: \".\")]",
Action: runTest,
}
-func runTest(_ *cli.Context) error {
+func runTest(ctx *cli.Context) error {
+ testPath := "."
+ if ctx.NArg() > 0 {
+ testPath = ctx.Args().First()
+ }
+
var errs []string
- if _, err := os.Lstat("template.toml"); err != nil {
+ if _, err := os.Lstat(filepath.Join(testPath, "template.toml")); err != nil {
errs = append(errs, "could not find template.toml")
}
- fi, err := os.Lstat("template")
+ fi, err := os.Lstat(filepath.Join(testPath, "template"))
if err != nil {
errs = append(errs, "no template directory found")
}
diff --git a/cmd/update.go b/cmd/update.go
index 00536464d7f774ff260a8de775ed8022e0dd0fa8..38cff987b281718bd378c264ff347f02ae925f6a 100644
--- a/cmd/update.go
+++ b/cmd/update.go
@@ -1,8 +1,6 @@
package cmd
import (
- "errors"
-
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/registry"
@@ -14,12 +12,13 @@ var Update = &cli.Command{
Name: "update",
Usage: "Update a template",
Description: "Update a template in the registry from the original source",
+ ArgsUsage: "[name]",
Action: runUpdate,
}
func runUpdate(ctx *cli.Context) error {
if ctx.NArg() < 1 {
- return errors.New("<name>")
+ return cli.ShowCommandHelp(ctx, ctx.Command.Name)
}
reg, err := registry.Open(flags.Registry)
diff --git a/cmd/use.go b/cmd/use.go
index 6654f4dcc7cee4dceda144e173fe834dbdec8c0a..351b22f9b330e1868c4f9e75390bf6d169d2c4ec 100644
--- a/cmd/use.go
+++ b/cmd/use.go
@@ -1,8 +1,6 @@
package cmd
import (
- "errors"
-
"go.jolheiser.com/tmpl/cmd/flags"
"go.jolheiser.com/tmpl/registry"
@@ -19,13 +17,23 @@ &cli.BoolFlag{
Name: "defaults",
Usage: "Use template defaults",
},
+ &cli.BoolFlag{
+ Name: "force",
+ Usage: "Overwrite existing files",
+ },
},
- Action: runUse,
+ ArgsUsage: "[name] [destination (default: \".\")]",
+ Action: runUse,
}
func runUse(ctx *cli.Context) error {
- if ctx.NArg() < 2 {
- return errors.New("<name> <dest>")
+ if ctx.NArg() < 1 {
+ return cli.ShowCommandHelp(ctx, ctx.Command.Name)
+ }
+
+ dest := "."
+ if ctx.NArg() >= 2 {
+ dest = ctx.Args().Get(1)
}
reg, err := registry.Open(flags.Registry)
@@ -38,7 +46,7 @@ if err != nil {
return err
}
- if err := tmpl.Execute(ctx.Args().Get(1), ctx.Bool("defaults")); err != nil {
+ if err := tmpl.Execute(dest, ctx.Bool("defaults"), ctx.Bool("force")); err != nil {
return err
}
diff --git a/docs.go b/docs.go
index 5f18aae7e9016ae49be44dbea722514c473e7ab9..3ca29f5fce836dde5d89ab9a045b34941d50cbcf 100644
--- a/docs.go
+++ b/docs.go
@@ -13,7 +13,7 @@
func main() {
app := cmd.NewApp()
- fi, err := os.Create("DOCS.md")
+ fi, err := os.Create("CLI.md")
if err != nil {
panic(err)
}
diff --git a/registry/prompt.go b/registry/prompt.go
new file mode 100644
index 0000000000000000000000000000000000000000..b683205df5a629705312d4ad1b5063a3af76ee8d
--- /dev/null
+++ b/registry/prompt.go
@@ -0,0 +1,130 @@
+package registry
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+ "text/template"
+
+ "github.com/AlecAivazis/survey/v2"
+ "github.com/pelletier/go-toml"
+)
+
+type templatePrompt struct {
+ Key string `toml:"-"`
+ Value interface{} `toml:"-"`
+ Message string `toml:"prompt"`
+ Help string `toml:"help"`
+ Default interface{} `toml:"default"`
+}
+
+func prompt(dir string, defaults bool) (templatePrompts, 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
+ }
+
+ prompts := make(templatePrompts, len(tree.Keys()))
+ for idx, k := range tree.Keys() {
+ v := tree.Get(k)
+
+ obj, ok := v.(*toml.Tree)
+ if !ok {
+ prompts[idx] = templatePrompt{
+ Key: k,
+ Message: k,
+ Default: v,
+ }
+ continue
+ }
+
+ var p templatePrompt
+ if err := obj.Unmarshal(&p); err != nil {
+ return nil, err
+ }
+ p.Key = k
+ if p.Message == "" {
+ p.Message = p.Key
+ }
+ if p.Default == nil {
+ p.Default = ""
+ }
+ 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 {
+ var p survey.Prompt
+ switch t := prompt.Default.(type) {
+ case []string:
+ p = &survey.Select{
+ Message: prompt.Message,
+ Options: t,
+ Help: prompt.Help,
+ }
+ default:
+ p = &survey.Input{
+ Message: prompt.Message,
+ Default: fmt.Sprintf("%v", t),
+ Help: prompt.Help,
+ }
+ }
+ var a string
+ if err := survey.AskOne(p, &a); err != nil {
+ return nil, err
+ }
+ prompts[idx].Value = a
+ }
+
+ return prompts, nil
+}
+
+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
+ }
+ return m
+}
+
+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)
+ }
+ }
+ return m
+}
+
+func (t templatePrompts) Len() int {
+ return len(t)
+}
+
+func (t templatePrompts) Less(i, j int) bool {
+ return t[i].Key > t[j].Key
+}
+
+func (t templatePrompts) Swap(i, j int) {
+ t[i], t[j] = t[j], t[i]
+}
diff --git a/registry/registry_test.go b/registry/registry_test.go
index 7c5caac00a2403526d768d4daf263a8933083db5..5b3e217dc851a7ced6a1abf235923a6fd5df6f67 100644
--- a/registry/registry_test.go
+++ b/registry/registry_test.go
@@ -13,11 +13,6 @@ tmplDir string
regDir string
destDir string
reg *Registry
-
- tmplContents = `{{title name}} {{.year}}`
- tmplTemplate = `name = "john olheiser"
-year = 2020`
- tmplGold = "John Olheiser 2020"
)
func TestMain(m *testing.M) {
@@ -78,30 +73,6 @@ t.FailNow()
}
}
-func testExecute(t *testing.T) {
- tmpl, err := reg.GetTemplate("test")
- if err != nil {
- t.Logf("could not get template")
- t.FailNow()
- }
-
- if err := tmpl.Execute(destDir, true); err != nil {
- t.Logf("could not execute template: %v\n", err)
- t.FailNow()
- }
-
- contents, err := ioutil.ReadFile(filepath.Join(destDir, "TEST"))
- if err != nil {
- t.Logf("could not read file: %v\n", err)
- t.FailNow()
- }
-
- if string(contents) != tmplGold {
- t.Logf("contents did not match:\n\tExpected: %s\n\tGot: %s", tmplGold, string(contents))
- t.FailNow()
- }
-}
-
func setupTemplate() {
var err error
tmplDir, err = ioutil.TempDir(os.TempDir(), "tmpl")
@@ -122,10 +93,20 @@ if err := fi.Close(); err != nil {
panic(err)
}
- // Template file
- if err := os.Mkdir(filepath.Join(tmplDir, "template"), os.ModePerm); err != nil {
+ // Template directories
+ pkgPath := filepath.Join(tmplDir, "template", "{{upper package}}")
+ if err := os.MkdirAll(pkgPath, os.ModePerm); err != nil {
panic(err)
}
+ fi, err = os.Create(filepath.Join(pkgPath, ".keep"))
+ if err != nil {
+ panic(err)
+ }
+ if err := fi.Close(); err != nil {
+ panic(err)
+ }
+
+ // Template file
fi, err = os.Create(filepath.Join(tmplDir, "template", "TEST"))
if err != nil {
panic(err)
diff --git a/registry/template.go b/registry/template.go
index e1651dddecde230eaaa8d3e75bab4a42263a5636..6703f14ed7c88637a97c23af162c82fc1bc940c2 100644
--- a/registry/template.go
+++ b/registry/template.go
@@ -1,18 +1,16 @@
package registry
import (
+ "bytes"
"fmt"
"io/ioutil"
"os"
"path/filepath"
- "sort"
"strings"
"text/template"
"time"
- "github.com/AlecAivazis/survey/v2"
"github.com/mholt/archiver/v3"
- "github.com/pelletier/go-toml"
)
// Template is a tmpl project
@@ -36,7 +34,7 @@ return filepath.Join(t.reg.dir, t.ArchiveName())
}
// Execute runs the Template and copies to dest
-func (t *Template) Execute(dest string, defaults bool) error {
+func (t *Template) Execute(dest string, defaults, overwrite bool) error {
tmp, err := ioutil.TempDir(os.TempDir(), "tmpl")
if err != nil {
return err
@@ -47,10 +45,12 @@ if err := archiver.Unarchive(t.ArchivePath(), tmp); err != nil {
return err
}
- vars, err := prompt(tmp, defaults)
+ prompts, err := prompt(tmp, defaults)
if err != nil {
return err
}
+
+ funcs := mergeMaps(funcMap, prompts.ToFuncMap())
base := filepath.Join(tmp, "template")
return filepath.Walk(base, func(walkPath string, walkInfo os.FileInfo, walkErr error) error {
@@ -67,13 +67,19 @@ if err != nil {
return err
}
- tmpl, err := template.New("tmpl").Funcs(mergeMaps(funcMap, convertMap(vars))).Parse(string(contents))
+ newDest := strings.TrimPrefix(walkPath, base+"/")
+ newDest = filepath.Join(dest, newDest)
+
+ tmplDest, err := template.New("dest").Funcs(funcs).Parse(newDest)
if err != nil {
return err
}
- newDest := strings.TrimPrefix(walkPath, base+"/")
- newDest = filepath.Join(dest, newDest)
+ var buf bytes.Buffer
+ if err := tmplDest.Execute(&buf, prompts.ToMap()); err != nil {
+ return err
+ }
+ newDest = buf.String()
if err := os.MkdirAll(filepath.Dir(newDest), os.ModePerm); err != nil {
return err
@@ -83,77 +89,27 @@ oldFi, err := os.Lstat(walkPath)
if err != nil {
return err
}
+
+ // Check if new file exists. If it does, only skip if not overwriting
+ if _, err := os.Lstat(newDest); err == nil && !overwrite {
+ return nil
+ }
+
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 {
+ tmplContents, err := template.New("tmpl").Funcs(funcs).Parse(string(contents))
+ if err != nil {
+ return err
+ }
+ if err := tmplContents.Execute(newFi, prompts.ToMap()); err != nil {
return err
}
return newFi.Close()
})
-}
-
-func prompt(dir string, defaults bool) (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()
-
- // Return early if we only want defaults
- if defaults {
- return vars, nil
- }
-
- // Sort the map keys so they are consistent
- sorted := make([]string, 0, len(vars))
- for k := range vars {
- sorted = append(sorted, k)
- }
- sort.Strings(sorted)
-
- for _, k := range sorted {
- v := vars[k]
- 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),
- }
- }
- var a string
- if err := survey.AskOne(p, &a); err != nil {
- return nil, err
- }
- vars[k] = a
- }
-
- return vars, nil
-}
-
-func convertMap(m map[string]interface{}) template.FuncMap {
- mm := make(template.FuncMap)
- for k, v := range m {
- vv := v // Enclosures in a loop
- mm[k] = func() interface{} {
- return fmt.Sprintf("%v", vv)
- }
- }
- return mm
}
func mergeMaps(maps ...map[string]interface{}) map[string]interface{} {
diff --git a/registry/template_test.go b/registry/template_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..be32ced12b454cee374abb3fed0f5829aa1c1eb4
--- /dev/null
+++ b/registry/template_test.go
@@ -0,0 +1,78 @@
+package registry
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+var (
+ tmplContents = `{{title name}} {{.year}}`
+ tmplTemplate = `name = "john olheiser"
+
+[year]
+default = 2020
+
+[package]
+default = "pkg"`
+ tmplGold = "John Olheiser 2020"
+ tmplNewGold = "DO NOT OVERWRITE!"
+)
+
+func testExecute(t *testing.T) {
+ // Get template
+ tmpl, err := reg.GetTemplate("test")
+ if err != nil {
+ t.Logf("could not get template")
+ t.FailNow()
+ }
+
+ // Execute template
+ if err := tmpl.Execute(destDir, true, true); err != nil {
+ t.Logf("could not execute template: %v\n", err)
+ t.FailNow()
+ }
+
+ // Check contents of file
+ testPath := filepath.Join(destDir, "TEST")
+ contents, err := ioutil.ReadFile(testPath)
+ if err != nil {
+ t.Logf("could not read file: %v\n", err)
+ t.FailNow()
+ }
+
+ if string(contents) != tmplGold {
+ t.Logf("contents did not match:\n\tExpected: %s\n\tGot: %s", tmplGold, string(contents))
+ t.FailNow()
+ }
+
+ // Check if directory was created
+ pkgPath := filepath.Join(destDir, "PKG")
+ if _, err := os.Lstat(pkgPath); err != nil {
+ t.Logf("expected a directory at %s: %v\n", pkgPath, err)
+ 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)
+ t.FailNow()
+ }
+
+ if err := tmpl.Execute(destDir, true, false); err != nil {
+ t.Logf("could not execute template: %v\n", err)
+ t.FailNow()
+ }
+
+ contents, err = ioutil.ReadFile(testPath)
+ if err != nil {
+ t.Logf("could not read file: %v\n", err)
+ t.FailNow()
+ }
+
+ if string(contents) != tmplNewGold {
+ t.Logf("contents did not match:\n\tExpected: %s\n\tGot: %s", tmplNewGold, string(contents))
+ t.FailNow()
+ }
+}