Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command-level and task-level warning #352

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func main() {
entrypoint string
output string
color bool
yes bool
)

pflag.BoolVar(&versionFlag, "version", false, "show Task version")
Expand All @@ -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 {
Expand Down Expand Up @@ -131,6 +133,7 @@ func main() {
Summary: summary,
Parallel: parallel,
Color: color,
Yes: yes,

Stdin: os.Stdin,
Stdout: os.Stdout,
Expand Down
44 changes: 44 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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!
3 changes: 3 additions & 0 deletions internal/taskfile/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Cmd struct {
Silent bool
Task string
Vars *Vars
Warning string
IgnoreError bool
}

Expand Down Expand Up @@ -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
}
Expand Down
3 changes: 3 additions & 0 deletions internal/taskfile/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Task struct {
Silent bool
Method string
Prefix string
Warning string
IgnoreError bool
}

Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions internal/taskfile/taskfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type Taskfile struct {
Env *Vars
Tasks Tasks
Silent bool
Warning string
}

// UnmarshalYAML implements yaml.Unmarshaler interface
Expand All @@ -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
Expand All @@ -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
}
Expand Down
38 changes: 38 additions & 0 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"strings"
"sync"
"sync/atomic"

Expand Down Expand Up @@ -42,6 +43,7 @@ type Executor struct {
Summary bool
Parallel bool
Color bool
Yes bool

Stdin io.Reader
Stdout io.Writer
Expand Down Expand Up @@ -255,6 +257,14 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
return &MaximumTaskCallExceededError{task: call.Task}
}

if t.Warning != "" && !e.Yes {
response := promptWithWarning(t.Warning)
if !isConfirmed(response) {
// Skip task
return nil
}
}

if err := e.runDeps(ctx, t); err != nil {
return err
}
Expand Down Expand Up @@ -345,6 +355,14 @@ func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfi
}
return nil
case cmd.Cmd != "":
if cmd.Warning != "" && !e.Yes {
response := promptWithWarning(cmd.Warning)
if !isConfirmed(response) {
// Skip command
return nil
}
}

if e.Verbose || (!cmd.Silent && !t.Silent && !e.Taskfile.Silent && !e.Silent) {
e.Logger.Errf(logger.Green, "task: %s", cmd.Cmd)
}
Expand Down Expand Up @@ -399,3 +417,23 @@ 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"}

for _, affirm := range affirmativeResponses {
if strings.ToLower(response) == affirm {
return true
}
}
return false
}
2 changes: 2 additions & 0 deletions variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down