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: #24pull/26/head v0.3.0
parent
c348255c37
commit
d02502078f
29
FAQ.md
29
FAQ.md
|
@ -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)
|
For a full list, see [helper.go](registry/helper.go)
|
||||||
|
|
||||||
| Helper | Example | Output |
|
| Helper | Example | Output |
|
||||||
|-------------|----------------------------------|-------------------------------------------------------------------------------------------------------|
|
|-------------|------------------------------------|-------------------------------------------------------------------------------------------------------|
|
||||||
| upper | `{{upper project}}` | `MY-PROJECT` |
|
| upper | `{{upper project}}` | `MY-PROJECT` |
|
||||||
| lower | `{{lower project}}` | `my-project` |
|
| lower | `{{lower project}}` | `my-project` |
|
||||||
| title | `{{title project}}` | `My-Project` |
|
| title | `{{title project}}` | `My-Project` |
|
||||||
| snake | `{{snake project}}` | `my_project` |
|
| snake | `{{snake project}}` | `my_project` |
|
||||||
| kebab | `{{kebab project}}` | `my-project` |
|
| kebab | `{{kebab project}}` | `my-project` |
|
||||||
| pascal | `{{pascal project}}` | `MyProject` |
|
| pascal | `{{pascal project}}` | `MyProject` |
|
||||||
| camel | `{{camel project}}` | `myProject` |
|
| camel | `{{camel project}}` | `myProject` |
|
||||||
| env | `{{env "USER"}}` | The current user |
|
| env | `{{env "USER"}}` | The current user |
|
||||||
| sep | `{{sep}}` | Filepath separator for current OS |
|
| 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/) |
|
| 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_prefix | `{{trim_prefix "foobar" "foo"}}` | `bar` |
|
||||||
| trim_suffix | `{{trim_suffix "foobar" "bar"}}` | `foo` |
|
| trim_suffix | `{{trim_suffix "foobar" "bar"}}` | `foo` |
|
||||||
|
| replace | `{{replace "foobar" "bar" "baz"}}` | `foobaz` |
|
||||||
|
|
||||||
## Sources
|
## Sources
|
||||||
|
|
||||||
|
|
13
cmd/init.go
13
cmd/init.go
|
@ -36,7 +36,7 @@ func runInit(_ *cli.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := fi.WriteString(comments); err != nil {
|
if _, err := fi.WriteString(initConfig); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := os.Mkdir("template", os.ModePerm); err != nil {
|
if err := os.Mkdir("template", os.ModePerm); err != nil {
|
||||||
|
@ -46,12 +46,15 @@ func runInit(_ *cli.Context) error {
|
||||||
return fi.Close()
|
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
|
# Write any template args here to prompt the user for, giving any defaults/options as applicable
|
||||||
|
|
||||||
prompts:
|
prompts:
|
||||||
- id: name # The unique ID for the prompt
|
- id: name # The unique ID for the prompt
|
||||||
label: Project Name # The prompt message/label
|
label: Project Name # (Optional) Prompt message/label, defaults to id
|
||||||
help: The name to use in the project # Optional help message for the prompt
|
help: The name to use in the project # (Optional) Help message for the prompt
|
||||||
default: tmpl # Prompt default
|
default: tmpl # (Optional) Prompt default
|
||||||
|
options: # (Optional) Set of options the user can choose from
|
||||||
|
- coolproject123
|
||||||
|
- ${USER}'s cool project
|
||||||
`
|
`
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -16,10 +16,11 @@ type Config struct {
|
||||||
|
|
||||||
// Prompt is a tmpl prompt
|
// Prompt is a tmpl prompt
|
||||||
type Prompt struct {
|
type Prompt struct {
|
||||||
ID string `yaml:"id"`
|
ID string `yaml:"id"`
|
||||||
Label string `yaml:"label"`
|
Label string `yaml:"label"`
|
||||||
Help string `yaml:"help"`
|
Help string `yaml:"help"`
|
||||||
Default any `yaml:"default"`
|
Default string `yaml:"default"`
|
||||||
|
Options []string `yaml:"options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load loads a tmpl config
|
// Load loads a tmpl config
|
||||||
|
|
|
@ -10,11 +10,6 @@ func (e ErrTemplateExists) Error() string {
|
||||||
return fmt.Sprintf("template %s already exists", e.Name)
|
return fmt.Sprintf("template %s already exists", e.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrTemplateExists(err error) bool {
|
|
||||||
_, ok := err.(ErrTemplateExists)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrTemplateNotFound struct {
|
type ErrTemplateNotFound struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
@ -30,8 +25,3 @@ type ErrSourceNotFound struct {
|
||||||
func (e ErrSourceNotFound) Error() string {
|
func (e ErrSourceNotFound) Error() string {
|
||||||
return fmt.Sprintf("Source not found for %s", e.Name)
|
return fmt.Sprintf("Source not found for %s", e.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsErrSourceNotFound(err error) bool {
|
|
||||||
_, ok := err.(ErrSourceNotFound)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var funcMap = map[string]any{
|
var funcMap = map[string]any{
|
||||||
// String conversions
|
|
||||||
"upper": strings.ToUpper,
|
"upper": strings.ToUpper,
|
||||||
"lower": strings.ToLower,
|
"lower": strings.ToLower,
|
||||||
"title": strings.Title,
|
"title": strings.Title,
|
||||||
|
@ -20,15 +19,10 @@ var funcMap = map[string]any{
|
||||||
"camel": func(in string) string {
|
"camel": func(in string) string {
|
||||||
return xstrings.FirstRuneToLower(xstrings.ToCamelCase(in))
|
return xstrings.FirstRuneToLower(xstrings.ToCamelCase(in))
|
||||||
},
|
},
|
||||||
"trim_prefix": func(in, trim string) string {
|
"trim_prefix": strings.TrimPrefix,
|
||||||
return strings.TrimPrefix(in, trim)
|
"trim_suffix": strings.TrimSuffix,
|
||||||
},
|
"replace": strings.ReplaceAll,
|
||||||
"trim_suffix": func(in, trim string) string {
|
"env": os.Getenv,
|
||||||
return strings.TrimSuffix(in, trim)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Other
|
|
||||||
"env": os.Getenv,
|
|
||||||
"sep": func() string {
|
"sep": func() string {
|
||||||
return string(filepath.Separator)
|
return string(filepath.Separator)
|
||||||
},
|
},
|
||||||
|
|
|
@ -33,9 +33,6 @@ func prompt(dir string, defaults bool) (templatePrompts, error) {
|
||||||
if tp.Label == "" {
|
if tp.Label == "" {
|
||||||
tp.Label = tp.ID
|
tp.Label = tp.ID
|
||||||
}
|
}
|
||||||
if tp.Default == nil {
|
|
||||||
tp.Default = ""
|
|
||||||
}
|
|
||||||
prompts = append(prompts, tp)
|
prompts = append(prompts, tp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,52 +47,32 @@ func prompt(dir string, defaults bool) (templatePrompts, error) {
|
||||||
|
|
||||||
// Check if we are using defaults
|
// Check if we are using defaults
|
||||||
if defaults {
|
if defaults {
|
||||||
val := prompt.Default
|
val := os.ExpandEnv(prompt.Default)
|
||||||
switch t := prompt.Default.(type) {
|
prompts[idx].Value = val
|
||||||
case []string:
|
os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), val)
|
||||||
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Otherwise, prompt
|
||||||
var p survey.Prompt
|
var p survey.Prompt
|
||||||
switch t := prompt.Default.(type) {
|
if len(prompt.Options) > 0 {
|
||||||
case []string:
|
opts := make([]string, 0, len(prompt.Options))
|
||||||
for idy, s := range t {
|
for idy, opt := range prompt.Options {
|
||||||
t[idy] = os.ExpandEnv(s)
|
opts[idy] = os.ExpandEnv(opt)
|
||||||
}
|
}
|
||||||
p = &survey.Select{
|
p = &survey.Select{
|
||||||
Message: prompt.Label,
|
Message: prompt.Label,
|
||||||
Options: t,
|
Options: opts,
|
||||||
Help: prompt.Help,
|
Help: prompt.Help,
|
||||||
}
|
}
|
||||||
case bool:
|
} else {
|
||||||
p = &survey.Confirm{
|
|
||||||
Message: prompt.Label,
|
|
||||||
Default: t,
|
|
||||||
Help: prompt.Help,
|
|
||||||
}
|
|
||||||
case string:
|
|
||||||
p = &survey.Input{
|
p = &survey.Input{
|
||||||
Message: prompt.Label,
|
Message: prompt.Label,
|
||||||
Default: os.ExpandEnv(t),
|
Default: os.ExpandEnv(prompt.Default),
|
||||||
Help: prompt.Help,
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
p = &survey.Input{
|
|
||||||
Message: prompt.Label,
|
|
||||||
Default: fmt.Sprint(t),
|
|
||||||
Help: prompt.Help,
|
Help: prompt.Help,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var a string
|
var a string
|
||||||
if err := survey.AskOne(p, &a); err != nil {
|
if err := survey.AskOne(p, &a); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,0 +1,10 @@
|
||||||
|
prompts:
|
||||||
|
- id: foo
|
||||||
|
label: Bar
|
||||||
|
default: baz
|
||||||
|
help: |
|
||||||
|
This is a foobar!
|
||||||
|
options:
|
||||||
|
- "1"
|
||||||
|
- bonk
|
||||||
|
- "false"
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$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",
|
"title": "tmpl template",
|
||||||
"description": "A template for tmpl",
|
"description": "A template for tmpl",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
|
@ -28,16 +28,16 @@
|
||||||
"description": "A label to show instead of the ID when prompting",
|
"description": "A label to show instead of the ID when prompting",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"default": {
|
|
||||||
"description": "A default value for the prompt",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"help": {
|
"help": {
|
||||||
"description": "A help message for more information on a prompt",
|
"description": "A help message for more information on a prompt",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"depends_on": {
|
"default": {
|
||||||
"description": "A list of prompt IDs that this prompt depends on",
|
"description": "A default value for the prompt",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"description": "A set of options for this prompt",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
"minItems": 1,
|
||||||
"items": {
|
"items": {
|
||||||
|
|
Loading…
Reference in New Issue