1
0
mirror of https://github.com/go-task/task.git synced 2025-01-04 03:48:02 +02:00

refactor: implement task list filtering

This commit is contained in:
Pete Davison 2022-11-02 14:38:26 +00:00
parent 3a0c7a8c36
commit fa105a8a93
6 changed files with 95 additions and 75 deletions

View File

@ -2,6 +2,8 @@
## Unreleased
- Tasks in the root Taskfile will now be displayed first in `--list`/`--list-all`
output ([#806](https://github.com/go-task/task/pull/806), [#890](https://github.com/go-task/task/pull/890)).
- It's now possible to call a `default` task in an included Taskfile by using
just the namespace. For example: `docs:default` is now automatically
aliased to `docs`

View File

@ -177,12 +177,16 @@ func main() {
}
if list {
e.ListTasksWithDesc()
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 {
e.ListAllTasks()
if ok := e.ListTasks(task.FilterOutInternal()); !ok {
e.Logger.Outf(logger.Yellow, "task: No tasks available")
}
return
}

58
help.go
View File

@ -10,34 +10,15 @@ import (
"text/tabwriter"
"github.com/go-task/task/v3/internal/logger"
"github.com/go-task/task/v3/taskfile"
)
// ListTasksWithDesc reports tasks that have a description spec.
func (e *Executor) ListTasksWithDesc() {
e.printTasks(false)
}
// ListAllTasks reports all tasks, with or without a description spec.
func (e *Executor) ListAllTasks() {
e.printTasks(true)
}
func (e *Executor) printTasks(listAll bool) {
var tasks []*taskfile.Task
if listAll {
tasks = e.allTaskNames()
} else {
tasks = e.tasksWithDesc()
}
// 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...)
if len(tasks) == 0 {
if listAll {
e.Logger.Outf(logger.Yellow, "task: No tasks available")
} else {
e.Logger.Outf(logger.Yellow, "task: No tasks with description available. Try --list-all to list all tasks")
}
return
return false
}
e.Logger.Outf(logger.Default, "task: Available tasks for this project:")
@ -53,32 +34,7 @@ func (e *Executor) printTasks(listAll bool) {
fmt.Fprint(w, "\n")
}
w.Flush()
}
func (e *Executor) allTaskNames() (tasks []*taskfile.Task) {
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
for _, task := range e.Taskfile.Tasks {
if !task.Internal {
tasks = append(tasks, task)
}
}
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
return
}
func (e *Executor) tasksWithDesc() (tasks []*taskfile.Task) {
tasks = make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
for _, task := range e.Taskfile.Tasks {
if !task.Internal && task.Desc != "" {
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
if err == nil {
task = compiledTask
}
tasks = append(tasks, task)
}
}
sort.Slice(tasks, func(i, j int) bool { return tasks[i].Task < tasks[j].Task })
return
return true
}
// ListTaskNames prints only the task names in a Taskfile.

76
task.go
View File

@ -5,6 +5,8 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
"sync"
"sync/atomic"
@ -70,12 +72,12 @@ func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
for _, call := range calls {
task, err := e.GetTask(call)
if err != nil {
e.ListTasksWithDesc()
e.ListTasks(FilterOutInternal(), FilterOutNoDesc())
return err
}
if task.Internal {
e.ListTasksWithDesc()
e.ListTasks(FilterOutInternal(), FilterOutNoDesc())
return &taskInternalError{taskName: call.Task}
}
}
@ -374,3 +376,73 @@ func (e *Executor) GetTask(call taskfile.Call) (*taskfile.Task, error) {
return matchingTask, nil
}
type FilterFunc func(tasks []*taskfile.Task) []*taskfile.Task
func (e *Executor) GetTaskList(filters ...FilterFunc) []*taskfile.Task {
tasks := make([]*taskfile.Task, 0, len(e.Taskfile.Tasks))
// Fetch and compile the list of tasks
for _, task := range e.Taskfile.Tasks {
compiledTask, err := e.FastCompiledTask(taskfile.Call{Task: task.Task})
if err == nil {
task = compiledTask
}
tasks = append(tasks, task)
}
// Filter the tasks
for _, filter := range filters {
tasks = filter(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 {
iContainsColon := strings.Contains(tasks[i].Task, ":")
jContainsColon := strings.Contains(tasks[j].Task, ":")
if iContainsColon == jContainsColon {
return tasks[i].Task < tasks[j].Task
}
if !iContainsColon && jContainsColon {
return true
}
return false
})
return tasks
}
// Filter is a generic task filtering function. It will remove each task in the
// slice where the result of the given function is true.
func Filter(f func(task *taskfile.Task) bool) FilterFunc {
return func(tasks []*taskfile.Task) []*taskfile.Task {
shift := 0
for _, task := range tasks {
if !f(task) {
tasks[shift] = task
shift++
}
}
// This loop stops any memory leaks
for j := shift; j < len(tasks); j++ {
tasks[j] = nil
}
return slices.Clip(tasks[:shift])
}
}
// FilterOutNoDesc removes all tasks that do not contain a description.
func FilterOutNoDesc() FilterFunc {
return Filter(func(task *taskfile.Task) bool {
return task.Desc == ""
})
}
// FilterOutInternal removes all tasks that are marked as internal.
func FilterOutInternal() FilterFunc {
return Filter(func(task *taskfile.Task) bool {
return task.Internal
})
}

View File

@ -497,9 +497,6 @@ func TestAlias(t *testing.T) {
func TestDuplicateAlias(t *testing.T) {
const dir = "testdata/alias"
data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias-duplicate.txt"))
assert.NoError(t, err)
var buff bytes.Buffer
e := task.Executor{
Dir: dir,
@ -508,7 +505,7 @@ func TestDuplicateAlias(t *testing.T) {
}
assert.NoError(t, e.Setup())
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "x"}))
assert.Equal(t, string(data), buff.String())
assert.Equal(t, "", buff.String())
}
func TestAliasSummary(t *testing.T) {
@ -609,7 +606,7 @@ func TestNoLabelInList(t *testing.T) {
Stderr: &buff,
}
assert.NoError(t, e.Setup())
e.ListTasksWithDesc()
e.ListTasks(task.FilterOutInternal(), task.FilterOutNoDesc())
assert.Contains(t, buff.String(), "foo")
}
@ -627,7 +624,7 @@ func TestListAllShowsNoDesc(t *testing.T) {
assert.NoError(t, e.Setup())
var title string
e.ListAllTasks()
e.ListTasks(task.FilterOutInternal())
for _, title = range []string{
"foo",
"voo",
@ -649,7 +646,7 @@ func TestListCanListDescOnly(t *testing.T) {
}
assert.NoError(t, e.Setup())
e.ListTasksWithDesc()
e.ListTasks(task.FilterOutInternal(), task.FilterOutNoDesc())
var title string
assert.Contains(t, buff.String(), "foo")
@ -1037,12 +1034,7 @@ func TestIncludesInternal(t *testing.T) {
}{
{"included internal task via task", "task-1", false, "Hello, World!\n"},
{"included internal task via dep", "task-2", false, "Hello, World!\n"},
{
"included internal direct",
"included:task-3",
true,
"task: No tasks with description available. Try --list-all to list all tasks\n",
},
{"included internal direct", "included:task-3", true, ""},
}
for _, test := range tests {
@ -1077,12 +1069,7 @@ func TestInternalTask(t *testing.T) {
}{
{"internal task via task", "task-1", false, "Hello, World!\n"},
{"internal task via dep", "task-2", false, "Hello, World!\n"},
{
"internal direct",
"task-3",
true,
"task: No tasks with description available. Try --list-all to list all tasks\n",
},
{"internal direct", "task-3", true, ""},
}
for _, test := range tests {

View File

@ -1 +0,0 @@
task: No tasks with description available. Try --list-all to list all tasks