1
0
mirror of https://github.com/go-task/task.git synced 2024-12-04 10:24:45 +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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 131 additions and 24 deletions

View File

@ -59,6 +59,7 @@ func main() {
init bool
list bool
listAll bool
listJson bool
status bool
force 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(&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(&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.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")
@ -162,7 +164,12 @@ func main() {
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)
return
}
@ -176,16 +183,9 @@ func main() {
return
}
if list {
if ok := e.ListTasks(task.FilterOutInternal(), task.FilterOutNoDesc()); !ok {
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks")
}
return
}
if listAll {
if ok := e.ListTasks(task.FilterOutInternal()); !ok {
e.Logger.Outf(logger.Yellow, "task: No tasks available")
if listOptions.ShouldListTasks() {
if foundTasks, err := e.ListTasks(listOptions); !foundTasks || err != nil {
os.Exit(1)
}
return
}

103
help.go
View File

@ -1,7 +1,10 @@
package task
import (
"context"
"encoding/json"
"fmt"
"github.com/go-task/task/v3/taskfile"
"io"
"log"
"os"
@ -9,16 +12,81 @@ import (
"strings"
"text/tabwriter"
"github.com/go-task/task/v3/internal/editors"
"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.
// Tasks that match the given filters will be excluded from the list.
// The function returns a boolean indicating whether or not tasks were found.
func (e *Executor) ListTasks(filters ...FilterFunc) bool {
tasks := e.GetTaskList(filters...)
// The function returns a boolean indicating whether tasks were found
// and an error if one was encountered while preparing the output.
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 {
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:")
@ -31,10 +99,12 @@ func (e *Executor) ListTasks(filters ...FilterFunc) bool {
if len(task.Aliases) > 0 {
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
}
fmt.Fprint(w, "\n")
_, _ = fmt.Fprint(w, "\n")
}
w.Flush()
return true
if err := w.Flush(); err != nil {
return false, err
}
return true, nil
}
// ListTaskNames prints only the task names in a Taskfile.
@ -69,3 +139,22 @@ func (e *Executor) ListTaskNames(allTasks bool) {
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 {
task, err := e.GetTask(call)
if err != nil {
e.ListTasks(FilterOutInternal(), FilterOutNoDesc())
return err
}
if task.Internal {
e.ListTasks(FilterOutInternal(), FilterOutNoDesc())
return &taskInternalError{taskName: call.Task}
}
}
@ -396,7 +394,7 @@ func (e *Executor) GetTaskList(filters ...FilterFunc) []*taskfile.Task {
tasks = filter(tasks)
}
// Sort the tasks
// Sort the tasks.
// Tasks that are not namespaced should be listed before tasks that are.
// We detect this by searching for a ':' in the task name.
sort.Slice(tasks, func(i, j int) bool {

View File

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