diff --git a/task_test.go b/task_test.go index b651a14f..940a8a27 100644 --- a/task_test.go +++ b/task_test.go @@ -1717,6 +1717,25 @@ func TestIncludesWithExclude(t *testing.T) { require.Error(t, err) buff.Reset() + err = e.Run(t.Context(), &task.Call{Task: "included:foo:child"}) + require.NoError(t, err) + assert.Equal(t, "foo:child\n", buff.String()) + buff.Reset() + + err = e.Run(t.Context(), &task.Call{Task: "included:namespace"}) + require.NoError(t, err) + assert.Equal(t, "namespace\n", buff.String()) + buff.Reset() + + err = e.Run(t.Context(), &task.Call{Task: "included:namespace:one"}) + require.Error(t, err) + buff.Reset() + + err = e.Run(t.Context(), &task.Call{Task: "included:namespace-other:one"}) + require.NoError(t, err) + assert.Equal(t, "namespace-other:one\n", buff.String()) + buff.Reset() + err = e.Run(t.Context(), &task.Call{Task: "bar"}) require.Error(t, err) buff.Reset() @@ -1724,6 +1743,20 @@ func TestIncludesWithExclude(t *testing.T) { err = e.Run(t.Context(), &task.Call{Task: "foo"}) require.NoError(t, err) assert.Equal(t, "foo\n", buff.String()) + buff.Reset() + + err = e.Run(t.Context(), &task.Call{Task: "namespace"}) + require.NoError(t, err) + assert.Equal(t, "namespace\n", buff.String()) + buff.Reset() + + err = e.Run(t.Context(), &task.Call{Task: "namespace:two"}) + require.Error(t, err) + buff.Reset() + + err = e.Run(t.Context(), &task.Call{Task: "namespace-other:one"}) + require.NoError(t, err) + assert.Equal(t, "namespace-other:one\n", buff.String()) } func TestIncludedTaskfileVarMerging(t *testing.T) { diff --git a/taskfile/ast/include.go b/taskfile/ast/include.go index 64432c19..9bb26e0b 100644 --- a/taskfile/ast/include.go +++ b/taskfile/ast/include.go @@ -37,6 +37,27 @@ type ( IncludeElement orderedmap.Element[string, *Include] ) +func (include *Include) isTaskExcluded(name string) bool { + if include == nil { + return false + } + + for _, exclude := range include.Excludes { + namespace, ok := strings.CutSuffix(exclude, NamespaceSeparator+"*") + if ok { + if namespace != "" && strings.HasPrefix(name, namespace+NamespaceSeparator) { + return true + } + continue + } + + if name == exclude { + return true + } + } + return false +} + // NewIncludes creates a new instance of Includes and initializes it with the // provided set of elements, if any. The elements are added in the order they // are passed. diff --git a/taskfile/ast/tasks.go b/taskfile/ast/tasks.go index 62aa53a6..6ce298fd 100644 --- a/taskfile/ast/tasks.go +++ b/taskfile/ast/tasks.go @@ -130,8 +130,9 @@ func (t1 *Tasks) Merge(t2 *Tasks, include *Include, includedTaskfileVars *Vars) task.Internal = task.Internal || (include != nil && include.Internal) taskName := name - // if the task is in the exclude list, don't add it to the merged taskfile - if slices.Contains(include.Excludes, name) { + // If the task or its namespace is in the exclude list, don't add it to + // the merged taskfile. + if include.isTaskExcluded(name) { continue } diff --git a/testdata/includes_with_excludes/Taskfile.yml b/testdata/includes_with_excludes/Taskfile.yml index 6548aab5..5c39969f 100644 --- a/testdata/includes_with_excludes/Taskfile.yml +++ b/testdata/includes_with_excludes/Taskfile.yml @@ -4,12 +4,14 @@ includes: included: taskfile: ./included/Taskfile.yml excludes: - - foo + - foo + - namespace:* included_flatten: taskfile: ./included/Taskfile.yml flatten: true excludes: - bar + - namespace:* tasks: default: diff --git a/testdata/includes_with_excludes/included/Taskfile.yml b/testdata/includes_with_excludes/included/Taskfile.yml index 6de33b01..6934cf5b 100644 --- a/testdata/includes_with_excludes/included/Taskfile.yml +++ b/testdata/includes_with_excludes/included/Taskfile.yml @@ -2,4 +2,9 @@ version: '3' tasks: foo: echo foo + foo:child: echo foo:child bar: echo bar + namespace: echo namespace + namespace:one: echo namespace:one + namespace:two: echo namespace:two + namespace-other:one: echo namespace-other:one diff --git a/website/src/docs/guide.md b/website/src/docs/guide.md index d5ca4622..a78b26d4 100644 --- a/website/src/docs/guide.md +++ b/website/src/docs/guide.md @@ -412,8 +412,10 @@ You can do this by using the ### Exclude tasks from being included -You can exclude tasks from being included by using the `excludes` option. This -option takes the list of tasks to be excluded from this include. +You can exclude tasks or entire namespaces from being included by using the +`excludes` option. This option takes the list of tasks or namespaces to be +excluded from this include. Task names are matched exactly. To exclude a +namespace, append `:*` to its name. ::: code-group @@ -423,7 +425,7 @@ version: '3' includes: included: taskfile: ./Included.yml - excludes: [foo] + excludes: [foo, 'internal:*', 'debug:*'] ``` ```yaml [Included.yml] @@ -432,11 +434,14 @@ version: '3' tasks: foo: echo "Foo" bar: echo "Bar" + internal:setup: echo "Internal setup" + debug:status: echo "Debug status" ``` ::: -`task included:foo` will throw an error because the `foo` task is excluded but +`task included:foo`, `task included:internal:setup`, and +`task included:debug:status` will throw errors because they are excluded, but `task included:bar` will work and display `Bar`. It's compatible with the `flatten` option. diff --git a/website/src/docs/reference/schema.md b/website/src/docs/reference/schema.md index 475c1964..2d9e9e84 100644 --- a/website/src/docs/reference/schema.md +++ b/website/src/docs/reference/schema.md @@ -308,13 +308,14 @@ includes: ### `excludes` - **Type**: `[]string` -- **Description**: Tasks to exclude from inclusion +- **Description**: Task names or namespace patterns ending in `:*` to exclude + from inclusion ```yaml includes: shared: taskfile: ./shared.yml - excludes: [internal-setup, debug-only] + excludes: [internal-setup, 'debug:*', 'experimental:*'] ``` ### `vars` diff --git a/website/src/public/schema.json b/website/src/public/schema.json index df0637b7..e9b78843 100644 --- a/website/src/public/schema.json +++ b/website/src/public/schema.json @@ -746,7 +746,7 @@ } }, "excludes": { - "description": "A list of tasks to be excluded from inclusion.", + "description": "A list of task names or namespace patterns ending in `:*` to be excluded from inclusion.", "type": "array", "items": { "type": "string"