Add options support (#24)
ci/woodpecker/push/woodpecker Pipeline was successful Details
ci/woodpecker/tag/woodpecker Pipeline was successful Details

<!--
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: #24
pull/26/head v0.3.0
jolheiser 2022-06-15 14:59:21 +00:00
parent c348255c37
commit d02502078f
12 changed files with 140 additions and 85 deletions

29
FAQ.md
View File

@ -48,20 +48,21 @@ See the [documentation](https://golang.org/pkg/text/template/) for every availab
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

View File

@ -36,7 +36,7 @@ func runInit(_ *cli.Context) error {
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 @@ func runInit(_ *cli.Context) error {
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
`

17
cmd/init_test.go 100644
View File

@ -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
}

View File

@ -16,10 +16,11 @@ type Config struct {
// 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

View File

@ -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 @@ type ErrSourceNotFound struct {
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
}

View File

@ -10,7 +10,6 @@ import (
)
var funcMap = map[string]any{
// String conversions
"upper": strings.ToUpper,
"lower": strings.ToLower,
"title": strings.Title,
@ -20,15 +19,10 @@ var funcMap = map[string]any{
"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)
},

View File

@ -33,9 +33,6 @@ func prompt(dir string, defaults bool) (templatePrompts, error) {
if tp.Label == "" {
tp.Label = tp.ID
}
if tp.Default == nil {
tp.Default = ""
}
prompts = append(prompts, tp)
}
@ -50,52 +47,32 @@ func prompt(dir string, defaults bool) (templatePrompts, error) {
// 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

View File

@ -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
}
})
}
}

20
schema/testdata/bad.yaml vendored 100644
View File

@ -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

0
schema/testdata/empty.yaml vendored 100644
View File

10
schema/testdata/good.yaml vendored 100644
View File

@ -0,0 +1,10 @@
prompts:
- id: foo
label: Bar
default: baz
help: |
This is a foobar!
options:
- "1"
- bonk
- "false"

View File

@ -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 @@
"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": {