tasks.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package ast
  2. import (
  3. "fmt"
  4. "slices"
  5. "strings"
  6. "gopkg.in/yaml.v3"
  7. "github.com/go-task/task/v3/errors"
  8. "github.com/go-task/task/v3/internal/filepathext"
  9. "github.com/go-task/task/v3/internal/omap"
  10. )
  11. // Tasks represents a group of tasks
  12. type Tasks struct {
  13. omap.OrderedMap[string, *Task]
  14. }
  15. type MatchingTask struct {
  16. Task *Task
  17. Wildcards []string
  18. }
  19. func (t *Tasks) FindMatchingTasks(call *Call) []*MatchingTask {
  20. if call == nil {
  21. return nil
  22. }
  23. var task *Task
  24. var matchingTasks []*MatchingTask
  25. // If there is a direct match, return it
  26. if task = t.OrderedMap.Get(call.Task); task != nil {
  27. matchingTasks = append(matchingTasks, &MatchingTask{Task: task, Wildcards: nil})
  28. return matchingTasks
  29. }
  30. // Attempt a wildcard match
  31. // For now, we can just nil check the task before each loop
  32. _ = t.Range(func(key string, value *Task) error {
  33. if match, wildcards := value.WildcardMatch(call.Task); match {
  34. matchingTasks = append(matchingTasks, &MatchingTask{
  35. Task: value,
  36. Wildcards: wildcards,
  37. })
  38. }
  39. return nil
  40. })
  41. return matchingTasks
  42. }
  43. func (t1 *Tasks) Merge(t2 Tasks, include *Include, includedTaskfileVars *Vars) error {
  44. err := t2.Range(func(name string, v *Task) error {
  45. // We do a deep copy of the task struct here to ensure that no data can
  46. // be changed elsewhere once the taskfile is merged.
  47. task := v.DeepCopy()
  48. // Set the task to internal if EITHER the included task or the included
  49. // taskfile are marked as internal
  50. task.Internal = task.Internal || (include != nil && include.Internal)
  51. taskName := name
  52. if !include.Flatten {
  53. // Add namespaces to task dependencies
  54. for _, dep := range task.Deps {
  55. if dep != nil && dep.Task != "" {
  56. dep.Task = taskNameWithNamespace(dep.Task, include.Namespace)
  57. }
  58. }
  59. // Add namespaces to task commands
  60. for _, cmd := range task.Cmds {
  61. if cmd != nil && cmd.Task != "" {
  62. cmd.Task = taskNameWithNamespace(cmd.Task, include.Namespace)
  63. }
  64. }
  65. // Add namespaces to task aliases
  66. for i, alias := range task.Aliases {
  67. task.Aliases[i] = taskNameWithNamespace(alias, include.Namespace)
  68. }
  69. // Add namespace aliases
  70. if include != nil {
  71. for _, namespaceAlias := range include.Aliases {
  72. task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
  73. for _, alias := range v.Aliases {
  74. task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
  75. }
  76. }
  77. }
  78. taskName = taskNameWithNamespace(name, include.Namespace)
  79. task.Namespace = include.Namespace
  80. task.Task = taskName
  81. }
  82. if include.AdvancedImport {
  83. task.Dir = filepathext.SmartJoin(include.Dir, task.Dir)
  84. if task.IncludeVars == nil {
  85. task.IncludeVars = &Vars{}
  86. }
  87. task.IncludeVars.Merge(include.Vars, nil)
  88. task.IncludedTaskfileVars = includedTaskfileVars.DeepCopy()
  89. }
  90. if t1.Get(taskName) != nil {
  91. return &errors.TaskNameFlattenConflictError{
  92. TaskName: taskName,
  93. Include: include.Namespace,
  94. }
  95. }
  96. // Add the task to the merged taskfile
  97. t1.Set(taskName, task)
  98. return nil
  99. })
  100. // If the included Taskfile has a default task, being not flattened and the parent namespace has
  101. // no task with a matching name, we can add an alias so that the user can
  102. // run the included Taskfile's default task without specifying its full
  103. // name. If the parent namespace has aliases, we add another alias for each
  104. // of them.
  105. if t2.Get("default") != nil && t1.Get(include.Namespace) == nil && !include.Flatten {
  106. defaultTaskName := fmt.Sprintf("%s:default", include.Namespace)
  107. t1.Get(defaultTaskName).Aliases = append(t1.Get(defaultTaskName).Aliases, include.Namespace)
  108. t1.Get(defaultTaskName).Aliases = slices.Concat(t1.Get(defaultTaskName).Aliases, include.Aliases)
  109. }
  110. return err
  111. }
  112. func (t *Tasks) UnmarshalYAML(node *yaml.Node) error {
  113. switch node.Kind {
  114. case yaml.MappingNode:
  115. tasks := omap.New[string, *Task]()
  116. if err := node.Decode(&tasks); err != nil {
  117. return errors.NewTaskfileDecodeError(err, node)
  118. }
  119. // nolint: errcheck
  120. tasks.Range(func(name string, task *Task) error {
  121. // Set the task's name
  122. if task == nil {
  123. task = &Task{
  124. Task: name,
  125. }
  126. }
  127. task.Task = name
  128. // Set the task's location
  129. for _, keys := range node.Content {
  130. if keys.Value == name {
  131. task.Location = &Location{
  132. Line: keys.Line,
  133. Column: keys.Column,
  134. }
  135. }
  136. }
  137. tasks.Set(name, task)
  138. return nil
  139. })
  140. *t = Tasks{
  141. OrderedMap: tasks,
  142. }
  143. return nil
  144. }
  145. return errors.NewTaskfileDecodeError(nil, node).WithTypeMessage("tasks")
  146. }
  147. func taskNameWithNamespace(taskName string, namespace string) string {
  148. if strings.HasPrefix(taskName, NamespaceSeparator) {
  149. return strings.TrimPrefix(taskName, NamespaceSeparator)
  150. }
  151. return fmt.Sprintf("%s%s%s", namespace, NamespaceSeparator, taskName)
  152. }