Home

tmpl @main - refs - log -
-
https://git.jolheiser.com/tmpl.git
Template automation
tree log patch
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
jolheiser <john+jojodev@jolheiser.com>
2 years ago
11 changed files, 140 additions(+), 85 deletions(-)
M FAQ.md -> FAQ.md
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
 
M cmd/init.go -> cmd/init.go
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
 `
I cmd/init_test.go
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
+}
M config/config.go -> config/config.go
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
M registry/error.go -> registry/error.go
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
-}
M registry/helper.go -> registry/helper.go
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)
 	},
M registry/prompt.go -> registry/prompt.go
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
I schema/schema_test.go
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
+			}
+		})
+	}
+}
I schema/testdata/bad.yaml
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
I schema/testdata/empty.yaml
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
I schema/testdata/good.yaml
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
M schema/tmpl.json -> schema/tmpl.json
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": {