Docs overhaul, path expansion, more tests, better prompts
Signed-off-by: jolheiser <john.olheiser@gmail.com>pull/7/head v0.0.5
parent
ff2f159802
commit
0b0de4be35
|
@ -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
|
146
DOCS.md
146
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`
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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("<repo> <name>")
|
||||
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
|
||||
}
|
||||
|
||||
reg, err := registry.Open(flags.Registry)
|
||||
|
|
|
@ -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"
|
||||
`
|
||||
|
|
|
@ -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("<name>")
|
||||
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
|
||||
}
|
||||
|
||||
reg, err := registry.Open(flags.Registry)
|
||||
|
|
|
@ -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("<path> <name>")
|
||||
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
|
||||
}
|
||||
|
||||
reg, err := registry.Open(flags.Registry)
|
||||
|
|
|
@ -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("<repo> <name>")
|
||||
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("<name>")
|
||||
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
|
||||
}
|
||||
|
||||
reg, err := registry.Open(flags.Registry)
|
||||
|
|
15
cmd/test.go
15
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")
|
||||
}
|
||||
|
|
|
@ -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("<name>")
|
||||
return cli.ShowCommandHelp(ctx, ctx.Command.Name)
|
||||
}
|
||||
|
||||
reg, err := registry.Open(flags.Registry)
|
||||
|
|
18
cmd/use.go
18
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",
|
||||
},
|
||||
},
|
||||
ArgsUsage: "[name] [destination (default: \".\")]",
|
||||
Action: runUse,
|
||||
}
|
||||
|
||||
func runUse(ctx *cli.Context) error {
|
||||
if ctx.NArg() < 2 {
|
||||
return errors.New("<name> <dest>")
|
||||
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
|
||||
}
|
||||
|
||||
|
|
2
docs.go
2
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)
|
||||
}
|
||||
|
|
|
@ -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]
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue