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:
@ -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
103
help.go
@ -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
|
||||||
|
}
|
||||||
|
14
internal/editors/output.go
Normal file
14
internal/editors/output.go
Normal 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"`
|
||||||
|
}
|
4
task.go
4
task.go
@ -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 {
|
||||||
|
12
task_test.go
12
task_test.go
@ -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")
|
||||||
|
Reference in New Issue
Block a user