diff --git a/CLI.md b/CLI.md new file mode 100644 index 0000000..0850bb6 --- /dev/null +++ b/CLI.md @@ -0,0 +1,81 @@ +# NAME + +tmpl - Template automation + +# SYNOPSIS + +tmpl + +``` +[--registry|-r]=[value] +[--source|-s]=[value] +``` + +**Usage**: + +``` +tmpl [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +``` + +# GLOBAL OPTIONS + +**--registry, -r**="": Registry directory of tmpl (default: ~/.tmpl) + +**--source, -s**="": Short-name source to use + + +# COMMANDS + +## download + +Download a template + +**--branch, -b**="": Branch to clone (default: main) + +## init + +Initialize a template + +## list + +List templates in the registry + +## remove + +Remove a template + +## save + +Save a local template + +## source + +Commands for working with sources + +### list + +List available sources + +### add + +Add a source + +### remove + +Remove a source + +## test + +Test if a directory is a valid template + +## update + +Update a template + +## use + +Use a template + +**--defaults**: Use template defaults + +**--force**: Overwrite existing files diff --git a/DOCS.md b/DOCS.md index 189a2c4..889b7f0 100644 --- a/DOCS.md +++ b/DOCS.md @@ -1,79 +1,107 @@ -# NAME +# tmpl templates -tmpl - Template automation +This documentation aims to cover FAQs and setup. -# SYNOPSIS +## Setting up a template -tmpl +A "valid" tmpl template only requires two things -``` -[--registry|-r]=[value] -[--source|-s]=[value] +1. A `template.toml` file in the root directory. +2. A `template` directory that serves as the "root" of the template. + +## template.toml + +```toml +# Key-value pairs can be simple +# The user will receive a basic prompt asking them to fill out the variable +project = "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 +default = "me" ``` -**Usage**: +## template directory + +This directory contains any and all files that are part of the template. + +Everything in this directory (including paths and file names!) will be executed as a [Go template](https://golang.org/pkg/text/template/). + +See the [documentation](https://golang.org/pkg/text/template/) for every available possibility, but some basic examples are... + +* A variable defined in template.toml (tmpl allows for keys to be called as a func or variable, whichever you prefer!) + * `{{project}}` or `{{.project}}` + * `{{author}}` or `{{.author}}` +* Conditionally including something + * `{{if eq project ""}} something... {{end}}` + +### 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/)| + +## Sources + +tmpl was designed to work with any local or git-based template. Unfortunately, in contrast to boilr, this means +it cannot be used with `user/repo` notation out of the box. + +However, you _can_ set up a source (and subsequent env variable) to make it easier to use your preferred source while +still allowing for others. + +### Setting up a source + +Let's set up a source for [Gitea](https://gitea.com) ``` -tmpl [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +tmpl source add https://gitea.com gitea ``` -# GLOBAL OPTIONS +To use it, either pass it in with the `--source` flag -**--registry, -r**="": Registry directory of tmpl (default: ~/.tmpl) +``` +tmpl --source gitea download jolheiser/tmpls tmpls +``` -**--source, -s**="": Short-name source to use +Or set it as the env variable `TMPL_SOURCE` +## Using a different branch -# COMMANDS +By default, tmpl will want to use a branch called `main` in your repository. -## download +If you are using another branch as your default, you can set it as the env variable `TMPL_BRANCH` -Download a template +Alternatively, you can specify on the command-line with the `--branch` flag of the `download` command -**--branch, -b**="": Branch to clone (default: main) +``` +tmpl --source gitea download --branch license jolheiser/tmpls license +``` +The above command would download the [license](https://gitea.com/jolheiser/tmpls/src/branch/license) template from `jolheiser/tmpls` -## init +## Putting it all together -Initialize a template +I realize that many users will be using GitHub, and most will likely still be using the `master` branch. -## list - -List templates in the registry - -## remove - -Remove a template - -## save - -Save a local template - -## source - -Commands for working with sources - -### list - -List available sources - -### add - -Add a source - -### remove - -Remove a source - -## test - -Test if a directory is a valid template - -## update - -Update a template - -## use - -Use a template - -**--defaults**: Use template defaults +1. Set up a source for GitHub + 1. `tmpl source add https://github.com github` + 2. Set the env variable `TMPL_SOURCE` to `github` +2. Set the env variable `TMPL_BRANCH` to `master` +3. Happy templating! `tmpl download user/repo repo` \ No newline at end of file diff --git a/README.md b/README.md index 3853e82..ea9fca4 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,13 @@ Heavily inspired by [boilr](https://github.com/tmrts/boilr). The two projects share many similarities, however other than general layout/structure the implementation is entirely my own. -[CLI Docs](DOCS.md) +[CLI Docs](CLI.md) + +[Project Docs/FAQs](DOCS.md) ## Examples -Checkout the [license](https://gitea.com/jolheiser/tmpls/src/branch/license) and [makefile](https://gitea.com/jolheiser/tmpls/src/branch/makefile) branch of my [template repository](https://gitea.com/jolheiser/tmpls). +Check out the [license](https://gitea.com/jolheiser/tmpls/src/branch/license) and [makefile](https://gitea.com/jolheiser/tmpls/src/branch/makefile) branch of my [template repository](https://gitea.com/jolheiser/tmpls). ## License diff --git a/cmd/download.go b/cmd/download.go index f88fd7c..76983a5 100644 --- a/cmd/download.go +++ b/cmd/download.go @@ -1,7 +1,6 @@ package cmd import ( - "errors" "fmt" "strings" @@ -16,6 +15,7 @@ var Download = &cli.Command{ Name: "download", Usage: "Download a template", Description: "Download a template and save it to the local registry", + ArgsUsage: "[repository URL] [name]", Flags: []cli.Flag{ &cli.StringFlag{ Name: "branch", @@ -30,7 +30,7 @@ var Download = &cli.Command{ func runDownload(ctx *cli.Context) error { if ctx.NArg() < 2 { - return errors.New(" ") + return cli.ShowCommandHelp(ctx, ctx.Command.Name) } reg, err := registry.Open(flags.Registry) diff --git a/cmd/init.go b/cmd/init.go index b5e4e0b..9f2f5cc 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -49,7 +49,8 @@ func runInit(_ *cli.Context) error { var comments = `# template.toml # Write any template args here to prompt the user for, giving any defaults/options as applicable -name = "MyProject" - -lang = ["Go", "Rust", "Python"] +[name] +prompt = "Project Name" +help = "The name to use in the project" +default = "tmpl" ` diff --git a/cmd/remove.go b/cmd/remove.go index e753fb4..3521a29 100644 --- a/cmd/remove.go +++ b/cmd/remove.go @@ -1,8 +1,6 @@ package cmd import ( - "errors" - "go.jolheiser.com/tmpl/cmd/flags" "go.jolheiser.com/tmpl/registry" @@ -14,12 +12,13 @@ var Remove = &cli.Command{ Name: "remove", Usage: "Remove a template", Description: "Remove a template from the registry", + ArgsUsage: "[name]", Action: runRemove, } func runRemove(ctx *cli.Context) error { if ctx.NArg() < 1 { - return errors.New("") + return cli.ShowCommandHelp(ctx, ctx.Command.Name) } reg, err := registry.Open(flags.Registry) diff --git a/cmd/save.go b/cmd/save.go index c7e50db..cd647cc 100644 --- a/cmd/save.go +++ b/cmd/save.go @@ -1,7 +1,6 @@ package cmd import ( - "errors" "path/filepath" "go.jolheiser.com/tmpl/cmd/flags" @@ -15,12 +14,13 @@ var Save = &cli.Command{ Name: "save", Usage: "Save a local template", Description: "Save a local template to the registry", + ArgsUsage: "[path] [name]", Action: runSave, } func runSave(ctx *cli.Context) error { if ctx.NArg() < 2 { - return errors.New(" ") + return cli.ShowCommandHelp(ctx, ctx.Command.Name) } reg, err := registry.Open(flags.Registry) diff --git a/cmd/source.go b/cmd/source.go index 9514091..e14530e 100644 --- a/cmd/source.go +++ b/cmd/source.go @@ -1,7 +1,6 @@ package cmd import ( - "errors" "fmt" "os" "text/tabwriter" @@ -37,6 +36,7 @@ var ( Name: "add", Usage: "Add a source", Description: "Add a new source to the registry", + ArgsUsage: "[base URL] [name]", Action: runSourceAdd, } @@ -44,6 +44,7 @@ var ( Name: "remove", Usage: "Remove a source", Description: "Remove a source from the registry", + ArgsUsage: "[name]", Action: runSourceRemove, } ) @@ -68,7 +69,7 @@ func runSourceList(_ *cli.Context) error { func runSourceAdd(ctx *cli.Context) error { if ctx.NArg() < 2 { - return errors.New(" ") + return cli.ShowCommandHelp(ctx, ctx.Command.Name) } reg, err := registry.Open(flags.Registry) @@ -87,7 +88,7 @@ func runSourceAdd(ctx *cli.Context) error { func runSourceRemove(ctx *cli.Context) error { if ctx.NArg() < 1 { - return errors.New("") + return cli.ShowCommandHelp(ctx, ctx.Command.Name) } reg, err := registry.Open(flags.Registry) diff --git a/cmd/test.go b/cmd/test.go index be6b258..cecaf11 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -2,6 +2,7 @@ package cmd import ( "os" + "path/filepath" "github.com/urfave/cli/v2" "go.jolheiser.com/beaver" @@ -10,17 +11,23 @@ import ( var Test = &cli.Command{ Name: "test", Usage: "Test if a directory is a valid template", - Description: "Test whether the current directory is valid for use with tmpl", + Description: "Test whether a directory is valid for use with tmpl", + ArgsUsage: "[path (default: \".\")]", Action: runTest, } -func runTest(_ *cli.Context) error { +func runTest(ctx *cli.Context) error { + testPath := "." + if ctx.NArg() > 0 { + testPath = ctx.Args().First() + } + var errs []string - if _, err := os.Lstat("template.toml"); err != nil { + if _, err := os.Lstat(filepath.Join(testPath, "template.toml")); err != nil { errs = append(errs, "could not find template.toml") } - fi, err := os.Lstat("template") + fi, err := os.Lstat(filepath.Join(testPath, "template")) if err != nil { errs = append(errs, "no template directory found") } diff --git a/cmd/update.go b/cmd/update.go index 0053646..38cff98 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -1,8 +1,6 @@ package cmd import ( - "errors" - "go.jolheiser.com/tmpl/cmd/flags" "go.jolheiser.com/tmpl/registry" @@ -14,12 +12,13 @@ var Update = &cli.Command{ Name: "update", Usage: "Update a template", Description: "Update a template in the registry from the original source", + ArgsUsage: "[name]", Action: runUpdate, } func runUpdate(ctx *cli.Context) error { if ctx.NArg() < 1 { - return errors.New("") + return cli.ShowCommandHelp(ctx, ctx.Command.Name) } reg, err := registry.Open(flags.Registry) diff --git a/cmd/use.go b/cmd/use.go index 6654f4d..351b22f 100644 --- a/cmd/use.go +++ b/cmd/use.go @@ -1,8 +1,6 @@ package cmd import ( - "errors" - "go.jolheiser.com/tmpl/cmd/flags" "go.jolheiser.com/tmpl/registry" @@ -19,13 +17,23 @@ var Use = &cli.Command{ Name: "defaults", Usage: "Use template defaults", }, + &cli.BoolFlag{ + Name: "force", + Usage: "Overwrite existing files", + }, }, - Action: runUse, + ArgsUsage: "[name] [destination (default: \".\")]", + Action: runUse, } func runUse(ctx *cli.Context) error { - if ctx.NArg() < 2 { - return errors.New(" ") + if ctx.NArg() < 1 { + return cli.ShowCommandHelp(ctx, ctx.Command.Name) + } + + dest := "." + if ctx.NArg() >= 2 { + dest = ctx.Args().Get(1) } reg, err := registry.Open(flags.Registry) @@ -38,7 +46,7 @@ func runUse(ctx *cli.Context) error { return err } - if err := tmpl.Execute(ctx.Args().Get(1), ctx.Bool("defaults")); err != nil { + if err := tmpl.Execute(dest, ctx.Bool("defaults"), ctx.Bool("force")); err != nil { return err } diff --git a/docs.go b/docs.go index 5f18aae..3ca29f5 100644 --- a/docs.go +++ b/docs.go @@ -13,7 +13,7 @@ import ( func main() { app := cmd.NewApp() - fi, err := os.Create("DOCS.md") + fi, err := os.Create("CLI.md") if err != nil { panic(err) } diff --git a/registry/prompt.go b/registry/prompt.go new file mode 100644 index 0000000..b683205 --- /dev/null +++ b/registry/prompt.go @@ -0,0 +1,130 @@ +package registry + +import ( + "fmt" + "os" + "path/filepath" + "sort" + "text/template" + + "github.com/AlecAivazis/survey/v2" + "github.com/pelletier/go-toml" +) + +type templatePrompt struct { + Key string `toml:"-"` + Value interface{} `toml:"-"` + Message string `toml:"prompt"` + Help string `toml:"help"` + Default interface{} `toml:"default"` +} + +func prompt(dir string, defaults bool) (templatePrompts, error) { + templatePath := filepath.Join(dir, "template.toml") + if _, err := os.Lstat(templatePath); err != nil { + return nil, err + } + + tree, err := toml.LoadFile(templatePath) + if 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 + } + + var p templatePrompt + if err := obj.Unmarshal(&p); err != nil { + return nil, err + } + p.Key = k + if p.Message == "" { + p.Message = p.Key + } + if p.Default == nil { + p.Default = "" + } + prompts[idx] = p + } + + // Return early if we only want defaults + if defaults { + return prompts, nil + } + + // Sort the prompts so they are consistent + sort.Sort(prompts) + + for idx, prompt := range prompts { + var p survey.Prompt + switch t := prompt.Default.(type) { + case []string: + p = &survey.Select{ + Message: prompt.Message, + Options: t, + Help: prompt.Help, + } + default: + p = &survey.Input{ + Message: prompt.Message, + Default: fmt.Sprintf("%v", t), + Help: prompt.Help, + } + } + var a string + if err := survey.AskOne(p, &a); err != nil { + return nil, err + } + prompts[idx].Value = a + } + + return prompts, nil +} + +type templatePrompts []templatePrompt + +func (t templatePrompts) ToMap() map[string]interface{} { + m := make(map[string]interface{}) + for _, p := range t { + if p.Value != nil { + m[p.Key] = p.Value + continue + } + m[p.Key] = p.Default + } + return m +} + +func (t templatePrompts) ToFuncMap() template.FuncMap { + m := make(map[string]interface{}) + for k, v := range t.ToMap() { + vv := v // Enclosure + m[k] = func() string { + return fmt.Sprintf("%v", vv) + } + } + return m +} + +func (t templatePrompts) Len() int { + return len(t) +} + +func (t templatePrompts) Less(i, j int) bool { + return t[i].Key > t[j].Key +} + +func (t templatePrompts) Swap(i, j int) { + t[i], t[j] = t[j], t[i] +} diff --git a/registry/registry_test.go b/registry/registry_test.go index 7c5caac..5b3e217 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -13,11 +13,6 @@ var ( regDir string destDir string reg *Registry - - tmplContents = `{{title name}} {{.year}}` - tmplTemplate = `name = "john olheiser" -year = 2020` - tmplGold = "John Olheiser 2020" ) func TestMain(m *testing.M) { @@ -78,30 +73,6 @@ func testGetFail(t *testing.T) { } } -func testExecute(t *testing.T) { - tmpl, err := reg.GetTemplate("test") - if err != nil { - t.Logf("could not get template") - t.FailNow() - } - - if err := tmpl.Execute(destDir, true); err != nil { - t.Logf("could not execute template: %v\n", err) - t.FailNow() - } - - contents, err := ioutil.ReadFile(filepath.Join(destDir, "TEST")) - 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() - } -} - func setupTemplate() { var err error tmplDir, err = ioutil.TempDir(os.TempDir(), "tmpl") @@ -122,10 +93,20 @@ func setupTemplate() { panic(err) } - // Template file - if err := os.Mkdir(filepath.Join(tmplDir, "template"), os.ModePerm); err != nil { + // Template directories + pkgPath := filepath.Join(tmplDir, "template", "{{upper package}}") + if err := os.MkdirAll(pkgPath, os.ModePerm); err != nil { panic(err) } + fi, err = os.Create(filepath.Join(pkgPath, ".keep")) + if err != nil { + panic(err) + } + if err := fi.Close(); err != nil { + panic(err) + } + + // Template file fi, err = os.Create(filepath.Join(tmplDir, "template", "TEST")) if err != nil { panic(err) diff --git a/registry/template.go b/registry/template.go index e1651dd..6703f14 100644 --- a/registry/template.go +++ b/registry/template.go @@ -1,18 +1,16 @@ package registry import ( + "bytes" "fmt" "io/ioutil" "os" "path/filepath" - "sort" "strings" "text/template" "time" - "github.com/AlecAivazis/survey/v2" "github.com/mholt/archiver/v3" - "github.com/pelletier/go-toml" ) // Template is a tmpl project @@ -36,7 +34,7 @@ func (t *Template) ArchivePath() string { } // Execute runs the Template and copies to dest -func (t *Template) Execute(dest string, defaults bool) error { +func (t *Template) Execute(dest string, defaults, overwrite bool) error { tmp, err := ioutil.TempDir(os.TempDir(), "tmpl") if err != nil { return err @@ -47,11 +45,13 @@ func (t *Template) Execute(dest string, defaults bool) error { return err } - vars, err := prompt(tmp, defaults) + prompts, err := prompt(tmp, defaults) if err != nil { return err } + 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 { @@ -67,13 +67,19 @@ func (t *Template) Execute(dest string, defaults bool) error { return err } - tmpl, err := template.New("tmpl").Funcs(mergeMaps(funcMap, convertMap(vars))).Parse(string(contents)) + newDest := strings.TrimPrefix(walkPath, base+"/") + newDest = filepath.Join(dest, newDest) + + tmplDest, err := template.New("dest").Funcs(funcs).Parse(newDest) if err != nil { return err } - newDest := strings.TrimPrefix(walkPath, base+"/") - newDest = filepath.Join(dest, newDest) + var buf bytes.Buffer + if err := tmplDest.Execute(&buf, prompts.ToMap()); err != nil { + return err + } + newDest = buf.String() if err := os.MkdirAll(filepath.Dir(newDest), os.ModePerm); err != nil { return err @@ -83,12 +89,22 @@ func (t *Template) Execute(dest string, defaults bool) error { if err != nil { return err } + + // Check if new file exists. If it does, only skip if not overwriting + if _, err := os.Lstat(newDest); err == nil && !overwrite { + return nil + } + newFi, err := os.OpenFile(newDest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, oldFi.Mode()) if err != nil { return err } - if err := tmpl.Execute(newFi, vars); err != nil { + tmplContents, err := template.New("tmpl").Funcs(funcs).Parse(string(contents)) + if err != nil { + return err + } + if err := tmplContents.Execute(newFi, prompts.ToMap()); err != nil { return err } @@ -96,66 +112,6 @@ func (t *Template) Execute(dest string, defaults bool) error { }) } -func prompt(dir string, defaults bool) (map[string]interface{}, error) { - templatePath := filepath.Join(dir, "template.toml") - if _, err := os.Lstat(templatePath); err != nil { - return nil, err - } - - tree, err := toml.LoadFile(templatePath) - if err != nil { - return nil, err - } - vars := tree.ToMap() - - // Return early if we only want defaults - if defaults { - return vars, nil - } - - // Sort the map keys so they are consistent - sorted := make([]string, 0, len(vars)) - for k := range vars { - sorted = append(sorted, k) - } - sort.Strings(sorted) - - for _, k := range sorted { - v := vars[k] - var p survey.Prompt - switch t := v.(type) { - case []string: - p = &survey.Select{ - Message: k, - Options: t, - } - default: - p = &survey.Input{ - Message: k, - Default: fmt.Sprintf("%v", t), - } - } - var a string - if err := survey.AskOne(p, &a); err != nil { - return nil, err - } - vars[k] = a - } - - return vars, nil -} - -func convertMap(m map[string]interface{}) template.FuncMap { - mm := make(template.FuncMap) - for k, v := range m { - vv := v // Enclosures in a loop - mm[k] = func() interface{} { - return fmt.Sprintf("%v", vv) - } - } - return mm -} - func mergeMaps(maps ...map[string]interface{}) map[string]interface{} { m := make(map[string]interface{}) for _, mm := range maps { diff --git a/registry/template_test.go b/registry/template_test.go new file mode 100644 index 0000000..be32ced --- /dev/null +++ b/registry/template_test.go @@ -0,0 +1,78 @@ +package registry + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +var ( + tmplContents = `{{title name}} {{.year}}` + tmplTemplate = `name = "john olheiser" + +[year] +default = 2020 + +[package] +default = "pkg"` + tmplGold = "John Olheiser 2020" + tmplNewGold = "DO NOT OVERWRITE!" +) + +func testExecute(t *testing.T) { + // Get template + tmpl, err := reg.GetTemplate("test") + if err != nil { + t.Logf("could not get template") + t.FailNow() + } + + // Execute template + if err := tmpl.Execute(destDir, true, true); err != nil { + t.Logf("could not execute template: %v\n", err) + t.FailNow() + } + + // 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() + } + + // 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() + } + + // 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() + } + + if err := tmpl.Execute(destDir, true, false); err != nil { + t.Logf("could not execute template: %v\n", err) + t.FailNow() + } + + 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() + } +}