tmpl @main -
refs -
log -
-
https://git.jolheiser.com/tmpl.git
Template automation
Add options support (#24)
<!--
1. Did you add documentation?
2. Did you add tests?
3. Do you need to re-run formatting?
4. Do you need to re-run docs.go?
-->
Co-authored-by: jolheiser <john.olheiser@gmail.com>
Reviewed-on: https://git.jojodev.com/jolheiser/tmpl/pulls/24
11 changed files, 140 additions(+), 85 deletions(-)
diff --git a/FAQ.md b/FAQ.md
index b6d813afde7e837d7febfbc90438b0470a39282b..6ec6f61f15d0f955c45be861a7c03c08ccda2684 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -48,20 +48,21 @@ ### 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` |
+| 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` |
+| replace | `{{replace "foobar" "bar" "baz"}}` | `foobaz` |
## Sources
diff --git a/cmd/init.go b/cmd/init.go
index 61c916c5ecfed7e0cb3716dae628cff5e8f93a62..371e25f18a371649afe499c76988134569fe5283 100644
--- a/cmd/init.go
+++ b/cmd/init.go
@@ -36,7 +36,7 @@ fi, err := os.Create("tmpl.yaml")
if err != nil {
return err
}
- if _, err := fi.WriteString(comments); err != nil {
+ if _, err := fi.WriteString(initConfig); err != nil {
return err
}
if err := os.Mkdir("template", os.ModePerm); err != nil {
@@ -46,12 +46,15 @@ log.Info().Msg("Template initialized!")
return fi.Close()
}
-var comments = `# tmpl.yaml
+var initConfig = `# tmpl.yaml
# Write any template args here to prompt the user for, giving any defaults/options as applicable
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
+ label: Project Name # (Optional) Prompt message/label, defaults to id
+ help: The name to use in the project # (Optional) Help message for the prompt
+ default: tmpl # (Optional) Prompt default
+ options: # (Optional) Set of options the user can choose from
+ - coolproject123
+ - ${USER}'s cool project
`
diff --git a/cmd/init_test.go b/cmd/init_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..6771ab478293cc73bd26fa295046e63dd09effd2
--- /dev/null
+++ b/cmd/init_test.go
@@ -0,0 +1,17 @@
+package cmd
+
+import (
+ "strings"
+ "testing"
+
+ "go.jolheiser.com/tmpl/schema"
+
+ "github.com/matryer/is"
+)
+
+func TestInitSchema(t *testing.T) {
+ assert := is.New(t)
+
+ err := schema.Lint(strings.NewReader(initConfig))
+ assert.NoErr(err) // Init config should conform to schema
+}
diff --git a/config/config.go b/config/config.go
index c3067433cddb69cb037ade4d4a0ede6116f8d7ea..fa7eb12661ab072b8b761f2e16c6064bdfa9027b 100644
--- a/config/config.go
+++ b/config/config.go
@@ -16,10 +16,11 @@ }
// Prompt is a tmpl prompt
type Prompt struct {
- ID string `yaml:"id"`
- Label string `yaml:"label"`
- Help string `yaml:"help"`
- Default any `yaml:"default"`
+ ID string `yaml:"id"`
+ Label string `yaml:"label"`
+ Help string `yaml:"help"`
+ Default string `yaml:"default"`
+ Options []string `yaml:"options"`
}
// Load loads a tmpl config
diff --git a/registry/error.go b/registry/error.go
index 168ed929ef5f93ace3fb96ec123d759ef18afcf5..35985ef3f11b08162c1f64cb4aa6ed17c675231f 100644
--- a/registry/error.go
+++ b/registry/error.go
@@ -10,11 +10,6 @@ func (e ErrTemplateExists) Error() string {
return fmt.Sprintf("template %s already exists", e.Name)
}
-func IsErrTemplateExists(err error) bool {
- _, ok := err.(ErrTemplateExists)
- return ok
-}
-
type ErrTemplateNotFound struct {
Name string
}
@@ -30,8 +25,3 @@
func (e ErrSourceNotFound) Error() string {
return fmt.Sprintf("Source not found for %s", e.Name)
}
-
-func IsErrSourceNotFound(err error) bool {
- _, ok := err.(ErrSourceNotFound)
- return ok
-}
diff --git a/registry/helper.go b/registry/helper.go
index a9bb49d27217bd38b0e6b206d98e7ded4830bb7a..dc9dfff3e3de77bcbf396e3314646234196852f5 100644
--- a/registry/helper.go
+++ b/registry/helper.go
@@ -10,7 +10,6 @@ "github.com/huandu/xstrings"
)
var funcMap = map[string]any{
- // String conversions
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
@@ -20,15 +19,10 @@ "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
- "env": os.Getenv,
+ "trim_prefix": strings.TrimPrefix,
+ "trim_suffix": strings.TrimSuffix,
+ "replace": strings.ReplaceAll,
+ "env": os.Getenv,
"sep": func() string {
return string(filepath.Separator)
},
diff --git a/registry/prompt.go b/registry/prompt.go
index e12adcecb9baf93c4ce71d614afe9f25f9a1d766..9f731ebed09f3cebf73d1d4ebe192258ed97280c 100644
--- a/registry/prompt.go
+++ b/registry/prompt.go
@@ -33,9 +33,6 @@ }
if tp.Label == "" {
tp.Label = tp.ID
}
- if tp.Default == nil {
- tp.Default = ""
- }
prompts = append(prompts, tp)
}
@@ -50,52 +47,32 @@ }
// Check if we are using defaults
if defaults {
- 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)
+ val := os.ExpandEnv(prompt.Default)
+ prompts[idx].Value = val
+ os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), val)
continue
}
+ // Otherwise, prompt
var p survey.Prompt
- switch t := prompt.Default.(type) {
- case []string:
- for idy, s := range t {
- t[idy] = os.ExpandEnv(s)
+ if len(prompt.Options) > 0 {
+ opts := make([]string, 0, len(prompt.Options))
+ for idy, opt := range prompt.Options {
+ opts[idy] = os.ExpandEnv(opt)
}
p = &survey.Select{
Message: prompt.Label,
- Options: t,
+ Options: opts,
Help: prompt.Help,
}
- case bool:
- p = &survey.Confirm{
- Message: prompt.Label,
- Default: t,
- Help: prompt.Help,
- }
- case string:
+ } else {
p = &survey.Input{
Message: prompt.Label,
- Default: os.ExpandEnv(t),
- Help: prompt.Help,
- }
- default:
- p = &survey.Input{
- Message: prompt.Label,
- Default: fmt.Sprint(t),
+ Default: os.ExpandEnv(prompt.Default),
Help: prompt.Help,
}
}
+
var a string
if err := survey.AskOne(p, &a); err != nil {
return nil, err
diff --git a/schema/schema_test.go b/schema/schema_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5d7daec3df12e01f30c0e02c9bfce746ab53a129
--- /dev/null
+++ b/schema/schema_test.go
@@ -0,0 +1,42 @@
+package schema
+
+import (
+ "embed"
+ "errors"
+ "fmt"
+ "testing"
+
+ "github.com/matryer/is"
+)
+
+//go:embed testdata
+var testdata embed.FS
+
+func TestSchema(t *testing.T) {
+ tt := []struct {
+ Name string
+ NumErr int
+ }{
+ {Name: "good", NumErr: 0},
+ {Name: "bad", NumErr: 10},
+ {Name: "empty", NumErr: 1},
+ }
+
+ for _, tc := range tt {
+ t.Run(tc.Name, func(t *testing.T) {
+ assert := is.New(t)
+
+ fi, err := testdata.Open(fmt.Sprintf("testdata/%s.yaml", tc.Name))
+ assert.NoErr(err) // Should open test file
+
+ err = Lint(fi)
+ if tc.NumErr > 0 {
+ var rerrs ResultErrors
+ assert.True(errors.As(err, &rerrs)) // Error should be ResultErrors
+ assert.True(len(rerrs) == tc.NumErr) // Number of errors should match test case
+ } else {
+ assert.NoErr(err) // Good schemas shouldn't return errors
+ }
+ })
+ }
+}
diff --git a/schema/testdata/bad.yaml b/schema/testdata/bad.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..7a4055dacca29076b2cb2c8652ba4d23f2920cea
--- /dev/null
+++ b/schema/testdata/bad.yaml
@@ -0,0 +1,20 @@
+prompts:
+ - label: Bar
+ default: baz
+ help: |
+ This is a foobar!
+ options:
+ - "1"
+ - bonk
+ - "false"
+ - id: test
+ label: 1234
+ - id: test123
+ options: []
+ - label: 1234
+ default: false
+ help: # nil
+ options:
+ - 1
+ - 2
+ - true
\ No newline at end of file
diff --git a/schema/testdata/empty.yaml b/schema/testdata/empty.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
Binary files /dev/null and b/schema/testdata/empty.yaml differ
diff --git a/schema/testdata/good.yaml b/schema/testdata/good.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..e5bb858c5b4e684fff9da52c48b4ee39dfd1ca0f
--- /dev/null
+++ b/schema/testdata/good.yaml
@@ -0,0 +1,10 @@
+prompts:
+ - id: foo
+ label: Bar
+ default: baz
+ help: |
+ This is a foobar!
+ options:
+ - "1"
+ - bonk
+ - "false"
\ No newline at end of file
diff --git a/schema/tmpl.json b/schema/tmpl.json
index b8acf9826e47b572262112985237ebeeb86d3797..a8fdb7f715ab668648744e180ce94485ebb29d00 100644
--- a/schema/tmpl.json
+++ b/schema/tmpl.json
@@ -1,6 +1,6 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
- "$id": "https://git.jojodev.com/jolheiser/tmpl/src/branch/main/schema/tmpl.json",
+ "$id": "https://git.jojodev.com/jolheiser/tmpl/raw/branch/main/schema/tmpl.json",
"title": "tmpl template",
"description": "A template for tmpl",
"type": "object",
@@ -28,16 +28,16 @@ "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",
+ "default": {
+ "description": "A default value for the prompt",
+ "type": "string"
+ },
+ "options": {
+ "description": "A set of options for this prompt",
"type": "array",
"minItems": 1,
"items": {