2020-11-17 05:41:34 +00:00
|
|
|
package registry
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2020-11-17 05:47:11 +00:00
|
|
|
"sort"
|
2020-11-17 05:41:34 +00:00
|
|
|
"strings"
|
2020-11-18 05:28:07 +00:00
|
|
|
"text/template"
|
2020-11-17 05:41:34 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/AlecAivazis/survey/v2"
|
|
|
|
"github.com/mholt/archiver/v3"
|
|
|
|
"github.com/pelletier/go-toml"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Template is a tmpl project
|
|
|
|
type Template struct {
|
|
|
|
reg *Registry `toml:"-"`
|
|
|
|
Name string `toml:"name"`
|
2020-11-18 05:28:07 +00:00
|
|
|
Path string `toml:"path"`
|
2020-11-17 05:41:34 +00:00
|
|
|
Repository string `toml:"repository"`
|
|
|
|
Branch string `toml:"branch"`
|
|
|
|
Created time.Time `toml:"created"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// ArchiveName is the name given to the archive for this Template
|
|
|
|
func (t *Template) ArchiveName() string {
|
|
|
|
return fmt.Sprintf("%s.tar.gz", t.Name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ArchivePath is the full path to the archive for this Template within the Registry
|
|
|
|
func (t *Template) ArchivePath() string {
|
|
|
|
return filepath.Join(t.reg.dir, t.ArchiveName())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute runs the Template and copies to dest
|
2020-11-18 05:28:07 +00:00
|
|
|
func (t *Template) Execute(dest string, defaults bool) error {
|
2020-11-17 05:41:34 +00:00
|
|
|
tmp, err := ioutil.TempDir(os.TempDir(), "tmpl")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmp)
|
|
|
|
|
|
|
|
if err := archiver.Unarchive(t.ArchivePath(), tmp); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-18 05:28:07 +00:00
|
|
|
vars, err := prompt(tmp, defaults)
|
2020-11-17 05:41:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
base := filepath.Join(tmp, "template")
|
|
|
|
return filepath.Walk(base, func(walkPath string, walkInfo os.FileInfo, walkErr error) error {
|
|
|
|
if walkErr != nil {
|
|
|
|
return walkErr
|
|
|
|
}
|
|
|
|
|
|
|
|
if walkInfo.IsDir() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
contents, err := ioutil.ReadFile(walkPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-11-18 05:28:07 +00:00
|
|
|
tmpl, err := template.New("tmpl").Funcs(mergeMaps(funcMap, convertMap(vars))).Parse(string(contents))
|
2020-11-17 05:41:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
newDest := strings.TrimPrefix(walkPath, base+"/")
|
|
|
|
newDest = filepath.Join(dest, newDest)
|
|
|
|
|
|
|
|
if err := os.MkdirAll(filepath.Dir(newDest), os.ModePerm); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
oldFi, err := os.Lstat(walkPath)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
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 {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return newFi.Close()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-18 05:28:07 +00:00
|
|
|
func prompt(dir string, defaults bool) (map[string]interface{}, error) {
|
2020-11-17 05:41:34 +00:00
|
|
|
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
|
|
|
|
}
|
2020-11-18 05:28:07 +00:00
|
|
|
vars := tree.ToMap()
|
|
|
|
|
|
|
|
// Return early if we only want defaults
|
|
|
|
if defaults {
|
|
|
|
return vars, nil
|
|
|
|
}
|
2020-11-17 05:41:34 +00:00
|
|
|
|
2020-11-17 05:47:11 +00:00
|
|
|
// 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)
|
2020-11-17 05:41:34 +00:00
|
|
|
|
2020-11-17 05:47:11 +00:00
|
|
|
for _, k := range sorted {
|
|
|
|
v := vars[k]
|
2020-11-17 05:41:34 +00:00
|
|
|
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),
|
|
|
|
}
|
|
|
|
}
|
2020-11-18 17:45:46 +00:00
|
|
|
var a string
|
|
|
|
if err := survey.AskOne(p, &a); err != nil {
|
2020-11-17 05:41:34 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
2020-11-18 17:45:46 +00:00
|
|
|
vars[k] = a
|
2020-11-17 05:41:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return vars, nil
|
|
|
|
}
|
2020-11-18 05:28:07 +00:00
|
|
|
|
|
|
|
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 {
|
|
|
|
for k, v := range mm {
|
|
|
|
m[k] = v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return m
|
|
|
|
}
|