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 }