help.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. package task
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "os"
  8. "strings"
  9. "github.com/Ladicle/tabwriter"
  10. "golang.org/x/sync/errgroup"
  11. "github.com/go-task/task/v3/internal/editors"
  12. "github.com/go-task/task/v3/internal/fingerprint"
  13. "github.com/go-task/task/v3/internal/logger"
  14. "github.com/go-task/task/v3/internal/sort"
  15. "github.com/go-task/task/v3/taskfile/ast"
  16. )
  17. // ListOptions collects list-related options
  18. type ListOptions struct {
  19. ListOnlyTasksWithDescriptions bool
  20. ListAllTasks bool
  21. FormatTaskListAsJSON bool
  22. NoStatus bool
  23. }
  24. // NewListOptions creates a new ListOptions instance
  25. func NewListOptions(list, listAll, listAsJson, noStatus bool) ListOptions {
  26. return ListOptions{
  27. ListOnlyTasksWithDescriptions: list,
  28. ListAllTasks: listAll,
  29. FormatTaskListAsJSON: listAsJson,
  30. NoStatus: noStatus,
  31. }
  32. }
  33. // ShouldListTasks returns true if one of the options to list tasks has been set to true
  34. func (o ListOptions) ShouldListTasks() bool {
  35. return o.ListOnlyTasksWithDescriptions || o.ListAllTasks
  36. }
  37. // Validate validates that the collection of list-related options are in a valid configuration
  38. func (o ListOptions) Validate() error {
  39. if o.ListOnlyTasksWithDescriptions && o.ListAllTasks {
  40. return fmt.Errorf("task: cannot use --list and --list-all at the same time")
  41. }
  42. if o.FormatTaskListAsJSON && !o.ShouldListTasks() {
  43. return fmt.Errorf("task: --json only applies to --list or --list-all")
  44. }
  45. if o.NoStatus && !o.FormatTaskListAsJSON {
  46. return fmt.Errorf("task: --no-status only applies to --json with --list or --list-all")
  47. }
  48. return nil
  49. }
  50. // Filters returns the slice of FilterFunc which filters a list
  51. // of ast.Task according to the given ListOptions
  52. func (o ListOptions) Filters() []FilterFunc {
  53. filters := []FilterFunc{FilterOutInternal}
  54. if o.ListOnlyTasksWithDescriptions {
  55. filters = append(filters, FilterOutNoDesc)
  56. }
  57. return filters
  58. }
  59. // ListTasks prints a list of tasks.
  60. // Tasks that match the given filters will be excluded from the list.
  61. // The function returns a boolean indicating whether tasks were found
  62. // and an error if one was encountered while preparing the output.
  63. func (e *Executor) ListTasks(o ListOptions) (bool, error) {
  64. tasks, err := e.GetTaskList(o.Filters()...)
  65. if err != nil {
  66. return false, err
  67. }
  68. if o.FormatTaskListAsJSON {
  69. output, err := e.ToEditorOutput(tasks, o.NoStatus)
  70. if err != nil {
  71. return false, err
  72. }
  73. encoder := json.NewEncoder(e.Stdout)
  74. encoder.SetIndent("", " ")
  75. if err := encoder.Encode(output); err != nil {
  76. return false, err
  77. }
  78. return len(tasks) > 0, nil
  79. }
  80. if len(tasks) == 0 {
  81. if o.ListOnlyTasksWithDescriptions {
  82. e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks\n")
  83. } else if o.ListAllTasks {
  84. e.Logger.Outf(logger.Yellow, "task: No tasks available\n")
  85. }
  86. return false, nil
  87. }
  88. e.Logger.Outf(logger.Default, "task: Available tasks for this project:\n")
  89. // Format in tab-separated columns with a tab stop of 8.
  90. w := tabwriter.NewWriter(e.Stdout, 0, 8, 6, ' ', 0)
  91. for _, task := range tasks {
  92. e.Logger.FOutf(w, logger.Yellow, "* ")
  93. e.Logger.FOutf(w, logger.Green, task.Task)
  94. desc := strings.ReplaceAll(task.Desc, "\n", " ")
  95. e.Logger.FOutf(w, logger.Default, ": \t%s", desc)
  96. if len(task.Aliases) > 0 {
  97. e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
  98. }
  99. _, _ = fmt.Fprint(w, "\n")
  100. }
  101. if err := w.Flush(); err != nil {
  102. return false, err
  103. }
  104. return true, nil
  105. }
  106. // ListTaskNames prints only the task names in a Taskfile.
  107. // Only tasks with a non-empty description are printed if allTasks is false.
  108. // Otherwise, all task names are printed.
  109. func (e *Executor) ListTaskNames(allTasks bool) error {
  110. // use stdout if no output defined
  111. var w io.Writer = os.Stdout
  112. if e.Stdout != nil {
  113. w = e.Stdout
  114. }
  115. // Get the list of tasks and sort them
  116. tasks := e.Taskfile.Tasks.Values()
  117. // Sort the tasks
  118. if e.TaskSorter == nil {
  119. e.TaskSorter = &sort.AlphaNumericWithRootTasksFirst{}
  120. }
  121. e.TaskSorter.Sort(tasks)
  122. // Create a list of task names
  123. taskNames := make([]string, 0, e.Taskfile.Tasks.Len())
  124. for _, task := range tasks {
  125. if (allTasks || task.Desc != "") && !task.Internal {
  126. taskNames = append(taskNames, strings.TrimRight(task.Task, ":"))
  127. for _, alias := range task.Aliases {
  128. taskNames = append(taskNames, strings.TrimRight(alias, ":"))
  129. }
  130. }
  131. }
  132. for _, t := range taskNames {
  133. fmt.Fprintln(w, t)
  134. }
  135. return nil
  136. }
  137. func (e *Executor) ToEditorOutput(tasks []*ast.Task, noStatus bool) (*editors.Taskfile, error) {
  138. o := &editors.Taskfile{
  139. Tasks: make([]editors.Task, len(tasks)),
  140. Location: e.Taskfile.Location,
  141. }
  142. var g errgroup.Group
  143. for i := range tasks {
  144. aliases := []string{}
  145. if len(tasks[i].Aliases) > 0 {
  146. aliases = tasks[i].Aliases
  147. }
  148. g.Go(func() error {
  149. o.Tasks[i] = editors.Task{
  150. Name: tasks[i].Name(),
  151. Desc: tasks[i].Desc,
  152. Summary: tasks[i].Summary,
  153. Aliases: aliases,
  154. UpToDate: false,
  155. Location: &editors.Location{
  156. Line: tasks[i].Location.Line,
  157. Column: tasks[i].Location.Column,
  158. Taskfile: tasks[i].Location.Taskfile,
  159. },
  160. }
  161. if noStatus {
  162. return nil
  163. }
  164. // Get the fingerprinting method to use
  165. method := e.Taskfile.Method
  166. if tasks[i].Method != "" {
  167. method = tasks[i].Method
  168. }
  169. upToDate, err := fingerprint.IsTaskUpToDate(context.Background(), tasks[i],
  170. fingerprint.WithMethod(method),
  171. fingerprint.WithTempDir(e.TempDir.Fingerprint),
  172. fingerprint.WithDry(e.Dry),
  173. fingerprint.WithLogger(e.Logger),
  174. )
  175. if err != nil {
  176. return err
  177. }
  178. o.Tasks[i].UpToDate = upToDate
  179. return nil
  180. })
  181. }
  182. return o, g.Wait()
  183. }