Prompt for template variables values based on the available manifest

This commit is contained in:
2020-02-21 14:29:26 +01:00
parent f5d5afa82d
commit d9e446553c
7 changed files with 217 additions and 73 deletions

View File

@ -9,7 +9,7 @@ import (
"strings"
"text/template"
"gitlab.com/wpetit/scaffold/internal/fs"
"forge.cadoles.com/wpetit/scaffold/internal/fs"
"gopkg.in/src-d/go-billy.v4"
"github.com/Masterminds/sprig"
@ -60,8 +60,6 @@ func CopyDir(vfs billy.Filesystem, baseDir string, dst string, opts *Option) err
dstPath := filepath.Join(dst, relSrcPath)
log.Printf("relSrcPath: %s, dstPath: %s", relSrcPath, dstPath)
if info.IsDir() {
log.Printf("creating dir '%s'", dstPath)
if err := os.MkdirAll(dstPath, 0755); err != nil {

View File

@ -1,11 +1,158 @@
package template
import (
"io"
"io/ioutil"
"github.com/antonmedv/expr"
"github.com/antonmedv/expr/vm"
"github.com/manifoldco/promptui"
"github.com/pkg/errors"
"gopkg.in/yaml.v2"
)
const (
ManifestVersion1 = "1"
VarTypeString = "string"
VarTypeSelect = "select"
)
type Data map[string]interface{}
type Manifest struct {
Version string `yaml:"version"`
Vars []Var `yaml:"vars"`
}
type Var struct {
Type string `yaml:"type"`
Name string `yaml:"name"`
Type string `yaml:"type"`
Name string `yaml:"name"`
Description string `yaml:"description"`
Default string `yaml:"default"`
Constraints []Constraint `yaml:"constraints"`
Items []string `yaml:"items"`
}
type Constraint struct {
Rule string `yaml:"rule"`
Message string `yaml:"message"`
}
func (m *Manifest) Validate() error {
if m.Version != ManifestVersion1 {
return errors.Errorf("unexpected manifest version: '%s'", m.Version)
}
return nil
}
func Load(r io.Reader) (*Manifest, error) {
data, err := ioutil.ReadAll(r)
if err != nil {
return nil, errors.Wrap(err, "could not read manifest")
}
manifest := &Manifest{}
if err := yaml.Unmarshal(data, manifest); err != nil {
return nil, errors.Wrap(err, "could not unmarshal manifest")
}
return manifest, nil
}
func Prompt(manifest *Manifest, data Data) error {
for _, v := range manifest.Vars {
var (
value string
err error
)
switch v.Type {
case VarTypeString:
value, err = promptString(v)
case VarTypeSelect:
value, err = promptSelect(v)
default:
err = errors.Errorf("unexpected variable type '%s'", v.Type)
}
if err != nil {
return err
}
data[v.Name] = value
}
return nil
}
func promptString(v Var) (string, error) {
var validate func(string) error
if len(v.Constraints) > 0 {
rules := make([]*vm.Program, 0, len(v.Constraints))
for _, c := range v.Constraints {
r, err := expr.Compile(c.Rule)
if err != nil {
return "", errors.Wrapf(err, "invalid constraint syntax: '%s'", c.Rule)
}
rules = append(rules, r)
}
validate = func(input string) error {
env := struct {
Input string
}{input}
for i, r := range rules {
result, err := expr.Run(r, env)
if err != nil {
return errors.Wrapf(err, "could not run constraint rule (constraint #%d)", i)
}
match, ok := result.(bool)
if !ok {
return errors.Errorf("unexpected constraint rule result '%v' (constraint #%d)", result, i)
}
if match {
return errors.New(v.Constraints[i].Message)
}
}
return nil
}
}
prompt := promptui.Prompt{
Label: v.Description,
Validate: validate,
Default: v.Default,
}
value, err := prompt.Run()
if err != nil {
return "", errors.Wrapf(err, "could not prompt for variable '%s'", v.Name)
}
return value, nil
}
func promptSelect(v Var) (string, error) {
prompt := promptui.Select{
Label: v.Description,
Items: v.Items,
}
_, value, err := prompt.Run()
if err != nil {
return "", errors.Wrapf(err, "could not prompt for variable '%s'", v.Name)
}
return value, nil
}

View File

@ -2,6 +2,6 @@ package template
type Option struct {
IgnorePatterns []string
TemplateData map[string]interface{}
TemplateData Data
TemplateExt string
}