2017-03-19 20:18:18 +02:00
|
|
|
package task
|
|
|
|
|
|
|
|
import (
|
2022-12-17 15:31:00 +02:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
2017-03-19 20:18:18 +02:00
|
|
|
"fmt"
|
2021-07-01 23:05:46 +02:00
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strings"
|
2018-02-17 18:22:18 +02:00
|
|
|
|
2024-06-28 18:59:46 +02:00
|
|
|
"github.com/Ladicle/tabwriter"
|
2023-03-17 14:34:06 +02:00
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
|
2022-12-17 15:31:00 +02:00
|
|
|
"github.com/go-task/task/v3/internal/editors"
|
2023-03-10 20:27:30 +02:00
|
|
|
"github.com/go-task/task/v3/internal/fingerprint"
|
2020-08-16 20:48:19 +02:00
|
|
|
"github.com/go-task/task/v3/internal/logger"
|
2023-04-06 13:07:57 +02:00
|
|
|
"github.com/go-task/task/v3/internal/sort"
|
2023-12-29 22:32:03 +02:00
|
|
|
"github.com/go-task/task/v3/taskfile/ast"
|
2017-03-19 20:18:18 +02:00
|
|
|
)
|
|
|
|
|
2022-12-17 15:31:00 +02:00
|
|
|
// ListOptions collects list-related options
|
|
|
|
type ListOptions struct {
|
|
|
|
ListOnlyTasksWithDescriptions bool
|
|
|
|
ListAllTasks bool
|
|
|
|
FormatTaskListAsJSON bool
|
2023-11-16 03:31:02 +02:00
|
|
|
NoStatus bool
|
2022-12-17 15:31:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewListOptions creates a new ListOptions instance
|
2023-11-16 03:31:02 +02:00
|
|
|
func NewListOptions(list, listAll, listAsJson, noStatus bool) ListOptions {
|
2022-12-17 15:31:00 +02:00
|
|
|
return ListOptions{
|
|
|
|
ListOnlyTasksWithDescriptions: list,
|
|
|
|
ListAllTasks: listAll,
|
|
|
|
FormatTaskListAsJSON: listAsJson,
|
2023-11-16 03:31:02 +02:00
|
|
|
NoStatus: noStatus,
|
2022-12-17 15:31:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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")
|
|
|
|
}
|
2023-11-16 03:31:02 +02:00
|
|
|
if o.NoStatus && !o.FormatTaskListAsJSON {
|
|
|
|
return fmt.Errorf("task: --no-status only applies to --json with --list or --list-all")
|
|
|
|
}
|
2022-12-17 15:31:00 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filters returns the slice of FilterFunc which filters a list
|
2023-12-29 22:32:03 +02:00
|
|
|
// of ast.Task according to the given ListOptions
|
2022-12-17 15:31:00 +02:00
|
|
|
func (o ListOptions) Filters() []FilterFunc {
|
2023-01-14 21:45:52 +02:00
|
|
|
filters := []FilterFunc{FilterOutInternal}
|
2022-12-17 15:31:00 +02:00
|
|
|
|
|
|
|
if o.ListOnlyTasksWithDescriptions {
|
2023-01-14 21:45:52 +02:00
|
|
|
filters = append(filters, FilterOutNoDesc)
|
2022-12-17 15:31:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return filters
|
|
|
|
}
|
|
|
|
|
2022-11-02 16:38:26 +02:00
|
|
|
// ListTasks prints a list of tasks.
|
|
|
|
// Tasks that match the given filters will be excluded from the list.
|
2022-12-17 15:31:00 +02:00
|
|
|
// 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) {
|
2023-01-14 21:45:52 +02:00
|
|
|
tasks, err := e.GetTaskList(o.Filters()...)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2022-12-17 15:31:00 +02:00
|
|
|
if o.FormatTaskListAsJSON {
|
2023-11-16 03:31:02 +02:00
|
|
|
output, err := e.ToEditorOutput(tasks, o.NoStatus)
|
2022-12-17 15:31:00 +02:00
|
|
|
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
|
|
|
|
}
|
2017-03-19 20:18:18 +02:00
|
|
|
if len(tasks) == 0 {
|
2022-12-17 15:31:00 +02:00
|
|
|
if o.ListOnlyTasksWithDescriptions {
|
2023-04-27 02:20:06 +02:00
|
|
|
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks\n")
|
2022-12-17 15:31:00 +02:00
|
|
|
} else if o.ListAllTasks {
|
2023-04-27 02:20:06 +02:00
|
|
|
e.Logger.Outf(logger.Yellow, "task: No tasks available\n")
|
2022-12-17 15:31:00 +02:00
|
|
|
}
|
|
|
|
return false, nil
|
2017-03-19 20:18:18 +02:00
|
|
|
}
|
2023-04-27 02:20:06 +02:00
|
|
|
e.Logger.Outf(logger.Default, "task: Available tasks for this project:\n")
|
2017-03-19 20:18:18 +02:00
|
|
|
|
|
|
|
// Format in tab-separated columns with a tab stop of 8.
|
2022-09-29 19:13:12 +02:00
|
|
|
w := tabwriter.NewWriter(e.Stdout, 0, 8, 6, ' ', 0)
|
2017-03-19 20:18:18 +02:00
|
|
|
for _, task := range tasks {
|
2023-05-07 00:04:20 +02:00
|
|
|
e.Logger.FOutf(w, logger.Yellow, "* ")
|
|
|
|
e.Logger.FOutf(w, logger.Green, task.Task)
|
2024-06-28 18:59:46 +02:00
|
|
|
desc := strings.ReplaceAll(task.Desc, "\n", " ")
|
|
|
|
e.Logger.FOutf(w, logger.Default, ": \t%s", desc)
|
2022-10-15 00:28:05 +02:00
|
|
|
if len(task.Aliases) > 0 {
|
2023-05-07 00:04:20 +02:00
|
|
|
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
|
2022-10-15 00:28:05 +02:00
|
|
|
}
|
2023-05-07 00:04:20 +02:00
|
|
|
_, _ = fmt.Fprint(w, "\n")
|
2017-03-19 20:18:18 +02:00
|
|
|
}
|
2022-12-17 15:31:00 +02:00
|
|
|
if err := w.Flush(); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return true, nil
|
2017-03-19 20:18:18 +02:00
|
|
|
}
|
2021-07-01 23:05:46 +02:00
|
|
|
|
2022-10-02 17:49:38 +02:00
|
|
|
// ListTaskNames prints only the task names in a Taskfile.
|
2022-03-21 19:59:25 +02:00
|
|
|
// Only tasks with a non-empty description are printed if allTasks is false.
|
|
|
|
// Otherwise, all task names are printed.
|
2024-02-19 23:15:51 +02:00
|
|
|
func (e *Executor) ListTaskNames(allTasks bool) error {
|
2022-03-21 19:59:25 +02:00
|
|
|
// use stdout if no output defined
|
2021-07-01 23:05:46 +02:00
|
|
|
var w io.Writer = os.Stdout
|
2022-03-21 19:59:25 +02:00
|
|
|
if e.Stdout != nil {
|
2021-07-01 23:05:46 +02:00
|
|
|
w = e.Stdout
|
|
|
|
}
|
2023-04-06 13:07:57 +02:00
|
|
|
|
|
|
|
// Get the list of tasks and sort them
|
|
|
|
tasks := e.Taskfile.Tasks.Values()
|
|
|
|
|
|
|
|
// Sort the tasks
|
|
|
|
if e.TaskSorter == nil {
|
|
|
|
e.TaskSorter = &sort.AlphaNumericWithRootTasksFirst{}
|
|
|
|
}
|
|
|
|
e.TaskSorter.Sort(tasks)
|
|
|
|
|
|
|
|
// Create a list of task names
|
|
|
|
taskNames := make([]string, 0, e.Taskfile.Tasks.Len())
|
|
|
|
for _, task := range tasks {
|
|
|
|
if (allTasks || task.Desc != "") && !task.Internal {
|
|
|
|
taskNames = append(taskNames, strings.TrimRight(task.Task, ":"))
|
|
|
|
for _, alias := range task.Aliases {
|
|
|
|
taskNames = append(taskNames, strings.TrimRight(alias, ":"))
|
2022-10-31 19:16:12 +02:00
|
|
|
}
|
2022-03-21 19:59:25 +02:00
|
|
|
}
|
2021-07-01 23:05:46 +02:00
|
|
|
}
|
2023-04-06 13:07:57 +02:00
|
|
|
for _, t := range taskNames {
|
2022-03-21 19:59:25 +02:00
|
|
|
fmt.Fprintln(w, t)
|
2021-07-01 23:05:46 +02:00
|
|
|
}
|
2024-02-19 23:15:51 +02:00
|
|
|
return nil
|
2021-07-01 23:05:46 +02:00
|
|
|
}
|
2022-12-17 15:31:00 +02:00
|
|
|
|
2023-12-29 22:32:03 +02:00
|
|
|
func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) {
|
2023-03-17 14:34:06 +02:00
|
|
|
o := &editors.Taskfile{
|
|
|
|
Tasks: make([]editors.Task, len(tasks)),
|
|
|
|
Location: e.Taskfile.Location,
|
2022-12-17 15:31:00 +02:00
|
|
|
}
|
2023-03-17 14:34:06 +02:00
|
|
|
var g errgroup.Group
|
|
|
|
for i := range tasks {
|
2023-12-18 11:45:41 +02:00
|
|
|
aliases := []string{}
|
2024-08-14 15:37:05 +02:00
|
|
|
if len(tasks[i].Aliases) > 0 {
|
|
|
|
aliases = tasks[i].Aliases
|
2023-12-18 11:45:41 +02:00
|
|
|
}
|
2023-11-16 03:38:53 +02:00
|
|
|
g.Go(func() error {
|
2024-08-14 15:37:05 +02:00
|
|
|
o.Tasks[i] = editors.Task{
|
|
|
|
Name: tasks[i].Name(),
|
|
|
|
Desc: tasks[i].Desc,
|
|
|
|
Summary: tasks[i].Summary,
|
2023-12-18 11:45:41 +02:00
|
|
|
Aliases: aliases,
|
2023-11-16 03:38:53 +02:00
|
|
|
UpToDate: false,
|
|
|
|
Location: &editors.Location{
|
2024-08-14 15:37:05 +02:00
|
|
|
Line: tasks[i].Location.Line,
|
|
|
|
Column: tasks[i].Location.Column,
|
|
|
|
Taskfile: tasks[i].Location.Taskfile,
|
2023-11-16 03:38:53 +02:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if noStatus {
|
2023-11-16 03:31:02 +02:00
|
|
|
return nil
|
2023-11-16 03:38:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the fingerprinting method to use
|
|
|
|
method := e.Taskfile.Method
|
2024-08-14 15:37:05 +02:00
|
|
|
if tasks[i].Method != "" {
|
|
|
|
method = tasks[i].Method
|
2023-11-16 03:38:53 +02:00
|
|
|
}
|
2024-08-14 15:37:05 +02:00
|
|
|
upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), tasks[i],
|
2023-11-16 03:38:53 +02:00
|
|
|
fingerprint.WithMethod(method),
|
2024-06-28 18:01:11 +02:00
|
|
|
fingerprint.WithTempDir(e.TempDir.Fingerprint),
|
2023-11-16 03:38:53 +02:00
|
|
|
fingerprint.WithDry(e.Dry),
|
|
|
|
fingerprint.WithLogger(e.Logger),
|
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2024-08-14 15:37:05 +02:00
|
|
|
o.Tasks[i].UpToDate = upToDate
|
2023-11-16 03:38:53 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
})
|
2022-12-17 15:31:00 +02:00
|
|
|
}
|
2023-03-17 14:34:06 +02:00
|
|
|
return o, g.Wait()
|
2022-12-17 15:31:00 +02:00
|
|
|
}
|