mirror of
				https://github.com/go-task/task.git
				synced 2025-10-30 23:58:01 +02:00 
			
		
		
		
	refactor: implement task list filtering
This commit is contained in:
		| @@ -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` | ||||
|   | ||||
| @@ -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
									
									
									
									
									
								
							
							
						
						
									
										58
									
								
								help.go
									
									
									
									
									
								
							| @@ -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
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								task.go
									
									
									
									
									
								
							| @@ -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 | ||||
| 	}) | ||||
| } | ||||
|   | ||||
							
								
								
									
										25
									
								
								task_test.go
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								task_test.go
									
									
									
									
									
								
							| @@ -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 { | ||||
|   | ||||
							
								
								
									
										1
									
								
								testdata/alias/alias-duplicate.txt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								testdata/alias/alias-duplicate.txt
									
									
									
									
										vendored
									
									
								
							| @@ -1 +0,0 @@ | ||||
| task: No tasks with description available. Try --list-all to list all tasks | ||||
		Reference in New Issue
	
	Block a user