Prompt for template variables values based on the available manifest
This commit is contained in:
@ -5,10 +5,9 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
"gitlab.com/wpetit/scaffold/internal/template"
|
||||
"forge.cadoles.com/wpetit/scaffold/internal/template"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"gitlab.com/wpetit/scaffold/internal/project"
|
||||
"forge.cadoles.com/wpetit/scaffold/internal/project"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli/v2"
|
||||
@ -65,23 +64,36 @@ func newProjectAction(c *cli.Context) error {
|
||||
return errors.Wrap(err, "could not fetch project")
|
||||
}
|
||||
|
||||
manifestFile := c.String("manifest")
|
||||
templateData := template.Data{}
|
||||
|
||||
manifestStat, err := vfs.Stat(manifestFile)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return errors.Wrap(err, "could not stat manifest file")
|
||||
}
|
||||
manifestPath := c.String("manifest")
|
||||
|
||||
templateData := make(map[string]interface{})
|
||||
|
||||
if os.IsNotExist(err) {
|
||||
log.Println("Could not find scaffold manifest.")
|
||||
} else {
|
||||
if manifestStat.IsDir() {
|
||||
return errors.New("scaffold manifest is not a file")
|
||||
manifestFile, err := vfs.Open(manifestPath)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return errors.Wrapf(err, "could not read manifest '%s'", manifestPath)
|
||||
}
|
||||
|
||||
log.Println("Could not find scaffold manifest.")
|
||||
}
|
||||
|
||||
if manifestFile != nil {
|
||||
defer manifestFile.Close()
|
||||
|
||||
log.Println("Loading template scaffold manifest...")
|
||||
|
||||
manifest, err := template.Load(manifestFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "could not load manifest '%s'", manifestPath)
|
||||
}
|
||||
|
||||
if err := manifest.Validate(); err != nil {
|
||||
return errors.Wrapf(err, "could not validate manifest '%s'", manifestPath)
|
||||
}
|
||||
|
||||
if err := template.Prompt(manifest, templateData); err != nil {
|
||||
return errors.Wrap(err, "could not prompt for template data")
|
||||
}
|
||||
}
|
||||
|
||||
directory := c.String("directory")
|
||||
@ -89,51 +101,6 @@ func newProjectAction(c *cli.Context) error {
|
||||
return template.CopyDir(vfs, ".", directory, &template.Option{
|
||||
TemplateData: templateData,
|
||||
TemplateExt: ".gotpl",
|
||||
IgnorePatterns: []string{manifestFile},
|
||||
IgnorePatterns: []string{manifestPath},
|
||||
})
|
||||
}
|
||||
|
||||
func promptForProjectName() (string, error) {
|
||||
validate := func(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("Project name cannot be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Project Name",
|
||||
Validate: validate,
|
||||
}
|
||||
|
||||
return prompt.Run()
|
||||
}
|
||||
|
||||
func promptForProjectNamespace() (string, error) {
|
||||
validate := func(input string) error {
|
||||
if input == "" {
|
||||
return errors.New("Project namespace cannot be empty")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Project namespace",
|
||||
Validate: validate,
|
||||
}
|
||||
|
||||
return prompt.Run()
|
||||
}
|
||||
|
||||
func promptForProjectType() (string, error) {
|
||||
prompt := promptui.Select{
|
||||
Label: "Project Type",
|
||||
Items: []string{"web"},
|
||||
}
|
||||
|
||||
_, result, err := prompt.Run()
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -2,6 +2,6 @@ package template
|
||||
|
||||
type Option struct {
|
||||
IgnorePatterns []string
|
||||
TemplateData map[string]interface{}
|
||||
TemplateData Data
|
||||
TemplateExt string
|
||||
}
|
||||
|
Reference in New Issue
Block a user