159 lines
3.0 KiB
Go
159 lines
3.0 KiB
Go
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"`
|
|
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
|
|
}
|