scaffold/internal/template/manifest.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
}