Home

tmpl @main - refs - log -
-
https://git.jolheiser.com/tmpl.git
Template automation
tree log patch
Major changes (#22) 1. Switch from TOML to YAML (oof) 2. Add a JSON schema for templates (anti-oof) 3. Add functionality for prompt defaults to be based on earlier prompt answers 4. Add env commands and setting/loading for tmpl-specific env 5. Use a more helpful testing library Co-authored-by: jolheiser <john.olheiser@gmail.com> Reviewed-on: https://git.jojodev.com/jolheiser/tmpl/pulls/22
jolheiser <john+jojodev@jolheiser.com>
3 years ago
34 changed files, 828 additions(+), 485 deletions(-)
CLI.mdDOCS.mdFAQ.mdREADME.mdcmd/app.gocmd/download.gocmd/env.gocmd/flags/flags.gocmd/init.gocmd/list.gocmd/remove.gocmd/restore.gocmd/save.gocmd/source.gocmd/test.gocmd/update.gocmd/use.goconfig/config.gocli.goenv/env.gogo.modgo.sumregistry/error.goregistry/helper.goregistry/prompt.goregistry/registry.goregistry/registry_test.goregistry/source.goregistry/source_test.goregistry/template.goregistry/template_test.goschema/convert.goschema/schema.goschema/tmpl.json
M CLI.mdCLI.md
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
diff --git a/CLI.md b/CLI.md
index c7c5a8dff80a2b743a70af340c1f4d5c8ca91d30..b9d864705f6b7164e117a588cee922922ca3fc17 100644
--- a/CLI.md
+++ b/CLI.md
@@ -11,6 +11,10 @@ [--registry|-r]=[value]
 [--source|-s]=[value]
 ```
 
+# DESCRIPTION
+
+Template automation
+
 **Usage**:
 
 ```
@@ -35,6 +39,14 @@
 ## env
 
 Show tmpl environment variables
+
+### set, add
+
+Set a tmpl environment variable (stored plaintext)
+
+### unset, delete
+
+Unsets a tmpl environment variable
 
 ## init
 
D DOCS.md
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
diff --git a/DOCS.md b/DOCS.md
deleted file mode 100644
index 005df0b563724c88cc5d4cdd6157eeef2b059cf6..0000000000000000000000000000000000000000
--- a/DOCS.md
+++ /dev/null
@@ -1,131 +0,0 @@
-# tmpl templates
-
-This documentation aims to cover FAQs and setup.
-
-## Setting up a template
-
-A "valid" tmpl template only requires two things
-
-1. A `template.toml` file in the root directory.
-2. A `template` directory that serves as the "root" of the template.
-
-## 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).
-
-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
-project = "my-project"
-
-# Extended properties MUST be added after any simple key-value pairs (due to how TOML works)
-
-# 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 = "$USER"
-```
-
-## template directory
-
-This directory contains any and all files that are part of the template.
-
-Everything in this directory (including paths and file names!) will be executed as a [Go template](https://golang.org/pkg/text/template/).
-
-See the [documentation](https://golang.org/pkg/text/template/) for every available possibility, but some basic examples are...
-
-* 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}}`
-
-### template helpers
-
-For a full list, see [helper.go](registry/helper.go)
-
-|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/)|
-
-## Sources
-
-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. 
-
-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.
-
-### Setting up a source
-
-Let's set up a source for [Gitea](https://gitea.com)
-
-```
-tmpl source add https://gitea.com gitea
-```
-
-To use it, either pass it in with the `--source` flag
-
-```
-tmpl --source gitea download jolheiser/tmpls tmpls
-```
-
-Or set it as the env variable `TMPL_SOURCE`
-
-## Using a different branch
-
-By default, tmpl will want to use a branch called `main` in your repository.
-
-If you are using another branch as your default, you can set it as the env variable `TMPL_BRANCH`
-
-Alternatively, you can specify on the command-line with the `--branch` flag of the `download` command
-
-```
-tmpl --source gitea download --branch license jolheiser/tmpls license
-```
-The above command would download the [license](https://git.jojodev.com/jolheiser/tmpls/src/branch/license) template from `jolheiser/tmpls`
-
-## Putting it all together
-
-I realize that many users will be using GitHub, and most will likely still be using the `master` branch.
-
-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`
-
-## 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.
-
-## `.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
I FAQ.md
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
diff --git a/FAQ.md b/FAQ.md
new file mode 100644
index 0000000000000000000000000000000000000000..b6d813afde7e837d7febfbc90438b0470a39282b
--- /dev/null
+++ b/FAQ.md
@@ -0,0 +1,127 @@
+# tmpl templates
+
+This documentation aims to cover FAQs and setup.
+
+## Setting up a template
+
+A "valid" tmpl template only requires two things
+
+1. A `tmpl.yaml` file in the root directory.
+2. A `template` directory that serves as the "root" of the template.
+
+## tmpl.yaml
+
+**NOTE:** The tmpl.yaml file will be expanded, though not with the full power of the template itself.  
+The tmpl.yaml 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`.
+
+```yaml
+# tmpl.yaml
+# Write any template args here to prompt the user for, giving any defaults/options as applicable
+
+prompts:
+  - id: project                           # The unique ID for the prompt
+    label: Project Name                   # The prompt message/label
+    help: The name to use in the project  # Optional help message for the prompt
+    default: tmpl                         # Prompt default
+```
+
+## template directory
+
+This directory contains any and all files that are part of the template.
+
+Everything in this directory (including paths and file names!) will be executed as a [Go template](https://golang.org/pkg/text/template/).
+
+See the [documentation](https://golang.org/pkg/text/template/) for every available possibility, but some basic examples are...
+
+* An id defined in `tmpl.yaml` (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}}`
+
+### template helpers
+
+For a full list, see [helper.go](registry/helper.go)
+
+| 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/) |
+| trim_prefix | `{{trim_prefix "foobar" "foo"}}` | `bar`                                                                                                 |
+| trim_suffix | `{{trim_suffix "foobar" "bar"}}` | `foo`                                                                                                 |
+
+## Sources
+
+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. 
+
+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.
+
+### Setting up a source
+
+Let's set up a source for [Gitea](https://gitea.com)
+
+```
+tmpl source add https://gitea.com gitea
+```
+
+To use it, either pass it in with the `--source` flag
+
+```
+tmpl --source gitea download jolheiser/tmpls tmpls
+```
+
+Or set it as the env variable `TMPL_SOURCE`
+
+## Using a different branch
+
+By default, tmpl will want to use a branch called `main` in your repository.
+
+If you are using another branch as your default, you can set it as the env variable `TMPL_BRANCH`
+
+Alternatively, you can specify on the command-line with the `--branch` flag of the `download` command
+
+```
+tmpl --source gitea download --branch license jolheiser/tmpls license
+```
+The above command would download the [license](https://git.jojodev.com/jolheiser/tmpls/src/branch/license) template from `jolheiser/tmpls`
+
+## Putting it all together
+
+I realize that many users will be using GitHub, and most will likely still be using the `master` branch.
+
+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`
+
+## 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.
+
+## `.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
M README.mdREADME.md
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
diff --git a/README.md b/README.md
index c41731578629b89b465b05f4d313687028121a12..70e91f82201dccdd553b45715ef5965c71959076 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ The two projects share many similarities, however other than general layout/structure the implementation is entirely my own.
 
 [CLI Docs](CLI.md)
 
-[Project Docs/FAQs](DOCS.md)
+[Project Docs/FAQs](FAQ.md)
 
 ## Examples 
 
M cmd/app.gocmd/app.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
diff --git a/cmd/app.go b/cmd/app.go
index 1befa0cbcea8e5b05f82a7a775cfc8558fc865af..a5a9d9c769492e405d18fcc3d5aadcc2b94a3152 100644
--- a/cmd/app.go
+++ b/cmd/app.go
@@ -4,8 +4,6 @@ import (
 	"os"
 	"path/filepath"
 
-	"go.jolheiser.com/tmpl/cmd/flags"
-
 	"github.com/rs/zerolog/log"
 	"github.com/urfave/cli/v2"
 )
@@ -13,12 +11,16 @@
 var (
 	Version    = "develop"
 	defaultDir string
+
+	registryFlag string
+	sourceFlag   string
 )
 
 func init() {
 	home, err := os.UserHomeDir()
 	if err != nil {
 		log.Error().Msg("could not locate user's home directory, tmpl will use temp dir for registry")
+		defaultDir = filepath.Join(os.TempDir(), ".tmpl")
 		return
 	}
 	defaultDir = filepath.Join(home, ".tmpl")
@@ -37,14 +39,14 @@ 			Aliases:     []string{"r"},
 			Usage:       "Registry directory of tmpl",
 			Value:       defaultDir,
 			DefaultText: "~/.tmpl",
-			Destination: &flags.Registry,
+			Destination: &registryFlag,
 			EnvVars:     []string{"TMPL_REGISTRY"},
 		},
 		&cli.StringFlag{
 			Name:        "source",
 			Aliases:     []string{"s"},
 			Usage:       "Short-name source to use",
-			Destination: &flags.Source,
+			Destination: &sourceFlag,
 			EnvVars:     []string{"TMPL_SOURCE"},
 		},
 	}
M cmd/download.gocmd/download.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
diff --git a/cmd/download.go b/cmd/download.go
index dac63bb158bcdff5d9545b71f626bef15fa867a1..90469dd9fb0fc9569ddd1ee10df2302810a6faa3 100644
--- a/cmd/download.go
+++ b/cmd/download.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"strings"
 
-	"go.jolheiser.com/tmpl/cmd/flags"
 	"go.jolheiser.com/tmpl/registry"
 
 	"github.com/rs/zerolog/log"
@@ -33,21 +32,21 @@ 	if ctx.NArg() < 2 {
 		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
 	}
 
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
 		return err
 	}
 
 	var source *registry.Source
-	if flags.Source != "" {
+	if sourceFlag != "" {
 		for _, s := range reg.Sources {
-			if strings.EqualFold(s.Name, flags.Source) {
+			if strings.EqualFold(s.Name, sourceFlag) {
 				source = s
 				break
 			}
 		}
 		if source == nil {
-			return fmt.Errorf("could not find source for %s", flags.Source)
+			return fmt.Errorf("could not find source for %s", sourceFlag)
 		}
 	}
 
M cmd/env.gocmd/env.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
diff --git a/cmd/env.go b/cmd/env.go
index 703efbf63b1920ca7ccb692c3935bfbc507570ba..de252d23460217043be455e963b8777aa7a762a1 100644
--- a/cmd/env.go
+++ b/cmd/env.go
@@ -3,6 +3,8 @@
 import (
 	"os"
 
+	"go.jolheiser.com/tmpl/env"
+
 	"github.com/rs/zerolog/log"
 	"github.com/urfave/cli/v2"
 )
@@ -12,20 +14,79 @@ 	Name:        "env",
 	Usage:       "Show tmpl environment variables",
 	Description: "Show tmpl environment variables and their configuration",
 	Action:      runEnv,
+	Subcommands: []*cli.Command{
+		{
+			Name:        "set",
+			Aliases:     []string{"add"},
+			Usage:       "Set a tmpl environment variable (stored plaintext)",
+			Description: "Set an environment variable that will be loaded specifically when running tmpl (stored plaintext)",
+			ArgsUsage:   "[key] [value]",
+			Action:      runEnvSet,
+		},
+		{
+			Name:        "unset",
+			Aliases:     []string{"delete"},
+			Usage:       "Unsets a tmpl environment variable",
+			Description: "Unsets an environment variable previously set for tmpl",
+			ArgsUsage:   "[key]",
+			Action:      runEnvUnset,
+		},
+	},
 }
 
 func runEnv(_ *cli.Context) error {
-
 	// Source
-	log.Info().Str("TMPL_SOURCE", os.Getenv("TMPL_SOURCE")).Msg("")
+	log.Info().Str("TMPL_SOURCE", os.Getenv("TMPL_SOURCE")).Msg("system")
 
 	// Registry Path
-	log.Info().Str("TMPL_REGISTRY", os.Getenv("TMPL_REGISTRY")).Msg("")
+	log.Info().Str("TMPL_REGISTRY", os.Getenv("TMPL_REGISTRY")).Msg("system")
 
 	// Branch
-	log.Info().Str("TMPL_BRANCH", os.Getenv("TMPL_BRANCH")).Msg("")
+	log.Info().Str("TMPL_BRANCH", os.Getenv("TMPL_BRANCH")).Msg("system")
+
+	// Custom
+	e, err := env.Load(registryFlag)
+	if err != nil {
+		return err
+	}
+	for key, val := range e {
+		log.Info().Str(key, val).Msg("env.json")
+	}
 
 	return nil
 }
 
+func runEnvSet(ctx *cli.Context) error {
+	if ctx.NArg() < 2 {
+		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
+	}
+	e, err := env.Load(registryFlag)
+	if err != nil {
+		return err
+	}
+	key, val := ctx.Args().Get(0), ctx.Args().Get(1)
+	e[key] = val
+	if err := env.Save(registryFlag, e); err != nil {
+		return err
+	}
+	log.Info().Str(key, val).Msg("Successfully saved tmpl environment variable!")
+	return nil
+}
 
+func runEnvUnset(ctx *cli.Context) error {
+	if ctx.NArg() < 1 {
+		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
+	}
+	e, err := env.Load(registryFlag)
+	if err != nil {
+		return err
+	}
+	key := ctx.Args().First()
+	val := e[key]
+	delete(e, key)
+	if err := env.Save(registryFlag, e); err != nil {
+		return err
+	}
+	log.Info().Str(key, val).Msg("Successfully unset tmpl environment variable!")
+	return nil
+}
D cmd/flags/flags.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
diff --git a/cmd/flags/flags.go b/cmd/flags/flags.go
deleted file mode 100644
index 8bc7cac7eb466a22fda15b2a76f075f6fed2fb17..0000000000000000000000000000000000000000
--- a/cmd/flags/flags.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package flags
-
-var (
-	Registry string
-	Source   string
-)
M cmd/init.gocmd/init.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
diff --git a/cmd/init.go b/cmd/init.go
index 08ba88c7ae296de73659fa81cba2a498725de5c1..61c916c5ecfed7e0cb3716dae628cff5e8f93a62 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -16,11 +16,11 @@ 	Action:      runInit,
 }
 
 func runInit(_ *cli.Context) error {
-	if _, err := os.Lstat("template.toml"); !os.IsNotExist(err) {
+	if _, err := os.Lstat("tmpl.yaml"); !os.IsNotExist(err) {
 		if err != nil {
 			return err
 		}
-		return errors.New("template.toml already detected, aborting initialization")
+		return errors.New("tmpl.yaml already detected, aborting initialization")
 	}
 	if fi, err := os.Lstat("template"); !os.IsNotExist(err) {
 		if err != nil {
@@ -32,7 +32,7 @@ 		}
 		return errors.New("template directory already detected, aborting initialization")
 	}
 
-	fi, err := os.Create("template.toml")
+	fi, err := os.Create("tmpl.yaml")
 	if err != nil {
 		return err
 	}
@@ -46,11 +46,12 @@ 	log.Info().Msg("Template initialized!")
 	return fi.Close()
 }
 
-var comments = `# template.toml
+var comments = `# tmpl.yaml
 # Write any template args here to prompt the user for, giving any defaults/options as applicable
 
-[name]
-prompt = "Project Name"
-help = "The name to use in the project"
-default = "tmpl"
+prompts:
+  - id: name                              # The unique ID for the prompt
+    label: Project Name                   # The prompt message/label
+    help: The name to use in the project  # Optional help message for the prompt
+    default: tmpl                         # Prompt default
 `
M cmd/list.gocmd/list.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/cmd/list.go b/cmd/list.go
index 4f7143d018b960402d3ec232b9f94da9cf0a9c36..32002bef12ef1fed10fd1aab583bc62557307709 100644
--- a/cmd/list.go
+++ b/cmd/list.go
@@ -5,7 +5,6 @@ 	"fmt"
 	"os"
 	"text/tabwriter"
 
-	"go.jolheiser.com/tmpl/cmd/flags"
 	"go.jolheiser.com/tmpl/registry"
 
 	"github.com/urfave/cli/v2"
@@ -19,7 +18,7 @@ 	Action:      runList,
 }
 
 func runList(_ *cli.Context) error {
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
 		return err
 	}
M cmd/remove.gocmd/remove.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/cmd/remove.go b/cmd/remove.go
index 98001187a55600aca14c128a72e774960bd0af18..c424517bcf844ca9e0dcdf68b92f4f55e5939fb4 100644
--- a/cmd/remove.go
+++ b/cmd/remove.go
@@ -1,7 +1,6 @@
 package cmd
 
 import (
-	"go.jolheiser.com/tmpl/cmd/flags"
 	"go.jolheiser.com/tmpl/registry"
 
 	"github.com/rs/zerolog/log"
@@ -21,7 +20,7 @@ 	if ctx.NArg() < 1 {
 		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
 	}
 
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
 		return err
 	}
M cmd/restore.gocmd/restore.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/cmd/restore.go b/cmd/restore.go
index cf96172f2d8f833eaeaa618b916c13383ab2a5a6..c88dc25aa5c5b2636d7f95b11046f85dc1a69319 100644
--- a/cmd/restore.go
+++ b/cmd/restore.go
@@ -3,7 +3,6 @@
 import (
 	"os"
 
-	"go.jolheiser.com/tmpl/cmd/flags"
 	"go.jolheiser.com/tmpl/registry"
 
 	"github.com/rs/zerolog/log"
@@ -18,7 +17,7 @@ 	Action:      runRestore,
 }
 
 func runRestore(_ *cli.Context) error {
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
 		return err
 	}
M cmd/save.gocmd/save.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/cmd/save.go b/cmd/save.go
index 8b8ccfc5c9f762fc82cc8f45f66e5ced748fe5b6..508f683abe050e43358c2e62c0fa77cf08202879 100644
--- a/cmd/save.go
+++ b/cmd/save.go
@@ -3,7 +3,6 @@
 import (
 	"path/filepath"
 
-	"go.jolheiser.com/tmpl/cmd/flags"
 	"go.jolheiser.com/tmpl/registry"
 
 	"github.com/rs/zerolog/log"
@@ -23,7 +22,7 @@ 	if ctx.NArg() < 2 {
 		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
 	}
 
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
 		return err
 	}
M cmd/source.gocmd/source.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
diff --git a/cmd/source.go b/cmd/source.go
index 355038edaedb86efc868c91bd99ffce02fe67824..1a5f820279092622d0ae16f29eb41e8269d1ce3a 100644
--- a/cmd/source.go
+++ b/cmd/source.go
@@ -5,7 +5,6 @@ 	"fmt"
 	"os"
 	"text/tabwriter"
 
-	"go.jolheiser.com/tmpl/cmd/flags"
 	"go.jolheiser.com/tmpl/registry"
 
 	"github.com/rs/zerolog/log"
@@ -50,7 +49,7 @@ 	}
 )
 
 func runSourceList(_ *cli.Context) error {
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
 		return err
 	}
@@ -72,7 +71,7 @@ 	if ctx.NArg() < 2 {
 		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
 	}
 
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
 		return err
 	}
@@ -91,7 +90,7 @@ 	if ctx.NArg() < 1 {
 		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
 	}
 
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
 		return err
 	}
M cmd/test.gocmd/test.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
diff --git a/cmd/test.go b/cmd/test.go
index 44502237b7bb70d29c2cb7f798a7bbad6f52e1c7..1cb327feccc9469153c26270bd0786960ecb5a56 100644
--- a/cmd/test.go
+++ b/cmd/test.go
@@ -1,8 +1,12 @@
 package cmd
 
 import (
+	"errors"
+	"fmt"
 	"os"
 	"path/filepath"
+
+	"go.jolheiser.com/tmpl/schema"
 
 	"github.com/rs/zerolog/log"
 	"github.com/urfave/cli/v2"
@@ -23,15 +27,28 @@ 		testPath = ctx.Args().First()
 	}
 
 	var errs []string
-	if _, err := os.Lstat(filepath.Join(testPath, "template.toml")); err != nil {
-		errs = append(errs, "could not find template.toml")
+
+	fi, err := os.Open(filepath.Join(testPath, "tmpl.yaml"))
+	if err != nil {
+		errs = append(errs, fmt.Sprintf("could not open tmpl.yaml: %v", err))
+	}
+	defer fi.Close()
+	if err := schema.Lint(fi); err != nil {
+		var rerr schema.ResultErrors
+		if errors.As(err, &rerr) {
+			for _, re := range rerr {
+				errs = append(errs, fmt.Sprintf("%s: %s", re.Field(), re.Description()))
+			}
+		} else {
+			errs = append(errs, fmt.Sprintf("could not lint tmpl.yaml: %v", err))
+		}
 	}
 
-	fi, err := os.Lstat(filepath.Join(testPath, "template"))
+	fstat, err := os.Lstat(filepath.Join(testPath, "template"))
 	if err != nil {
 		errs = append(errs, "no template directory found")
 	}
-	if err == nil && !fi.IsDir() {
+	if err == nil && !fstat.IsDir() {
 		errs = append(errs, "template path is a file, not a directory")
 	}
 
@@ -41,6 +58,7 @@ 			log.Error().Msg(err)
 		}
 		return nil
 	}
-	log.Info().Msg("this is a valid tmpl template")
+
+	log.Info().Msg("This is a valid tmpl template!")
 	return nil
 }
M cmd/update.gocmd/update.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
diff --git a/cmd/update.go b/cmd/update.go
index d621bc6422fb148c34ad2ff981d1322f3b00e90b..f71efe3190e2ddc3f27d02012ce65867eb66a5ea 100644
--- a/cmd/update.go
+++ b/cmd/update.go
@@ -1,7 +1,6 @@
 package cmd
 
 import (
-	"go.jolheiser.com/tmpl/cmd/flags"
 	"go.jolheiser.com/tmpl/registry"
 
 	"github.com/rs/zerolog/log"
@@ -21,7 +20,7 @@ 	if ctx.NArg() < 1 {
 		return cli.ShowCommandHelp(ctx, ctx.Command.Name)
 	}
 
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
 		return err
 	}
M cmd/use.gocmd/use.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
diff --git a/cmd/use.go b/cmd/use.go
index 74fa3d63419e3e3e4f911774246461cf0c41dd17..dfe03596ac42866074a2b71d08449336a5bbdfad 100644
--- a/cmd/use.go
+++ b/cmd/use.go
@@ -1,7 +1,7 @@
 package cmd
 
 import (
-	"go.jolheiser.com/tmpl/cmd/flags"
+	"go.jolheiser.com/tmpl/env"
 	"go.jolheiser.com/tmpl/registry"
 
 	"github.com/rs/zerolog/log"
@@ -36,8 +36,16 @@ 	if ctx.NArg() >= 2 {
 		dest = ctx.Args().Get(1)
 	}
 
-	reg, err := registry.Open(flags.Registry)
+	reg, err := registry.Open(registryFlag)
 	if err != nil {
+		return err
+	}
+
+	e, err := env.Load(registryFlag)
+	if err != nil {
+		return err
+	}
+	if err := e.Set(); err != nil {
 		return err
 	}
 
I config/config.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000000000000000000000000000000000000..c3067433cddb69cb037ade4d4a0ede6116f8d7ea
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,41 @@
+package config
+
+import (
+	"fmt"
+	"io"
+	"os"
+	"strings"
+
+	"gopkg.in/yaml.v3"
+)
+
+// Config is a tmpl config
+type Config struct {
+	Prompts []Prompt `yaml:"prompts"`
+}
+
+// Prompt is a tmpl prompt
+type Prompt struct {
+	ID      string `yaml:"id"`
+	Label   string `yaml:"label"`
+	Help    string `yaml:"help"`
+	Default any    `yaml:"default"`
+}
+
+// Load loads a tmpl config
+func Load(r io.Reader) (*Config, error) {
+	configBytes, err := io.ReadAll(r)
+	if err != nil {
+		return nil, err
+	}
+
+	configContents := os.Expand(string(configBytes), func(s string) string {
+		if strings.HasPrefix(s, "TMPL_PROMPT") {
+			return fmt.Sprintf("${%s}", s)
+		}
+		return os.Getenv(s)
+	})
+
+	var c Config
+	return &c, yaml.Unmarshal([]byte(configContents), &c)
+}
M docs.gocli.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
diff --git a/docs.go b/cli.go
rename from docs.go
rename to cli.go
index 00deb1fa59f2a2883ecdb97fa51185fc7f1c512c..d7d598801e84a27d4ff05b6db88ff139fe6d429c 100644
--- a/docs.go
+++ b/cli.go
@@ -11,7 +11,7 @@
 	"go.jolheiser.com/tmpl/cmd"
 )
 
-//go:generate go run docs.go
+//go:generate go run cli.go
 func main() {
 	app := cmd.NewApp()
 
I env/env.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
diff --git a/env/env.go b/env/env.go
new file mode 100644
index 0000000000000000000000000000000000000000..6c091ec0bc154db6a7e552394d31622ec8fb6774
--- /dev/null
+++ b/env/env.go
@@ -0,0 +1,50 @@
+package env
+
+import (
+	"encoding/json"
+	"errors"
+	"os"
+	"path/filepath"
+)
+
+// Env is tmpl environment variables
+type Env map[string]string
+
+// Set sets all environment variables from an Env
+func (e Env) Set() error {
+	for key, val := range e {
+		if err := os.Setenv(key, val); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Load loads an env from <path>/env.json
+func Load(path string) (Env, error) {
+	p := filepath.Join(path, "env.json")
+	fi, err := os.Open(p)
+	if err != nil {
+		if errors.Is(err, os.ErrNotExist) {
+			return Env{}, nil
+		}
+		return nil, err
+	}
+	defer fi.Close()
+
+	var e Env
+	if err := json.NewDecoder(fi).Decode(&e); err != nil {
+		return nil, err
+	}
+	return e, nil
+}
+
+// Save saves an Env to <path>/env.json
+func Save(path string, e Env) error {
+	p := filepath.Join(path, "env.json")
+	fi, err := os.Create(p)
+	if err != nil {
+		return err
+	}
+	return json.NewEncoder(fi).Encode(e)
+}
M go.modgo.mod
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
diff --git a/go.mod b/go.mod
index 63613e52265b7222b269d692526d2eb288e9ce40..e0e059104ef876ea2f60394458d255f3f99c2697 100644
--- a/go.mod
+++ b/go.mod
@@ -1,15 +1,55 @@
 module go.jolheiser.com/tmpl
 
-go 1.15
+go 1.18
 
 require (
-	github.com/AlecAivazis/survey/v2 v2.2.2
-	github.com/go-git/go-git/v5 v5.2.0
+	github.com/AlecAivazis/survey/v2 v2.3.5
+	github.com/go-git/go-git/v5 v5.4.2
 	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/rs/zerolog v1.26.0
-	github.com/urfave/cli/v2 v2.3.0
-	golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
+	github.com/matryer/is v1.4.0
+	github.com/mholt/archiver/v3 v3.5.1
+	github.com/rs/zerolog v1.27.0
+	github.com/urfave/cli/v2 v2.8.1
+	github.com/xeipuuv/gojsonschema v1.2.0
+	gopkg.in/yaml.v3 v3.0.1
+)
+
+require (
+	github.com/Microsoft/go-winio v0.5.2 // indirect
+	github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b // indirect
+	github.com/acomagu/bufpipe v1.0.3 // indirect
+	github.com/andybalholm/brotli v1.0.4 // indirect
+	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
+	github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
+	github.com/emirpasic/gods v1.18.1 // indirect
+	github.com/go-git/gcfg v1.5.0 // indirect
+	github.com/go-git/go-billy/v5 v5.3.1 // indirect
+	github.com/golang/snappy v0.0.4 // indirect
+	github.com/imdario/mergo v0.3.13 // indirect
+	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
+	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
+	github.com/kevinburke/ssh_config v1.2.0 // indirect
+	github.com/klauspost/compress v1.15.6 // indirect
+	github.com/klauspost/pgzip v1.2.5 // indirect
+	github.com/mattn/go-colorable v0.1.12 // indirect
+	github.com/mattn/go-isatty v0.0.14 // indirect
+	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
+	github.com/mitchellh/go-homedir v1.1.0 // indirect
+	github.com/nwaples/rardecode v1.1.3 // indirect
+	github.com/pierrec/lz4/v4 v4.1.14 // indirect
+	github.com/russross/blackfriday/v2 v2.1.0 // indirect
+	github.com/sergi/go-diff v1.2.0 // indirect
+	github.com/stretchr/testify v1.7.1 // indirect
+	github.com/ulikunitz/xz v0.5.10 // indirect
+	github.com/xanzy/ssh-agent v0.3.1 // indirect
+	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
+	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+	github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect
+	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
+	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
+	golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
+	golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect
+	golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 // indirect
+	golang.org/x/text v0.3.7 // indirect
+	gopkg.in/warnings.v0 v0.1.2 // indirect
 )
M go.sumgo.sum
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
diff --git a/go.sum b/go.sum
index 2901739a5e00ee535150e20ed6c4e4a11a11d624..5be3a0dc3a3922a52626dad82630f2c5d8246b58 100644
--- a/go.sum
+++ b/go.sum
@@ -1,162 +1,202 @@
-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 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
-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 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
-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/AlecAivazis/survey/v2 v2.3.5 h1:A8cYupsAZkjaUmhtTYv3sSqc7LO5mp1XDfqe5E/9wRQ=
+github.com/AlecAivazis/survey/v2 v2.3.5/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI=
+github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
+github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
+github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
+github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
+github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
+github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
+github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
+github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b h1:lcbBNuQhppsc7A5gjdHmdlqUqJfgGMylBdGyDs0j7G8=
+github.com/ProtonMail/go-crypto v0.0.0-20220517143526-88bb52951d5b/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
+github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk=
+github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
+github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
+github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
+github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
 github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
-github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
-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/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
+github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
+github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
-github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
+github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
+github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
 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 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
 github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
 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-fixtures/v4 v4.0.2-0.20200613231340-f56387b50c12 h1:PbKy9zOy4aAKrJ5pibIRpVO2BXnK1Tlcg+caKI7Ox5M=
-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/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34=
+github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
+github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8=
+github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0=
+github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4=
+github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc=
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
-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 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
+github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
+github.com/golang/snappy v0.0.4/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 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
-github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
+github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
+github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
 github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
 github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
-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/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
+github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
 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/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
+github.com/kevinburke/ssh_config v1.2.0/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/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.15.6 h1:6D9PcO8QWu0JyaQ2zUMmu16T1T+zjjEpP91guRsvDfY=
+github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
 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/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE=
+github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
-github.com/kr/pty v1.1.4 h1:5Myjjh3JY/NaAi4IsUbHADytDyl1VE1Y9PXDlL+P/VQ=
-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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 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/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
+github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
 github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
+github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
 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/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
+github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
 github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
-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/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
+github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
+github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
+github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
 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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
 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/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc=
+github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0=
+github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
+github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
-github.com/rs/zerolog v1.26.0 h1:ORM4ibhEZeTeQlCojCK2kPz1ogAY4bGs4tD+SaAdGaE=
-github.com/rs/zerolog v1.26.0/go.mod h1:yBiM87lvSqX8h0Ww4sdzNSkVYZ8dL2xjZJG1lAuGZEo=
-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/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
+github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 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/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
+github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 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/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/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8=
+github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/urfave/cli/v2 v2.8.1 h1:CGuYNZF9IKZY/rfBe3lJpccSoIY1ytfvmgQT90cNOl4=
+github.com/urfave/cli/v2 v2.8.1/go.mod h1:Z41J9TPoffeoqP0Iza0YbAhGvymRdZAd2uPmZ5JxRdY=
+github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0=
+github.com/xanzy/ssh-agent v0.3.1 h1:AmzO1SSWxw73zxFZPRwaMN1MohDw8UyHnmuxyceTEGo=
+github.com/xanzy/ssh-agent v0.3.1/go.mod h1:QIE4lCeL7nkC25x+yA3LBIYfwCc1TFziCtG7cBAac6w=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
 github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
-github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
 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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
-golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI=
-golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-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/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
+golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8=
+golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s=
+golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
-golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
+golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM=
+golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
-golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 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 h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 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 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
 gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
M registry/error.goregistry/error.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
diff --git a/registry/error.go b/registry/error.go
index f7950f4e65e6ce1efca168980d7f4479cfc92c2a..168ed929ef5f93ace3fb96ec123d759ef18afcf5 100644
--- a/registry/error.go
+++ b/registry/error.go
@@ -23,11 +23,6 @@ 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
 }
M registry/helper.goregistry/helper.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
diff --git a/registry/helper.go b/registry/helper.go
index 2ce93cb0cc6ec2b8975c8b6b34c8cf6ef05d1eab..a9bb49d27217bd38b0e6b206d98e7ded4830bb7a 100644
--- a/registry/helper.go
+++ b/registry/helper.go
@@ -9,8 +9,7 @@
 	"github.com/huandu/xstrings"
 )
 
-var funcMap = map[string]interface{}{
-
+var funcMap = map[string]any{
 	// String conversions
 	"upper":  strings.ToUpper,
 	"lower":  strings.ToLower,
@@ -20,6 +19,12 @@ 	"kebab":  xstrings.ToKebabCase,
 	"pascal": xstrings.ToCamelCase,
 	"camel": func(in string) string {
 		return xstrings.FirstRuneToLower(xstrings.ToCamelCase(in))
+	},
+	"trim_prefix": func(in, trim string) string {
+		return strings.TrimPrefix(in, trim)
+	},
+	"trim_suffix": func(in, trim string) string {
+		return strings.TrimSuffix(in, trim)
 	},
 
 	// Other
M registry/prompt.goregistry/prompt.go
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
diff --git a/registry/prompt.go b/registry/prompt.go
index d0d34cfa2852248c909e09c5b547608325068458..e12adcecb9baf93c4ce71d614afe9f25f9a1d766 100644
--- a/registry/prompt.go
+++ b/registry/prompt.go
@@ -2,112 +2,97 @@ package registry
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
-	"sort"
 	"strings"
 	"text/template"
+
+	"go.jolheiser.com/tmpl/config"
 
 	"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
-	}
-
-	templateBytes, err := ioutil.ReadFile(templatePath)
+	templatePath := filepath.Join(dir, "tmpl.yaml")
+	fi, err := os.Open(templatePath)
 	if err != nil {
 		return nil, err
 	}
-
-	// Expand the template with environment variables
-	templateContents := os.ExpandEnv(string(templateBytes))
+	defer fi.Close()
 
-	tree, err := toml.Load(templateContents)
+	cfg, err := config.Load(fi)
 	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
+	prompts := make(templatePrompts, 0, len(cfg.Prompts))
+	for _, prompt := range cfg.Prompts {
+		tp := templatePrompt{
+			Prompt: prompt,
 		}
-
-		var p templatePrompt
-		if err := obj.Unmarshal(&p); err != nil {
-			return nil, err
+		if tp.Label == "" {
+			tp.Label = tp.ID
 		}
-		p.Key = k
-		if p.Message == "" {
-			p.Message = p.Key
+		if tp.Default == nil {
+			tp.Default = ""
 		}
-		if p.Default == nil {
-			p.Default = ""
-		}
-		prompts[idx] = p
+		prompts = append(prompts, tp)
 	}
 
-	// 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 {
+		envKey := strings.ToUpper(prompt.ID)
+		if e, ok := os.LookupEnv(fmt.Sprintf("TMPL_VAR_%s", envKey)); ok {
 			prompts[idx].Value = e
+			os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), e)
 			continue
 		}
 
 		// Check if we are using defaults
 		if defaults {
-			prompts[idx].Value = prompt.Default
+			val := prompt.Default
+			switch t := prompt.Default.(type) {
+			case []string:
+				for idy, s := range t {
+					t[idy] = os.ExpandEnv(s)
+				}
+				val = t
+			case string:
+				val = os.ExpandEnv(t)
+			}
+			s := fmt.Sprint(val)
+			prompts[idx].Value = s
+			os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), s)
 			continue
 		}
 
 		var p survey.Prompt
 		switch t := prompt.Default.(type) {
 		case []string:
+			for idy, s := range t {
+				t[idy] = os.ExpandEnv(s)
+			}
 			p = &survey.Select{
-				Message: prompt.Message,
+				Message: prompt.Label,
 				Options: t,
 				Help:    prompt.Help,
 			}
 		case bool:
 			p = &survey.Confirm{
-				Message: prompt.Message,
+				Message: prompt.Label,
 				Default: t,
 				Help:    prompt.Help,
 			}
 		case string:
 			p = &survey.Input{
-				Message: prompt.Message,
-				Default: t,
+				Message: prompt.Label,
+				Default: os.ExpandEnv(t),
 				Help:    prompt.Help,
 			}
 		default:
 			p = &survey.Input{
-				Message: prompt.Message,
-				Default: fmt.Sprintf("%v", t),
+				Message: prompt.Label,
+				Default: fmt.Sprint(t),
 				Help:    prompt.Help,
 			}
 		}
@@ -116,45 +101,36 @@ 		if err := survey.AskOne(p, &a); err != nil {
 			return nil, err
 		}
 		prompts[idx].Value = a
+		os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), a)
 	}
 
 	return prompts, nil
 }
 
+type templatePrompt struct {
+	config.Prompt
+	Value string
+}
+
 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{})
+func (t templatePrompts) ToMap() map[string]any {
+	m := make(map[string]any)
 	for _, p := range t {
-		m[p.Key] = p.Value
+		m[p.ID] = p.Value
 	}
 	return m
 }
 
 // ToFuncMap converts a slice of templatePrompt into a suitable template.FuncMap
 func (t templatePrompts) ToFuncMap() template.FuncMap {
-	m := make(map[string]interface{})
+	m := make(map[string]any)
 	for k, v := range t.ToMap() {
 		vv := v // Enclosure
-		m[k] = func() interface{} {
+		m[k] = func() any {
 			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]
-}
M registry/registry.goregistry/registry.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
diff --git a/registry/registry.go b/registry/registry.go
index d2ddf01e4d13496e46b74255217518859c46fdff..a0f40aeea8d764ec9d1dce0ee801cdd5b8d40c21 100644
--- a/registry/registry.go
+++ b/registry/registry.go
@@ -11,14 +11,14 @@
 	"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"
+	"gopkg.in/yaml.v3"
 )
 
 // Registry is a collection of Template
 type Registry struct {
 	dir       string
-	Sources   []*Source   `toml:"sources"`
-	Templates []*Template `toml:"templates"`
+	Sources   []*Source   `yaml:"sources"`
+	Templates []*Template `yaml:"templates"`
 }
 
 func (r *Registry) save() error {
@@ -26,7 +26,7 @@ 	fi, err := os.Create(r.MetaFilePath())
 	if err != nil {
 		return err
 	}
-	if err := toml.NewEncoder(fi).Encode(r); err != nil {
+	if err := yaml.NewEncoder(fi).Encode(r); err != nil {
 		return err
 	}
 	return fi.Close()
@@ -34,7 +34,7 @@ }
 
 // MetaFilePath is the path to the Registry meta-file
 func (r *Registry) MetaFilePath() string {
-	return filepath.Join(r.dir, "registry.toml")
+	return filepath.Join(r.dir, "registry.yaml")
 }
 
 // GetTemplate retrieves a Template from the Registry
@@ -111,7 +111,7 @@
 	return nil
 }
 
-// RemoveTemplate updates the Template on disk and in meta
+// UpdateTemplate updates the Template on disk and in meta
 func (r *Registry) UpdateTemplate(name string) error {
 	_, err := r.GetTemplate(name)
 	if err != nil {
@@ -195,12 +195,12 @@ 			return nil, err
 		}
 	}
 
-	tree, err := toml.LoadFile(reg.MetaFilePath())
+	contents, err := os.ReadFile(reg.MetaFilePath())
 	if err != nil {
 		return nil, err
 	}
 
-	if err := tree.Unmarshal(&reg); err != nil {
+	if err := yaml.Unmarshal(contents, &reg); err != nil {
 		return nil, err
 	}
 
@@ -254,9 +254,8 @@ 	return nil
 }
 
 func save(source, dest string) error {
-
 	// Make sure it's a valid template
-	if _, err := os.Lstat(filepath.Join(source, "template.toml")); err != nil {
+	if _, err := os.Lstat(filepath.Join(source, "tmpl.yaml")); err != nil {
 		return err
 	}
 	fi, err := os.Lstat(filepath.Join(source, "template"))
M registry/registry_test.goregistry/registry_test.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
diff --git a/registry/registry_test.go b/registry/registry_test.go
index 628e29dc09b5359e0650526c7661837a9a477384..eb99ce1b26218e84c6577d9c91d75ef64e7690fd 100644
--- a/registry/registry_test.go
+++ b/registry/registry_test.go
@@ -1,26 +1,24 @@
 package registry
 
 import (
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
+
+	"github.com/matryer/is"
 )
 
 var (
 	tmplDir string
 	regDir  string
-	destDir string
 	reg     *Registry
 )
 
 func TestMain(m *testing.M) {
 	var err error
-	destDir, err = ioutil.TempDir(os.TempDir(), "tmpl-dest")
-	if err != nil {
-		panic(err)
-	}
 
 	// Set up template
 	setupTemplate()
@@ -30,9 +28,6 @@ 	setupRegistry()
 
 	status := m.Run()
 
-	if err = os.RemoveAll(destDir); err != nil {
-		fmt.Printf("could not clean up temp directory %s\n", destDir)
-	}
 	if err = os.RemoveAll(tmplDir); err != nil {
 		fmt.Printf("could not clean up temp directory %s\n", tmplDir)
 	}
@@ -51,25 +46,22 @@ 	t.Run("execute", testExecute)
 }
 
 func testSave(t *testing.T) {
-	if _, err := reg.SaveTemplate("test", tmplDir); err != nil {
-		t.Log("could not save template")
-		t.FailNow()
-	}
+	assert := is.New(t)
+	_, err := reg.SaveTemplate("test", tmplDir)
+	assert.NoErr(err) // Should save template
 }
 
 func testGet(t *testing.T) {
+	assert := is.New(t)
 	_, err := reg.GetTemplate("test")
-	if err != nil {
-		t.Logf("could not get template")
-		t.FailNow()
-	}
+	assert.NoErr(err) // Should get template
 }
 
 func testGetFail(t *testing.T) {
+	assert := is.New(t)
 	_, err := reg.GetTemplate("fail")
-	if !IsErrTemplateNotFound(err) {
-		t.Logf("template should not exist")
-		t.FailNow()
+	if !errors.As(err, &ErrTemplateNotFound{}) {
+		assert.Fail() // Template should not exist
 	}
 }
 
@@ -81,7 +73,7 @@ 		panic(err)
 	}
 
 	// Template config
-	fi, err := os.Create(filepath.Join(tmplDir, "template.toml"))
+	fi, err := os.Create(filepath.Join(tmplDir, "tmpl.yaml"))
 	if err != nil {
 		panic(err)
 	}
M registry/source.goregistry/source.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
diff --git a/registry/source.go b/registry/source.go
index 7770538f8756aacb8cd0da0b6110351fae86318c..7e9029082222a87c205e1a192ab3681d429e973b 100644
--- a/registry/source.go
+++ b/registry/source.go
@@ -5,8 +5,8 @@
 // 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"`
+	Name string `yaml:"name"`
+	URL  string `yaml:"url"`
 }
 
 // CloneURL constructs a URL suitable for cloning a repository
M registry/source_test.goregistry/source_test.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
diff --git a/registry/source_test.go b/registry/source_test.go
index 26134410acc69401a1261858e3fbea271701f95c..950e068251c0700d826399d7b7544e4891883c27 100644
--- a/registry/source_test.go
+++ b/registry/source_test.go
@@ -1,11 +1,14 @@
 package registry
 
 import (
-	"strings"
 	"testing"
+
+	"github.com/matryer/is"
 )
 
 func TestSource(t *testing.T) {
+	assert := is.New(t)
+
 	tt := []struct {
 		Name     string
 		Source   *Source
@@ -38,10 +41,7 @@ 	namespace := "user/repo"
 	for _, tc := range tt {
 		t.Run(tc.Name, func(t *testing.T) {
 			cloneURL := tc.Source.CloneURL(namespace)
-			if !strings.EqualFold(tc.CloneURL, cloneURL) {
-				t.Logf("incorrect clone URL:\n\tExpected: %s\n\tGot: %s\n", tc.CloneURL, cloneURL)
-				t.Fail()
-			}
+			assert.Equal(tc.CloneURL, cloneURL) // Clone URLs should match
 		})
 	}
 }
M registry/template.goregistry/template.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
diff --git a/registry/template.go b/registry/template.go
index 76bf84c425959dc4e15facc9726d9a22ba620ca5..c60e03aebe2f61ccb3f28392d8b5ca555ea56efd 100644
--- a/registry/template.go
+++ b/registry/template.go
@@ -15,12 +15,12 @@ )
 
 // Template is a tmpl project
 type Template struct {
-	reg        *Registry `toml:"-"`
-	Name       string    `toml:"name"`
-	Path       string    `toml:"path"`
-	Repository string    `toml:"repository"`
-	Branch     string    `toml:"branch"`
-	LastUpdate time.Time `toml:"last_update"`
+	reg        *Registry `yaml:"-"`
+	Name       string    `yaml:"name"`
+	Path       string    `yaml:"path"`
+	Repository string    `yaml:"repository"`
+	Branch     string    `yaml:"branch"`
+	LastUpdate time.Time `yaml:"last_update"`
 }
 
 // ArchiveName is the name given to the archive for this Template
@@ -51,7 +51,6 @@ 		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 {
 		if walkErr != nil {
@@ -117,8 +116,8 @@ 		return newFi.Close()
 	})
 }
 
-func mergeMaps(maps ...map[string]interface{}) map[string]interface{} {
-	m := make(map[string]interface{})
+func mergeMaps(maps ...map[string]any) map[string]any {
+	m := make(map[string]any)
 	for _, mm := range maps {
 		for k, v := range mm {
 			m[k] = v
M registry/template_test.goregistry/template_test.go
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
diff --git a/registry/template_test.go b/registry/template_test.go
index a51717be1396de93d93f661ffb6b77aafad1bf12..138d84d12cf94a9e0eaa75cc77e7bdb0463d1b49 100644
--- a/registry/template_test.go
+++ b/registry/template_test.go
@@ -5,99 +5,74 @@ 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
+
+	"github.com/matryer/is"
 )
 
 var (
-	tmplContents = `{{title name}} (@{{username}}) {{if .bool}}{{.year}}{{end}}`
+	tmplContents = `{{title name}} (@{{username}}) {{if .bool}}{{.year}}{{end}} {{org}}`
 	tmplTemplate = `
-name = "john olheiser"
-
-[year]
-default = ${TMPL_TEST} # 2020
-
-[package]
-default = "pkg"
-
-[bool]
-default = true
-
-[username]
-default = "username"
+prompts:
+  - id: name
+    default: john olheiser
+  - id: year
+    default: ${TMPL_TEST} # 2020
+  - id: package
+    default: pkg
+  - id: bool
+    default: true
+  - id: username
+    default: username
+  - id: org
+    default: ${TMPL_PROMPT_USERNAME}/org
 `
-	tmplGold    = "John Olheiser (@jolheiser) 2020"
+	tmplGold    = "John Olheiser (@jolheiser) 2020 jolheiser/org"
 	tmplNewGold = "DO NOT OVERWRITE!"
 )
 
 func testExecute(t *testing.T) {
+	assert := is.New(t)
+	destDir := t.TempDir()
+
 	// Set environment variable
-	if err := os.Setenv("TMPL_TEST", "2020"); err != nil {
-		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()
-	}
+	err := os.Setenv("TMPL_TEST", "2020")
+	assert.NoErr(err) // Should set TMPL_TEST env
+
+	err = os.Setenv("TMPL_VAR_USERNAME", "jolheiser")
+	assert.NoErr(err) // Should set TMPL_VAR_USERNAME env
 
 	// Get template
 	tmpl, err := reg.GetTemplate("test")
-	if err != nil {
-		t.Logf("could not get template")
-		t.FailNow()
-	}
+	assert.NoErr(err) // Should get template
 
 	// Execute template
-	if err := tmpl.Execute(destDir, true, true); err != nil {
-		t.Logf("could not execute template: %v\n", err)
-		t.FailNow()
-	}
+	err = tmpl.Execute(destDir, true, true)
+	assert.NoErr(err) // Should execute template
 
 	// 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()
-	}
+	assert.NoErr(err)                        // Should be able to read TEST file
+	assert.Equal(string(contents), tmplGold) // Template should match golden file
 
 	// 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()
-	}
+	_, err = os.Lstat(pkgPath)
+	assert.NoErr(err) // PKG directory should exist
 
 	// 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()
-	}
+	_, err = os.Lstat(tmplKeep)
+	assert.True(err != nil) // .tmplkeep file should NOT be retained
 
 	// 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()
-	}
+	err = ioutil.WriteFile(testPath, []byte(tmplNewGold), os.ModePerm)
+	assert.NoErr(err) // Writing file should succeed
 
-	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()
-	}
+	err = tmpl.Execute(destDir, true, false)
+	assert.NoErr(err) // Should execute template
 
-	if string(contents) != tmplNewGold {
-		t.Logf("contents did not match:\n\tExpected: %s\n\tGot: %s", tmplNewGold, string(contents))
-		t.FailNow()
-	}
+	contents, err = os.ReadFile(testPath)
+	assert.NoErr(err)                           // Should be able to read file
+	assert.Equal(string(contents), tmplNewGold) // Template should match new golden file
 }
I schema/convert.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
diff --git a/schema/convert.go b/schema/convert.go
new file mode 100644
index 0000000000000000000000000000000000000000..03ff44c69d353c59789d1678cfd290de45b2da04
--- /dev/null
+++ b/schema/convert.go
@@ -0,0 +1,46 @@
+package schema
+
+import (
+	"fmt"
+
+	"gopkg.in/yaml.v3"
+)
+
+// Unmarshal YAML to map[string]any instead of map[any]any.
+func Unmarshal(in []byte, out any) error {
+	var res any
+
+	if err := yaml.Unmarshal(in, &res); err != nil {
+		return err
+	}
+	*out.(*any) = mapValue(res)
+
+	return nil
+}
+
+func mapSlice(in []any) []any {
+	res := make([]any, len(in))
+	for i, v := range in {
+		res[i] = mapValue(v)
+	}
+	return res
+}
+
+func mapMap(in map[any]any) map[string]any {
+	res := make(map[string]any)
+	for k, v := range in {
+		res[fmt.Sprintf("%v", k)] = mapValue(v)
+	}
+	return res
+}
+
+func mapValue(v any) any {
+	switch v := v.(type) {
+	case []any:
+		return mapSlice(v)
+	case map[any]any:
+		return mapMap(v)
+	default:
+		return v
+	}
+}
I schema/schema.go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
diff --git a/schema/schema.go b/schema/schema.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f925d9a56e69d85dc96643821da9a080b3bcd24
--- /dev/null
+++ b/schema/schema.go
@@ -0,0 +1,49 @@
+package schema
+
+import (
+	_ "embed"
+	"fmt"
+	"io"
+	"strings"
+
+	"github.com/xeipuuv/gojsonschema"
+)
+
+var (
+	//go:embed tmpl.json
+	schema       []byte
+	schemaLoader = gojsonschema.NewBytesLoader(schema)
+)
+
+// Lint is for linting a recipe against the schema
+func Lint(r io.Reader) error {
+	data, err := io.ReadAll(r)
+	if err != nil {
+		return err
+	}
+	var m any
+	if err := Unmarshal(data, &m); err != nil {
+		return err
+	}
+	sourceLoader := gojsonschema.NewGoLoader(m)
+	result, err := gojsonschema.Validate(schemaLoader, sourceLoader)
+	if err != nil {
+		return err
+	}
+	if len(result.Errors()) > 0 {
+		return ResultErrors(result.Errors())
+	}
+	return nil
+}
+
+// ResultErrors is a slice of gojsonschema.ResultError that implements error
+type ResultErrors []gojsonschema.ResultError
+
+// Error implements error
+func (r ResultErrors) Error() string {
+	errs := make([]string, 0, len(r))
+	for _, re := range r {
+		errs = append(errs, fmt.Sprintf("%s: %s", re.Field(), re.Description()))
+	}
+	return strings.Join(errs, " | ")
+}
I schema/tmpl.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
diff --git a/schema/tmpl.json b/schema/tmpl.json
new file mode 100644
index 0000000000000000000000000000000000000000..b8acf9826e47b572262112985237ebeeb86d3797
--- /dev/null
+++ b/schema/tmpl.json
@@ -0,0 +1,51 @@
+{
+  "$schema": "https://json-schema.org/draft/2020-12/schema",
+  "$id": "https://git.jojodev.com/jolheiser/tmpl/src/branch/main/schema/tmpl.json",
+  "title": "tmpl template",
+  "description": "A template for tmpl",
+  "type": "object",
+  "required": [
+    "prompts"
+  ],
+  "additionalProperties": false,
+  "properties": {
+    "prompts": {
+      "description": "Template prompts",
+      "type": "array",
+      "minItems": 1,
+      "items": {
+        "type": "object",
+        "required": [
+          "id"
+        ],
+        "additionalProperties": false,
+        "properties": {
+          "id": {
+            "description": "The unique prompt ID",
+            "type": "string"
+          },
+          "label": {
+            "description": "A label to show instead of the ID when prompting",
+            "type": "string"
+          },
+          "default": {
+            "description": "A default value for the prompt",
+            "type": "string"
+          },
+          "help": {
+            "description": "A help message for more information on a prompt",
+            "type": "string"
+          },
+          "depends_on": {
+            "description": "A list of prompt IDs that this prompt depends on",
+            "type": "array",
+            "minItems": 1,
+            "items": {
+              "type": "string"
+            }
+          }
+        }
+      }
+    }
+  }
+}