From a6f7984d4b37ab5c8c53fb04eb9c109f6caf29a3 Mon Sep 17 00:00:00 2001 From: parkerduckworth Date: Sun, 19 Jul 2020 18:52:32 -0500 Subject: [PATCH 1/5] Add command-level warning prompt --- internal/taskfile/cmd.go | 3 +++ internal/taskfile/task.go | 3 +++ internal/taskfile/taskfile.go | 3 +++ task.go | 24 ++++++++++++++++++++++++ variables.go | 2 ++ 5 files changed, 35 insertions(+) diff --git a/internal/taskfile/cmd.go b/internal/taskfile/cmd.go index c992dd30c8..4ed7043a47 100644 --- a/internal/taskfile/cmd.go +++ b/internal/taskfile/cmd.go @@ -11,6 +11,7 @@ type Cmd struct { Silent bool Task string Vars *Vars + Warning string IgnoreError bool } @@ -41,11 +42,13 @@ func (c *Cmd) UnmarshalYAML(unmarshal func(interface{}) error) error { var cmdStruct struct { Cmd string Silent bool + Warning string IgnoreError bool `yaml:"ignore_error"` } if err := unmarshal(&cmdStruct); err == nil && cmdStruct.Cmd != "" { c.Cmd = cmdStruct.Cmd c.Silent = cmdStruct.Silent + c.Warning = cmdStruct.Warning c.IgnoreError = cmdStruct.IgnoreError return nil } diff --git a/internal/taskfile/task.go b/internal/taskfile/task.go index 40c66b58a2..2083e059be 100644 --- a/internal/taskfile/task.go +++ b/internal/taskfile/task.go @@ -25,6 +25,7 @@ type Task struct { Silent bool Method string Prefix string + Warning string IgnoreError bool } @@ -69,6 +70,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { Silent bool Method string Prefix string + Warning string IgnoreError bool `yaml:"ignore_error"` } if err := unmarshal(&task); err == nil { @@ -87,6 +89,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error { t.Silent = task.Silent t.Method = task.Method t.Prefix = task.Prefix + t.Warning = task.Warning t.IgnoreError = task.IgnoreError return nil diff --git a/internal/taskfile/taskfile.go b/internal/taskfile/taskfile.go index d9a17ac016..96d20f686e 100644 --- a/internal/taskfile/taskfile.go +++ b/internal/taskfile/taskfile.go @@ -16,6 +16,7 @@ type Taskfile struct { Env *Vars Tasks Tasks Silent bool + Warning string } // UnmarshalYAML implements yaml.Unmarshaler interface @@ -30,6 +31,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error { Env *Vars Tasks Tasks Silent bool + Warning string } if err := unmarshal(&taskfile); err != nil { return err @@ -43,6 +45,7 @@ func (tf *Taskfile) UnmarshalYAML(unmarshal func(interface{}) error) error { tf.Env = taskfile.Env tf.Tasks = taskfile.Tasks tf.Silent = taskfile.Silent + tf.Warning = taskfile.Warning if tf.Expansions <= 0 { tf.Expansions = 2 } diff --git a/task.go b/task.go index 2807dd7fed..135e9b20fa 100644 --- a/task.go +++ b/task.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "os" + "strings" "sync" "sync/atomic" @@ -34,6 +35,7 @@ type Executor struct { Dir string Entrypoint string + Warning string Force bool Watch bool Verbose bool @@ -345,6 +347,17 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi } return nil case cmd.Cmd != "": + if cmd.Warning != "" { + fmt.Printf("%s (y/N): ", cmd.Warning) + + var response string + fmt.Scanln(&response) + + if !isConfirmed(response) { + return errors.New("cancelled at warning") + } + } + if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) { e.Logger.Errf(logger.Green, "task: %s", cmd.Cmd) } @@ -399,3 +412,14 @@ func getEnviron(t *taskfile.Task) []string { } return environ } + +func isConfirmed(response string) bool { + affirmativeResponses := []string{"y", "yes"} + + for _, affirm := range affirmativeResponses { + if strings.ToLower(response) == affirm { + return true + } + } + return false +} diff --git a/variables.go b/variables.go index 4470d29652..8a2c937f26 100644 --- a/variables.go +++ b/variables.go @@ -41,6 +41,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { Vars: nil, Env: nil, Silent: origTask.Silent, + Warning: origTask.Warning, Method: r.Replace(origTask.Method), Prefix: r.Replace(origTask.Prefix), IgnoreError: origTask.IgnoreError, @@ -77,6 +78,7 @@ func (e *Executor) CompiledTask(call taskfile.Call) (*taskfile.Task, error) { new.Cmds[i] = &taskfile.Cmd{ Task: r.Replace(cmd.Task), Silent: cmd.Silent, + Warning: cmd.Warning, Cmd: r.Replace(cmd.Cmd), Vars: r.ReplaceVars(cmd.Vars), IgnoreError: cmd.IgnoreError, From a0abd484bbfd27624977e8e97b45207d2858ea24 Mon Sep 17 00:00:00 2001 From: parkerduckworth Date: Sun, 19 Jul 2020 19:08:26 -0500 Subject: [PATCH 2/5] add task-level warning prompt --- task.go | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/task.go b/task.go index 135e9b20fa..02a5a4962d 100644 --- a/task.go +++ b/task.go @@ -35,7 +35,6 @@ type Executor struct { Dir string Entrypoint string - Warning string Force bool Watch bool Verbose bool @@ -257,6 +256,13 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { return &MaximumTaskCallExceededError{task: call.Task} } + if t.Warning != "" { + response := promptWithWarning(t.Warning) + if !isConfirmed(response) { + return errors.New("cancelled at warning") + } + } + if err := e.runDeps(ctx, t); err != nil { return err } @@ -348,13 +354,10 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi return nil case cmd.Cmd != "": if cmd.Warning != "" { - fmt.Printf("%s (y/N): ", cmd.Warning) - - var response string - fmt.Scanln(&response) - + response := promptWithWarning(cmd.Warning) if !isConfirmed(response) { - return errors.New("cancelled at warning") + // Continue to next cmd + return nil } } @@ -413,6 +416,15 @@ func getEnviron(t *taskfile.Task) []string { return environ } +func promptWithWarning(warning string) string { + fmt.Printf("%s (y/N): ", warning) + + var response string + fmt.Scanln(&response) + + return response +} + func isConfirmed(response string) bool { affirmativeResponses := []string{"y", "yes"} From 4f5606503c5e01a486071d39c597f9edddd548b9 Mon Sep 17 00:00:00 2001 From: parkerduckworth Date: Tue, 4 Aug 2020 12:05:21 -0500 Subject: [PATCH 3/5] skip task when task-level warning denied --- task.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/task.go b/task.go index 02a5a4962d..f6ed61754e 100644 --- a/task.go +++ b/task.go @@ -259,7 +259,7 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { if t.Warning != "" { response := promptWithWarning(t.Warning) if !isConfirmed(response) { - return errors.New("cancelled at warning") + return nil } } From 719c0111108305ed0070e3031bd784baa0c4e474 Mon Sep 17 00:00:00 2001 From: parkerduckworth Date: Tue, 4 Aug 2020 12:24:01 -0500 Subject: [PATCH 4/5] add yes option to automatically confirm warning prompts --- cmd/task/task.go | 3 +++ task.go | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/cmd/task/task.go b/cmd/task/task.go index 889fa0adb5..d985c5a2aa 100644 --- a/cmd/task/task.go +++ b/cmd/task/task.go @@ -68,6 +68,7 @@ func main() { entrypoint string output string color bool + yes bool ) pflag.BoolVar(&versionFlag, "version", false, "show Task version") @@ -86,6 +87,7 @@ func main() { pflag.StringVarP(&entrypoint, "taskfile", "t", "", `choose which Taskfile to run. Defaults to "Taskfile.yml"`) pflag.StringVarP(&output, "output", "o", "", "sets output style: [interleaved|group|prefixed]") pflag.BoolVarP(&color, "color", "c", true, "colored output") + pflag.BoolVarP(&yes, "yes", "y", false, "automatic yes to warning prompts") pflag.Parse() if versionFlag { @@ -131,6 +133,7 @@ func main() { Summary: summary, Parallel: parallel, Color: color, + Yes: yes, Stdin: os.Stdin, Stdout: os.Stdout, diff --git a/task.go b/task.go index f6ed61754e..88896e655b 100644 --- a/task.go +++ b/task.go @@ -43,6 +43,7 @@ type Executor struct { Summary bool Parallel bool Color bool + Yes bool Stdin io.Reader Stdout io.Writer @@ -256,9 +257,10 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error { return &MaximumTaskCallExceededError{task: call.Task} } - if t.Warning != "" { + if t.Warning != "" && !e.Yes { response := promptWithWarning(t.Warning) if !isConfirmed(response) { + // Skip task return nil } } @@ -353,10 +355,10 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi } return nil case cmd.Cmd != "": - if cmd.Warning != "" { + if cmd.Warning != "" && !e.Yes { response := promptWithWarning(cmd.Warning) if !isConfirmed(response) { - // Continue to next cmd + // Skip command return nil } } From acd6a4a72c918bfd88cea072132ccfae685bbb15 Mon Sep 17 00:00:00 2001 From: parkerduckworth Date: Tue, 4 Aug 2020 12:56:50 -0500 Subject: [PATCH 5/5] add documentation for warning --- docs/usage.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index a3a6a30449..67ebfc980d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -920,3 +920,47 @@ so task know which files to watch. [gotemplate]: https://golang.org/pkg/text/template/ [minify]: https://github.com/tdewolff/minify/tree/master/cmd/minify + +## Warning prompts + +You can add both task level and command level warning prompts to your Taskfile. + +Here is how `warning` can be used at both levels: + +```yaml +version: '3' + +tasks: + delete-things: + desc: Remove both important and unimportant files + warning: Are you sure you want to run this task? + cmds: + - rm -rf /unimportant-files + - cmd: rm -rf /important-files + warning: Are you sure you want to run this command? +``` + +In this example, a warning prompt will be presented both at the start of the task, +and again when the task reaches the command with a warning. + +> NOTE: as with [silent mode](#silent-mode) and [ignore errors](#ignore-errors), +> to add an option at the command level requires prefixing the target command with `cmd:` + +### Behavior of denied warnings + +When a task level warning is denied, the task is skipped. Command level warnings have +the same behavior: when denied, the task continues on to the next command (if any). + +### Automatic confirmation + +The `[-y | --yes]` option passed with a task call enables the ability to automatically +confirm a task with warnings. When provided, all warnings, at both task and command +levels are confirmed. + +To build off the example above: + +```bash +$ task delete-things -y +``` + +Will run the entire task without warning. Use with caution!