diff --git a/DOCS.md b/DOCS.md index 005df0b..c27e5ae 100644 --- a/DOCS.md +++ b/DOCS.md @@ -22,18 +22,22 @@ For example, `author` would have the corresponding environment variable `TMPL_VA ```toml # Key-value pairs can be simple # The user will receive a basic prompt asking them to fill out the variable -project = "my-project" +[project] +default = "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 +# This could be a suggested value, and will get expanded by the environment variables set default = "$USER" + +[app] +# The default can also refer to other prompts +default = "${TMPL_PROMPT_AUTHOR}'s app" +# However, this means the other prompt must be ensured to be prompted first +depends_on = ["author"] ``` ## template directory @@ -54,18 +58,20 @@ 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/)| +| 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 diff --git a/docs.go b/cli.go similarity index 95% rename from docs.go rename to cli.go index 00deb1f..d7d5988 100644 --- a/docs.go +++ b/cli.go @@ -11,7 +11,7 @@ import ( "go.jolheiser.com/tmpl/cmd" ) -//go:generate go run docs.go +//go:generate go run cli.go func main() { app := cmd.NewApp() diff --git a/cmd/env.go b/cmd/env.go index 703efbf..4c7b402 100644 --- a/cmd/env.go +++ b/cmd/env.go @@ -15,7 +15,6 @@ var Env = &cli.Command{ } func runEnv(_ *cli.Context) error { - // Source log.Info().Str("TMPL_SOURCE", os.Getenv("TMPL_SOURCE")).Msg("") @@ -27,5 +26,3 @@ func runEnv(_ *cli.Context) error { return nil } - - diff --git a/go.mod b/go.mod index 63613e5..cea0032 100644 --- a/go.mod +++ b/go.mod @@ -1,15 +1,47 @@ 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/huandu/xstrings v1.3.2 - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/matryer/is v1.4.0 github.com/mholt/archiver/v3 v3.5.0 - github.com/pelletier/go-toml v1.8.1 + github.com/pelletier/go-toml/v2 v2.0.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 +) + +require ( + github.com/andybalholm/brotli v1.0.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect + github.com/dsnet/compress v0.0.1 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.0.0 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/imdario/mergo v0.3.9 // 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 v0.0.0-20190725054713-01f96b0aa0cd // indirect + github.com/klauspost/compress v1.10.10 // indirect + github.com/klauspost/pgzip v1.2.4 // indirect + github.com/mattn/go-colorable v0.1.2 // indirect + github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/nwaples/rardecode v1.1.0 // indirect + github.com/pierrec/lz4/v4 v4.0.3 // indirect + github.com/russross/blackfriday/v2 v2.0.1 // indirect + github.com/sergi/go-diff v1.1.0 // indirect + github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/ulikunitz/xz v0.5.7 // indirect + github.com/xanzy/ssh-agent v0.2.1 // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect + golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect + golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect + golang.org/x/text v0.3.6 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/go.sum b/go.sum index 2901739..e150907 100644 --- a/go.sum +++ b/go.sum @@ -23,7 +23,6 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh 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/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= @@ -66,6 +65,8 @@ 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/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 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -81,8 +82,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb 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/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= 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/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -101,8 +102,9 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 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/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +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.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= @@ -160,3 +162,5 @@ 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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/registry/error.go b/registry/error.go index f7950f4..168ed92 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 } diff --git a/registry/helper.go b/registry/helper.go index 2ce93cb..a9bb49d 100644 --- a/registry/helper.go +++ b/registry/helper.go @@ -9,8 +9,7 @@ import ( "github.com/huandu/xstrings" ) -var funcMap = map[string]interface{}{ - +var funcMap = map[string]any{ // String conversions "upper": strings.ToUpper, "lower": strings.ToLower, @@ -21,6 +20,12 @@ var funcMap = map[string]interface{}{ "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, diff --git a/registry/prompt.go b/registry/prompt.go index d0d34cf..196b93b 100644 --- a/registry/prompt.go +++ b/registry/prompt.go @@ -10,15 +10,16 @@ import ( "text/template" "github.com/AlecAivazis/survey/v2" - "github.com/pelletier/go-toml" + "github.com/pelletier/go-toml/v2" ) type templatePrompt struct { - Key string `toml:"-"` - Value interface{} `toml:"-"` - Message string `toml:"prompt"` - Help string `toml:"help"` - Default interface{} `toml:"default"` + Name string `toml:"-"` + Value any `toml:"-"` + Message string `toml:"prompt"` + Help string `toml:"help"` + Default any `toml:"default"` + DependsOn []string `toml:"depends_on"` } func prompt(dir string, defaults bool) (templatePrompts, error) { @@ -33,60 +34,62 @@ func prompt(dir string, defaults bool) (templatePrompts, error) { } // Expand the template with environment variables - templateContents := os.ExpandEnv(string(templateBytes)) + templateContents := os.Expand(string(templateBytes), func(s string) string { + if strings.HasPrefix(s, "TMPL_PROMPT") { + return fmt.Sprintf("${%s}", s) + } + return os.Getenv(s) + }) - tree, err := toml.Load(templateContents) - if err != nil { + var templateMap map[string]templatePrompt + if err := toml.Unmarshal([]byte(templateContents), &templateMap); 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 := sortMap(templateMap) + for idx, prompt := range prompts { + if prompt.Message == "" { + prompt.Message = prompt.Name } - - var p templatePrompt - if err := obj.Unmarshal(&p); err != nil { - return nil, err + if prompt.Default == nil { + prompt.Default = "" } - p.Key = k - if p.Message == "" { - p.Message = p.Key - } - if p.Default == nil { - p.Default = "" - } - prompts[idx] = p + prompts[idx] = prompt } - // 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.Name) + 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, Options: t, @@ -101,13 +104,13 @@ func prompt(dir string, defaults bool) (templatePrompts, error) { case string: p = &survey.Input{ Message: prompt.Message, - Default: t, + Default: os.ExpandEnv(t), Help: prompt.Help, } default: p = &survey.Input{ Message: prompt.Message, - Default: fmt.Sprintf("%v", t), + Default: fmt.Sprint(t), Help: prompt.Help, } } @@ -116,45 +119,52 @@ func prompt(dir string, defaults bool) (templatePrompts, error) { return nil, err } prompts[idx].Value = a + os.Setenv(fmt.Sprintf("TMPL_PROMPT_%s", envKey), a) } return prompts, nil } +func sortMap(m map[string]templatePrompt) templatePrompts { + prompts := make(templatePrompts, 0, len(m)) + for name, prompt := range m { + prompt.Name = name + prompts = append(prompts, prompt) + } + sort.Slice(prompts, func(i, j int) bool { + return prompts[i].Name < prompts[j].Name + }) + sort.Slice(prompts, func(i, j int) bool { + for _, dep := range prompts[i].DependsOn { + if strings.EqualFold(dep, prompts[j].Name) { + return false + } + } + return true + }) + fmt.Println(prompts) + return prompts +} + 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.Name] = 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] -} diff --git a/registry/registry.go b/registry/registry.go index d2ddf01..329a52d 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -11,7 +11,7 @@ import ( "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" + "github.com/pelletier/go-toml/v2" ) // Registry is a collection of Template @@ -195,12 +195,12 @@ func Open(dir string) (*Registry, error) { } } - tree, err := toml.LoadFile(reg.MetaFilePath()) + contents, err := os.ReadFile(reg.MetaFilePath()) if err != nil { return nil, err } - if err := tree.Unmarshal(®); err != nil { + if err := toml.Unmarshal(contents, ®); err != nil { return nil, err } @@ -254,7 +254,6 @@ func download(cloneURL, branch, dest string) error { } func save(source, dest string) error { - // Make sure it's a valid template if _, err := os.Lstat(filepath.Join(source, "template.toml")); err != nil { return err diff --git a/registry/registry_test.go b/registry/registry_test.go index 628e29d..722d678 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 @@ func TestMain(m *testing.M) { 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 @@ func TestTemplate(t *testing.T) { } 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 } } diff --git a/registry/source_test.go b/registry/source_test.go index 2613441..950e068 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 @@ func TestSource(t *testing.T) { 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 }) } } diff --git a/registry/template.go b/registry/template.go index 76bf84c..75438ed 100644 --- a/registry/template.go +++ b/registry/template.go @@ -51,7 +51,6 @@ func (t *Template) Execute(dest string, defaults, overwrite bool) error { } 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 @@ func (t *Template) Execute(dest string, defaults, overwrite bool) error { }) } -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 diff --git a/registry/template_test.go b/registry/template_test.go index a51717b..86197b3 100644 --- a/registry/template_test.go +++ b/registry/template_test.go @@ -5,12 +5,15 @@ import ( "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" +[name] +default = "john olheiser" [year] default = ${TMPL_TEST} # 2020 @@ -18,86 +21,63 @@ default = ${TMPL_TEST} # 2020 [package] default = "pkg" +[org] +default = "${TMPL_PROMPT_USERNAME}/org" +depends_on = ["username"] + [bool] default = true [username] default = "username" ` - 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() - } + err = tmpl.Execute(destDir, true, false) + assert.NoErr(err) // Should execute template - contents, err = ioutil.ReadFile(testPath) - if err != nil { - t.Logf("could not read file: %v\n", err) - t.FailNow() - } - - 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 }