Skip to content

Commit

Permalink
refactor: implement task list filtering
Browse files Browse the repository at this point in the history
  • Loading branch information
pd93 committed Nov 2, 2022
1 parent 3a0c7a8 commit fa105a8
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 75 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
8 changes: 6 additions & 2 deletions cmd/task/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
58 changes: 7 additions & 51 deletions help.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:")

Expand All @@ -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.
Expand Down
76 changes: 74 additions & 2 deletions task.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
"sync"
"sync/atomic"

Expand Down Expand Up @@ -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}
}
}
Expand Down Expand Up @@ -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
})
}
25 changes: 6 additions & 19 deletions task_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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) {
Expand Down Expand Up @@ -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")
}

Expand All @@ -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",
Expand All @@ -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")
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 0 additions & 1 deletion testdata/alias/alias-duplicate.txt

This file was deleted.

0 comments on commit fa105a8

Please sign in to comment.