1
0
mirror of https://github.com/go-task/task.git synced 2025-06-15 00:15:10 +02:00

Add --json flag to be used by editor extensions (#936)

This commit is contained in:
David Alpert
2022-12-17 07:31:00 -06:00
committed by GitHub
parent 41a9316523
commit 321f7b59d8
5 changed files with 131 additions and 24 deletions

View File

@ -59,6 +59,7 @@ func main() {
init bool init bool
list bool list bool
listAll bool listAll bool
listJson bool
status bool status bool
force bool force bool
watch bool watch bool
@ -81,6 +82,7 @@ func main() {
pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yaml in the current folder") pflag.BoolVarP(&init, "init", "i", false, "creates a new Taskfile.yaml in the current folder")
pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile") pflag.BoolVarP(&list, "list", "l", false, "lists tasks with description of current Taskfile")
pflag.BoolVarP(&listAll, "list-all", "a", false, "lists tasks with or without a description") pflag.BoolVarP(&listAll, "list-all", "a", false, "lists tasks with or without a description")
pflag.BoolVarP(&listJson, "json", "j", false, "formats task list as json")
pflag.BoolVar(&status, "status", false, "exits with non-zero exit code if any of the given tasks is not up-to-date") pflag.BoolVar(&status, "status", false, "exits with non-zero exit code if any of the given tasks is not up-to-date")
pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date") pflag.BoolVarP(&force, "force", "f", false, "forces execution even when the task is up-to-date")
pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task") pflag.BoolVarP(&watch, "watch", "w", false, "enables watch of the given task")
@ -162,7 +164,12 @@ func main() {
OutputStyle: output, OutputStyle: output,
} }
if (list || listAll) && silent { var listOptions = task.NewListOptions(list, listAll, listJson)
if err := listOptions.Validate(); err != nil {
log.Fatal(err)
}
if (listOptions.ShouldListTasks()) && silent {
e.ListTaskNames(listAll) e.ListTaskNames(listAll)
return return
} }
@ -176,16 +183,9 @@ func main() {
return return
} }
if list { if listOptions.ShouldListTasks() {
if ok := e.ListTasks(task.FilterOutInternal(), task.FilterOutNoDesc()); !ok { if foundTasks, err := e.ListTasks(listOptions); !foundTasks || err != nil {
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks") os.Exit(1)
}
return
}
if listAll {
if ok := e.ListTasks(task.FilterOutInternal()); !ok {
e.Logger.Outf(logger.Yellow, "task: No tasks available")
} }
return return
} }

103
help.go
View File

@ -1,7 +1,10 @@
package task package task
import ( import (
"context"
"encoding/json"
"fmt" "fmt"
"github.com/go-task/task/v3/taskfile"
"io" "io"
"log" "log"
"os" "os"
@ -9,16 +12,81 @@ import (
"strings" "strings"
"text/tabwriter" "text/tabwriter"
"github.com/go-task/task/v3/internal/editors"
"github.com/go-task/task/v3/internal/logger" "github.com/go-task/task/v3/internal/logger"
) )
// ListOptions collects list-related options
type ListOptions struct {
ListOnlyTasksWithDescriptions bool
ListAllTasks bool
FormatTaskListAsJSON bool
}
// NewListOptions creates a new ListOptions instance
func NewListOptions(list, listAll, listAsJson bool) ListOptions {
return ListOptions{
ListOnlyTasksWithDescriptions: list,
ListAllTasks: listAll,
FormatTaskListAsJSON: listAsJson,
}
}
// ShouldListTasks returns true if one of the options to list tasks has been set to true
func (o ListOptions) ShouldListTasks() bool {
return o.ListOnlyTasksWithDescriptions || o.ListAllTasks
}
// Validate validates that the collection of list-related options are in a valid configuration
func (o ListOptions) Validate() error {
if o.ListOnlyTasksWithDescriptions && o.ListAllTasks {
return fmt.Errorf("task: cannot use --list and --list-all at the same time")
}
if o.FormatTaskListAsJSON && !o.ShouldListTasks() {
return fmt.Errorf("task: --json only applies to --list or --list-all")
}
return nil
}
// Filters returns the slice of FilterFunc which filters a list
// of taskfile.Task according to the given ListOptions
func (o ListOptions) Filters() []FilterFunc {
filters := []FilterFunc{FilterOutInternal()}
if o.ListOnlyTasksWithDescriptions {
filters = append(filters, FilterOutNoDesc())
}
return filters
}
// ListTasks prints a list of tasks. // ListTasks prints a list of tasks.
// Tasks that match the given filters will be excluded from the list. // Tasks that match the given filters will be excluded from the list.
// The function returns a boolean indicating whether or not tasks were found. // The function returns a boolean indicating whether tasks were found
func (e *Executor) ListTasks(filters ...FilterFunc) bool { // and an error if one was encountered while preparing the output.
tasks := e.GetTaskList(filters...) func (e *Executor) ListTasks(o ListOptions) (bool, error) {
tasks := e.GetTaskList(o.Filters()...)
if o.FormatTaskListAsJSON {
output, err := e.ToEditorOutput(tasks)
if err != nil {
return false, err
}
encoder := json.NewEncoder(e.Stdout)
encoder.SetIndent("", " ")
if err := encoder.Encode(output); err != nil {
return false, err
}
return len(tasks) > 0, nil
}
if len(tasks) == 0 { if len(tasks) == 0 {
return false if o.ListOnlyTasksWithDescriptions {
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks")
} else if o.ListAllTasks {
e.Logger.Outf(logger.Yellow, "task: No tasks available")
}
return false, nil
} }
e.Logger.Outf(logger.Default, "task: Available tasks for this project:") e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
@ -31,10 +99,12 @@ func (e *Executor) ListTasks(filters ...FilterFunc) bool {
if len(task.Aliases) > 0 { if len(task.Aliases) > 0 {
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", ")) e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
} }
fmt.Fprint(w, "\n") _, _ = fmt.Fprint(w, "\n")
} }
w.Flush() if err := w.Flush(); err != nil {
return true return false, err
}
return true, nil
} }
// ListTaskNames prints only the task names in a Taskfile. // ListTaskNames prints only the task names in a Taskfile.
@ -69,3 +139,22 @@ func (e *Executor) ListTaskNames(allTasks bool) {
fmt.Fprintln(w, t) fmt.Fprintln(w, t)
} }
} }
func (e *Executor) ToEditorOutput(tasks []*taskfile.Task) (*editors.Output, error) {
o := &editors.Output{
Tasks: make([]editors.Task, len(tasks)),
}
for i, t := range tasks {
upToDate, err := e.isTaskUpToDate(context.Background(), t)
if err != nil {
return nil, err
}
o.Tasks[i] = editors.Task{
Name: t.Name(),
Desc: t.Desc,
Summary: t.Summary,
UpToDate: upToDate,
}
}
return o, nil
}

View File

@ -0,0 +1,14 @@
package editors
// Output wraps task list output for use in editor integrations (e.g. VSCode, etc)
type Output struct {
Tasks []Task `json:"tasks"`
}
// Task describes a single task
type Task struct {
Name string `json:"name"`
Desc string `json:"desc"`
Summary string `json:"summary"`
UpToDate bool `json:"up_to_date"`
}

View File

@ -72,12 +72,10 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
for _, call := range calls { for _, call := range calls {
task, err := e.GetTask(call) task, err := e.GetTask(call)
if err != nil { if err != nil {
e.ListTasks(FilterOutInternal(), FilterOutNoDesc())
return err return err
} }
if task.Internal { if task.Internal {
e.ListTasks(FilterOutInternal(), FilterOutNoDesc())
return &taskInternalError{taskName: call.Task} return &taskInternalError{taskName: call.Task}
} }
} }
@ -396,7 +394,7 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) []*taskfile.Task {
tasks = filter(tasks) tasks = filter(tasks)
} }
// Sort the tasks // Sort the tasks.
// Tasks that are not namespaced should be listed before tasks that are. // Tasks that are not namespaced should be listed before tasks that are.
// We detect this by searching for a ':' in the task name. // We detect this by searching for a ':' in the task name.
sort.Slice(tasks, func(i, j int) bool { sort.Slice(tasks, func(i, j int) bool {

View File

@ -606,7 +606,9 @@ func TestNoLabelInList(t *testing.T) {
Stderr: &buff, Stderr: &buff,
} }
assert.NoError(t, e.Setup()) assert.NoError(t, e.Setup())
e.ListTasks(task.FilterOutInternal(), task.FilterOutNoDesc()) if _, err := e.ListTasks(task.ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
t.Error(err)
}
assert.Contains(t, buff.String(), "foo") assert.Contains(t, buff.String(), "foo")
} }
@ -624,7 +626,9 @@ func TestListAllShowsNoDesc(t *testing.T) {
assert.NoError(t, e.Setup()) assert.NoError(t, e.Setup())
var title string var title string
e.ListTasks(task.FilterOutInternal()) if _, err := e.ListTasks(task.ListOptions{ListAllTasks: true}); err != nil {
t.Error(err)
}
for _, title = range []string{ for _, title = range []string{
"foo", "foo",
"voo", "voo",
@ -646,7 +650,9 @@ func TestListCanListDescOnly(t *testing.T) {
} }
assert.NoError(t, e.Setup()) assert.NoError(t, e.Setup())
e.ListTasks(task.FilterOutInternal(), task.FilterOutNoDesc()) if _, err := e.ListTasks(task.ListOptions{ListOnlyTasksWithDescriptions: true}); err != nil {
t.Error(err)
}
var title string var title string
assert.Contains(t, buff.String(), "foo") assert.Contains(t, buff.String(), "foo")