diff --git a/cmd/task/task.go b/cmd/task/task.go index 72f73bd87d..304927951b 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -18,7 +18,7 @@ var ( version = "master" ) -const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--dry] [--summary] [task...] +const usage = `Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--dry] [--summary] [--interactive] [task...] Runs the specified task(s). Falls back to the "default" task if no task name was specified, or lists all tasks if an unknown task name was specified. @@ -58,6 +58,7 @@ func main() { silent bool dry bool summary bool + interactive bool dir string output string color bool @@ -73,6 +74,7 @@ func main() { pflag.BoolVarP(&silent, "silent", "s", false, "disables echoing") pflag.BoolVar(&dry, "dry", false, "compiles and prints tasks in the order that they would be run, without executing them") pflag.BoolVar(&summary, "summary", false, "show summary about a task") + pflag.BoolVar(&interactive, "interactive", true, "interactive prompt inputs") pflag.StringVarP(&dir, "dir", "d", "", "sets directory of execution") pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]") pflag.BoolVarP(&color, "color", "c", true, "colored output") @@ -95,14 +97,16 @@ func main() { } e := task.Executor{ - Force: force, - Watch: watch, - Verbose: verbose, - Silent: silent, - Dir: dir, - Dry: dry, - Summary: summary, - Color: color, + Force: force, + Watch: watch, + Verbose: verbose, + Silent: silent, + Dir: dir, + Dry: dry, + Entrypoint: entrypoint, + Summary: summary, + Color: color, + Interactive: interactive, Stdin: os.Stdin, Stdout: os.Stdout, diff --git a/completion/zsh/_task b/completion/zsh/_task index f2bd81de01..b65c0dbec5 100755 --- a/completion/zsh/_task +++ b/completion/zsh/_task @@ -18,6 +18,7 @@ _arguments \ '(-l --list)'{-l,--list} \ '(-s --silent)'{-s,--silent} \ '(--status)'--status \ + '(--interactive)'--interactive \ '(-v --verbose)'{-v,--verbose} \ '(--version)'--version \ '(-w --watch)'{-w,--watch} \ diff --git a/internal/taskfile/input.go b/internal/taskfile/input.go new file mode 100644 index 0000000000..aeab41a1be --- /dev/null +++ b/internal/taskfile/input.go @@ -0,0 +1,67 @@ +package taskfile + +import ( + "fmt" + "regexp" + "strings" +) + +// Inputs represents a group of Input +type Inputs map[string]*Input + +// Input represents an interactive input variable +type Input struct { + Desc string + Required bool + Default string + Validator string +} + +// FullTitle returns the input full title suffixed +func (i *Input) FullTitle(name string) (t string) { + t = i.Desc + if t == "" { + t = name + } + + st := i.subtitle() + if st == "" { + return fmt.Sprintf("%s:", t) + } + + return fmt.Sprintf("%s (%s):", t, st) +} + +// subtitle returns the input subtitle infos +func (i *Input) subtitle() (subtitle string) { + infos := []string{} + + // Required input + if i.Required { + infos = append(infos, "required: yes") + } + // Default value + if i.Default != "" { + infos = append(infos, fmt.Sprintf("default: \"%s\"", i.Default)) + } + // Validator value + if i.Validator != "" { + infos = append(infos, fmt.Sprintf("validator: \"%s\"", i.Validator)) + } + + if len(infos) > 0 { + subtitle = fmt.Sprintf("%s", strings.Join(infos, ",")) + } + + return +} + +// Validate returns if the input is validate +func (i *Input) Validate(val string) bool { + if i.Validator == "" { + return true + } + + m, _ := regexp.MatchString(i.Validator, val) + return m +} diff --git a/internal/taskfile/task.go b/internal/taskfile/task.go index bc4bf9812b..03e1b656ba 100644 --- a/internal/taskfile/task.go +++ b/internal/taskfile/task.go @@ -5,20 +5,21 @@ type Tasks map[string]*Task // Task represents a task type Task struct { - Task string - Cmds []*Cmd - Deps []*Dep - Desc string - Summary string - Sources []string - Generates []string - Status []string + Task string + Inputs Inputs + Cmds []*Cmd + Deps []*Dep + Desc string + Summary string + Sources []string + Generates []string + Status []string Preconditions []*Precondition - Dir string - Vars Vars - Env Vars - Silent bool - Method string - Prefix string - IgnoreError bool `yaml:"ignore_error"` + Dir string + Vars Vars + Env Vars + Silent bool + Method string + Prefix string + IgnoreError bool `yaml:"ignore_error"` } diff --git a/task.go b/task.go index 72e3e0b5ea..abe3328612 100644 --- a/task.go +++ b/task.go @@ -32,14 +32,17 @@ const ( // Executor executes a Taskfile type Executor struct { Taskfile *taskfile.Taskfile - Dir string - Force bool - Watch bool - Verbose bool - Silent bool - Dry bool - Summary bool - Color bool + + Dir string + Entrypoint string + Force bool + Watch bool + Verbose bool + Silent bool + Dry bool + Summary bool + Color bool + Interactive bool Stdin io.Reader Stdout io.Writer @@ -204,6 +207,8 @@ func (e *Executor) Setup() error { // RunTask runs a task by its name func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { + e.runInputs(ctx, &call) + t, err := e.CompiledTask(call) if err != nil { return err @@ -273,6 +278,34 @@ func (e *Executor) mkdir(t *taskfile.Task) error { return nil } +func (e *Executor) runInputs(ctx context.Context, call *taskfile.Call) { + if origTask, ok := e.Taskfile.Tasks[call.Task]; ok { + if call.Vars == nil { + call.Vars = taskfile.Vars{} + } + for name, input := range origTask.Inputs { + var in string + if e.Interactive { + fmt.Println(input.FullTitle(name)) + fmt.Fscanln(e.Stdin, &in) + } + if in == "" { + if input.Required { + e.Logger.Errf("Input required") + continue + } + in = input.Default + } + if !input.Validate(in) { + e.Logger.Errf("Input invalid") + continue + } + + call.Vars[name] = taskfile.Var{Static: in} + } + } +} + func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error { g, ctx := errgroup.WithContext(ctx) diff --git a/variables.go b/variables.go index 9c22737255..315ba254f8 100644 --- a/variables.go +++ b/variables.go @@ -24,6 +24,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { new := taskfile.Task{ Task: origTask.Task, + Inputs: origTask.Inputs, Desc: r.Replace(origTask.Desc), Sources: r.ReplaceSlice(origTask.Sources), Generates: r.ReplaceSlice(origTask.Generates),