mirror of
https://github.com/go-task/task.git
synced 2025-03-19 21:17:46 +02:00
commit
6d90c781c9
@ -2,6 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Add ability to set `aliases` for tasks and namespaces ([#268](https://github.com/go-task/task/pull/268), [#340](https://github.com/go-task/task/pull/340), [#879](https://github.com/go-task/task/pull/879)).
|
||||
- Improvements to Fish shell completion
|
||||
([#897](https://github.com/go-task/task/pull/897)).
|
||||
- Added ability to set a different watch interval by setting
|
||||
|
@ -2,6 +2,7 @@ version: '3'
|
||||
|
||||
includes:
|
||||
docs:
|
||||
aliases: [d]
|
||||
taskfile: ./docs
|
||||
dir: ./docs
|
||||
|
||||
@ -23,6 +24,7 @@ tasks:
|
||||
|
||||
install:
|
||||
desc: Installs Task
|
||||
aliases: [i]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
cmds:
|
||||
@ -42,6 +44,7 @@ tasks:
|
||||
|
||||
lint:
|
||||
desc: Runs golangci-lint
|
||||
aliases: [l]
|
||||
sources:
|
||||
- './**/*.go'
|
||||
cmds:
|
||||
@ -65,6 +68,7 @@ tasks:
|
||||
|
||||
test:
|
||||
desc: Runs test suite
|
||||
aliases: [t]
|
||||
deps: [install]
|
||||
cmds:
|
||||
- go test {{catLines .GO_PACKAGES}}
|
||||
|
@ -8,6 +8,7 @@ tasks:
|
||||
|
||||
start:
|
||||
desc: Start website
|
||||
aliases: [s]
|
||||
vars:
|
||||
HOST: '{{default "localhost" .HOST}}'
|
||||
PORT: '{{default "3001" .PORT}}'
|
||||
@ -25,6 +26,7 @@ tasks:
|
||||
- rm -rf ./build
|
||||
|
||||
deploy:
|
||||
desc: Build and deploy Docusaurus. Requires GIT_USER and GIT_PASS envs to be previous set
|
||||
desc: Build and deploy Docusaurus
|
||||
summary: Requires GIT_USER and GIT_PASS envs to be previous set
|
||||
cmds:
|
||||
- npx docusaurus deploy
|
||||
|
@ -91,7 +91,6 @@ Some environment variables can be overriden to adjust Task behavior.
|
||||
| `dotenv` | `[]string` | | A list of `.env` file paths to be parsed. |
|
||||
| `tasks` | [`map[string]Task`](#task) | | The task definitions. |
|
||||
|
||||
|
||||
### Include
|
||||
|
||||
| Attribute | Type | Default | Description |
|
||||
@ -99,7 +98,8 @@ Some environment variables can be overriden to adjust Task behavior.
|
||||
| `taskfile` | `string` | | The path for the Taskfile or directory to be included. If a directory, Task will look for files named `Taskfile.yml` or `Taskfile.yaml` inside that directory. If a relative path, resolved relative to the directory containing the including Taskfile. |
|
||||
| `dir` | `string` | The parent Taskfile directory | The working directory of the included tasks when run. |
|
||||
| `optional` | `bool` | `false` | If `true`, no errors will be thrown if the specified file does not exist. |
|
||||
| `internal` | `bool` | `false` | If `true`, tasks will be omitted from both `--list` and `--list-all`. |
|
||||
| `internal` | `bool` | `false` | If `true`, tasks will not be callable from the command line and will be omitted from both `--list` and `--list-all`. |
|
||||
| `aliases` | `[]string` | | Alternative names for the namespace of the included Taskfile. |
|
||||
|
||||
:::info
|
||||
|
||||
@ -118,11 +118,13 @@ includes:
|
||||
| - | - | - | - |
|
||||
| `desc` | `string` | | A short description of the task. This is listed when calling `task --list`. |
|
||||
| `summary` | `string` | | A longer description of the task. This is listed when calling `task --summary [task]`. |
|
||||
| `aliases` | `[]string` | | Alternative names for the task. |
|
||||
| `label` | `string` | | Overrides the name of the task in the output when a task is run. Supports variables. |
|
||||
| `sources` | `[]string` | | List of sources to check before running this task. Relevant for `checksum` and `timestamp` methods. Can be file paths or star globs. |
|
||||
| `dir` | `string` | | The current directory which this task should run. |
|
||||
| `method` | `string` | `checksum` | Method used by this task. Default to the one declared globally or `checksum`. Available options: `checksum`, `timestamp` and `none` |
|
||||
| `silent` | `bool` | `false` | Skips some output for this task. Note that STDOUT and STDERR of the commands will still be redirected. |
|
||||
| `internal` | `bool` | `false` | If `true`, omit this task from both `--list` and `--list-all`. |
|
||||
| `internal` | `bool` | `false` | If `true`, this task will not be callable from the command line and will be omitted from both `--list` and `--list-all`. |
|
||||
| `run` | `string` | The one declared globally in the Taskfile or `always` | Specifies whether the task should run again or not if called more than once. Available options: `always`, `once` and `when_changed`. |
|
||||
| `prefix` | `string` | | Allows to override the prefix print before the STDOUT. Only relevant when using the `prefixed` output mode. |
|
||||
| `ignore_error` | `bool` | `false` | Continue execution if errors happen while executing the commands. |
|
||||
|
@ -230,6 +230,21 @@ includes:
|
||||
DOCKER_IMAGE: frontend_image
|
||||
```
|
||||
|
||||
### Namespace aliases
|
||||
|
||||
When including a Taskfile, you can give the namespace a list of `aliases`.
|
||||
This works in the same way as [task aliases](#task-aliases) and can be used
|
||||
together to create shorter and easier-to-type commands.
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
generate:
|
||||
taskfile: ./taskfiles/Generate.yml
|
||||
aliases: [gen]
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
Vars declared in the included Taskfile have preference over the
|
||||
@ -965,6 +980,30 @@ If the task does not have a summary or a description, a warning is printed.
|
||||
|
||||
Please note: *showing the summary will not execute the command*.
|
||||
|
||||
## Task aliases
|
||||
|
||||
Aliases are alternative names for tasks. They can be used to make it easier and
|
||||
quicker to run tasks with long or hard-to-type names. You can use them on the
|
||||
command line, when [calling sub-tasks](#calling-another-task) in your Taskfile
|
||||
and when [including tasks](#including-other-taskfiles) with aliases from another
|
||||
Taskfile. They can also be used together with [namespace
|
||||
aliases](#namespace-aliases).
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
generate:
|
||||
aliases: [gen]
|
||||
cmds:
|
||||
- task: gen-mocks
|
||||
|
||||
generate-mocks:
|
||||
aliases: [gen-mocks]
|
||||
cmds:
|
||||
- echo "generating..."
|
||||
```
|
||||
|
||||
## Overriding task name
|
||||
|
||||
Sometimes you may want to override the task name printed on the summary, up-to-date
|
||||
|
10
errors.go
10
errors.go
@ -3,6 +3,7 @@ package task
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"mvdan.cc/sh/v3/interp"
|
||||
)
|
||||
@ -20,6 +21,15 @@ func (err *taskNotFoundError) Error() string {
|
||||
return fmt.Sprintf(`task: Task %q not found`, err.taskName)
|
||||
}
|
||||
|
||||
type multipleTasksWithAliasError struct {
|
||||
aliasName string
|
||||
taskNames []string
|
||||
}
|
||||
|
||||
func (err *multipleTasksWithAliasError) Error() string {
|
||||
return fmt.Sprintf(`task: Multiple tasks (%s) with alias %q found`, strings.Join(err.taskNames, ", "), err.aliasName)
|
||||
}
|
||||
|
||||
type taskInternalError struct {
|
||||
taskName string
|
||||
}
|
||||
|
3
go.mod
3
go.mod
@ -9,6 +9,7 @@ require (
|
||||
github.com/radovskyb/watcher v1.0.7
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.8.0
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
mvdan.cc/sh/v3 v3.6.0-0.dev.0.20220704111049-a6e3029cd899
|
||||
@ -19,7 +20,7 @@ require (
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||
)
|
||||
|
9
go.sum
9
go.sum
@ -7,7 +7,7 @@ github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYF
|
||||
github.com/frankban/quicktest v1.14.0 h1:+cqqvzZV87b4adx/5ayVOaYZ2CrvM4ejQvUdBzPPUss=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
@ -34,16 +34,17 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 h1:RjggHMcaTVp0LOVZcW0bo8alwHrOaCrGUDgfWUHhnN4=
|
||||
golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
3
help.go
3
help.go
@ -47,6 +47,9 @@ func (e *Executor) printTasks(listAll bool) {
|
||||
e.Logger.FOutf(w, logger.Yellow, "* ")
|
||||
e.Logger.FOutf(w, logger.Green, task.Task)
|
||||
e.Logger.FOutf(w, logger.Default, ": \t%s", task.Desc)
|
||||
if len(task.Aliases) > 0 {
|
||||
e.Logger.FOutf(w, logger.Cyan, "\t(aliases: %s)", strings.Join(task.Aliases, ", "))
|
||||
}
|
||||
fmt.Fprint(w, "\n")
|
||||
}
|
||||
w.Flush()
|
||||
|
@ -28,6 +28,7 @@ func PrintTask(l *logger.Logger, t *taskfile.Task) {
|
||||
printTaskName(l, t)
|
||||
printTaskDescribingText(t, l)
|
||||
printTaskDependencies(l, t)
|
||||
printTaskAliases(l, t)
|
||||
printTaskCommands(l, t)
|
||||
}
|
||||
|
||||
@ -61,6 +62,18 @@ func printTaskName(l *logger.Logger, t *taskfile.Task) {
|
||||
l.Outf(logger.Default, "")
|
||||
}
|
||||
|
||||
func printTaskAliases(l *logger.Logger, t *taskfile.Task) {
|
||||
if len(t.Aliases) == 0 {
|
||||
return
|
||||
}
|
||||
l.Outf(logger.Default, "")
|
||||
l.Outf(logger.Default, "aliases:")
|
||||
for _, alias := range t.Aliases {
|
||||
l.FOutf(l.Stdout, logger.Default, " - ")
|
||||
l.Outf(logger.Cyan, alias)
|
||||
}
|
||||
}
|
||||
|
||||
func hasDescription(t *taskfile.Task) bool {
|
||||
return t.Desc != ""
|
||||
}
|
||||
|
53
task.go
53
task.go
@ -16,6 +16,7 @@ import (
|
||||
"github.com/go-task/task/v3/internal/templater"
|
||||
"github.com/go-task/task/v3/taskfile"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
@ -64,16 +65,15 @@ type Executor struct {
|
||||
// Run runs Task
|
||||
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
|
||||
// check if given tasks exist
|
||||
for _, c := range calls {
|
||||
t, ok := e.Taskfile.Tasks[c.Task]
|
||||
if !ok {
|
||||
// FIXME: move to the main package
|
||||
for _, call := range calls {
|
||||
task, err := e.GetTask(call)
|
||||
if err != nil {
|
||||
e.ListTasksWithDesc()
|
||||
return &taskNotFoundError{taskName: c.Task}
|
||||
return err
|
||||
}
|
||||
if t.Internal {
|
||||
if task.Internal {
|
||||
e.ListTasksWithDesc()
|
||||
return &taskInternalError{taskName: c.Task}
|
||||
return &taskInternalError{taskName: call.Task}
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,8 +113,8 @@ func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !e.Watch && atomic.AddInt32(e.taskCallCount[call.Task], 1) >= MaximumTaskCall {
|
||||
return &MaximumTaskCallExceededError{task: call.Task}
|
||||
if !e.Watch && atomic.AddInt32(e.taskCallCount[t.Task], 1) >= MaximumTaskCall {
|
||||
return &MaximumTaskCallExceededError{task: t.Task}
|
||||
}
|
||||
|
||||
release := e.acquireConcurrencyLimit()
|
||||
@ -331,3 +331,38 @@ func (e *Executor) startExecution(ctx context.Context, t *taskfile.Task, execute
|
||||
|
||||
return execute(ctx)
|
||||
}
|
||||
|
||||
// GetTask will return the task with the name matching the given call from the taskfile.
|
||||
// If no task is found, it will search for tasks with a matching alias.
|
||||
// If multiple tasks contain the same alias or no matches are found an error is returned.
|
||||
func (e *Executor) GetTask(call taskfile.Call) (*taskfile.Task, error) {
|
||||
// Search for a matching task
|
||||
matchingTask, ok := e.Taskfile.Tasks[call.Task]
|
||||
if ok {
|
||||
return matchingTask, nil
|
||||
}
|
||||
|
||||
// If didn't find one, search for a task with a matching alias
|
||||
var aliasedTasks []string
|
||||
for _, task := range e.Taskfile.Tasks {
|
||||
if slices.Contains(task.Aliases, call.Task) {
|
||||
aliasedTasks = append(aliasedTasks, task.Task)
|
||||
matchingTask = task
|
||||
}
|
||||
}
|
||||
// If we found multiple tasks
|
||||
if len(aliasedTasks) > 1 {
|
||||
return nil, &multipleTasksWithAliasError{
|
||||
aliasName: call.Task,
|
||||
taskNames: aliasedTasks,
|
||||
}
|
||||
}
|
||||
// If we found no tasks
|
||||
if len(aliasedTasks) == 0 {
|
||||
return nil, &taskNotFoundError{
|
||||
taskName: call.Task,
|
||||
}
|
||||
}
|
||||
|
||||
return matchingTask, nil
|
||||
}
|
||||
|
56
task_test.go
56
task_test.go
@ -477,6 +477,58 @@ func TestStatusChecksum(t *testing.T) {
|
||||
assert.Equal(t, `task: Task "build" is up to date`+"\n", buff.String())
|
||||
}
|
||||
|
||||
func TestAlias(t *testing.T) {
|
||||
const dir = "testdata/alias"
|
||||
|
||||
data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "f"}))
|
||||
assert.Equal(t, string(data), buff.String())
|
||||
}
|
||||
|
||||
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,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.Error(t, e.Run(context.Background(), taskfile.Call{Task: "x"}))
|
||||
assert.Equal(t, string(data), buff.String())
|
||||
}
|
||||
|
||||
func TestAliasSummary(t *testing.T) {
|
||||
const dir = "testdata/alias"
|
||||
|
||||
data, err := os.ReadFile(filepathext.SmartJoin(dir, "alias-summary.txt"))
|
||||
assert.NoError(t, err)
|
||||
|
||||
var buff bytes.Buffer
|
||||
e := task.Executor{
|
||||
Dir: dir,
|
||||
Summary: true,
|
||||
Stdout: &buff,
|
||||
Stderr: &buff,
|
||||
}
|
||||
assert.NoError(t, e.Setup())
|
||||
assert.NoError(t, e.Run(context.Background(), taskfile.Call{Task: "f"}))
|
||||
assert.Equal(t, string(data), buff.String())
|
||||
}
|
||||
|
||||
func TestLabelUpToDate(t *testing.T) {
|
||||
const dir = "testdata/label_uptodate"
|
||||
|
||||
@ -990,7 +1042,7 @@ func TestIncludesInternal(t *testing.T) {
|
||||
|
||||
err := e.Run(context.Background(), taskfile.Call{Task: test.task})
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err, test.expectedErr)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@ -1030,7 +1082,7 @@ func TestInternalTask(t *testing.T) {
|
||||
|
||||
err := e.Run(context.Background(), taskfile.Call{Task: test.task})
|
||||
if test.expectedErr {
|
||||
assert.Error(t, err, test.expectedErr)
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
23
taskfile/copy.go
Normal file
23
taskfile/copy.go
Normal file
@ -0,0 +1,23 @@
|
||||
package taskfile
|
||||
|
||||
import "golang.org/x/exp/constraints"
|
||||
|
||||
func deepCopySlice[T any](orig []T) []T {
|
||||
if orig == nil {
|
||||
return nil
|
||||
}
|
||||
c := make([]T, len(orig))
|
||||
copy(c, orig)
|
||||
return c
|
||||
}
|
||||
|
||||
func deepCopyMap[K constraints.Ordered, V any](orig map[K]V) map[K]V {
|
||||
if orig == nil {
|
||||
return nil
|
||||
}
|
||||
c := make(map[K]V, len(orig))
|
||||
for k, v := range orig {
|
||||
c[k] = v
|
||||
}
|
||||
return c
|
||||
}
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/go-task/task/v3/internal/execext"
|
||||
"github.com/go-task/task/v3/internal/filepathext"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@ -17,6 +18,7 @@ type IncludedTaskfile struct {
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Aliases []string
|
||||
AdvancedImport bool
|
||||
Vars *Vars
|
||||
BaseDir string // The directory from which the including taskfile was loaded; used to resolve relative paths
|
||||
@ -71,7 +73,7 @@ func (tfs *IncludedTaskfiles) Set(key string, includedTaskfile IncludedTaskfile)
|
||||
if tfs.Mapping == nil {
|
||||
tfs.Mapping = make(map[string]IncludedTaskfile, 1)
|
||||
}
|
||||
if !stringSliceContains(tfs.Keys, key) {
|
||||
if !slices.Contains(tfs.Keys, key) {
|
||||
tfs.Keys = append(tfs.Keys, key)
|
||||
}
|
||||
tfs.Mapping[key] = includedTaskfile
|
||||
@ -103,6 +105,7 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err
|
||||
Dir string
|
||||
Optional bool
|
||||
Internal bool
|
||||
Aliases []string
|
||||
Vars *Vars
|
||||
}
|
||||
if err := unmarshal(&includedTaskfile); err != nil {
|
||||
@ -112,11 +115,29 @@ func (it *IncludedTaskfile) UnmarshalYAML(unmarshal func(interface{}) error) err
|
||||
it.Dir = includedTaskfile.Dir
|
||||
it.Optional = includedTaskfile.Optional
|
||||
it.Internal = includedTaskfile.Internal
|
||||
it.Aliases = includedTaskfile.Aliases
|
||||
it.AdvancedImport = true
|
||||
it.Vars = includedTaskfile.Vars
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of IncludedTaskfile and copies
|
||||
// data by value from the source struct.
|
||||
func (it *IncludedTaskfile) DeepCopy() *IncludedTaskfile {
|
||||
if it == nil {
|
||||
return nil
|
||||
}
|
||||
return &IncludedTaskfile{
|
||||
Taskfile: it.Taskfile,
|
||||
Dir: it.Dir,
|
||||
Optional: it.Optional,
|
||||
Internal: it.Internal,
|
||||
AdvancedImport: it.AdvancedImport,
|
||||
Vars: it.Vars.DeepCopy(),
|
||||
BaseDir: it.BaseDir,
|
||||
}
|
||||
}
|
||||
|
||||
// FullTaskfilePath returns the fully qualified path to the included taskfile
|
||||
func (it *IncludedTaskfile) FullTaskfilePath() (string, error) {
|
||||
return it.resolvePath(it.Taskfile)
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
const NamespaceSeparator = ":"
|
||||
|
||||
// Merge merges the second Taskfile into the first
|
||||
func Merge(t1, t2 *Taskfile, internal bool, namespaces ...string) error {
|
||||
func Merge(t1, t2 *Taskfile, includedTaskfile *IncludedTaskfile, namespaces ...string) error {
|
||||
if t1.Version != t2.Version {
|
||||
return fmt.Errorf(`task: Taskfiles versions should match. First is "%s" but second is "%s"`, t1.Version, t2.Version)
|
||||
}
|
||||
@ -39,22 +39,38 @@ func Merge(t1, t2 *Taskfile, internal bool, namespaces ...string) error {
|
||||
t1.Tasks = make(Tasks)
|
||||
}
|
||||
for k, v := range t2.Tasks {
|
||||
// FIXME(@andreynering): Refactor this block, otherwise we can
|
||||
// have serious side-effects in the future, since we're editing
|
||||
// the original references instead of deep copying them.
|
||||
// We do a deep copy of the task struct here to ensure that no data can
|
||||
// be changed elsewhere once the taskfile is merged.
|
||||
task := v.DeepCopy()
|
||||
|
||||
v.Internal = v.Internal || internal
|
||||
// Set the task to internal if EITHER the included task or the included
|
||||
// taskfile are marked as internal
|
||||
task.Internal = task.Internal || includedTaskfile.Internal
|
||||
|
||||
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = v
|
||||
|
||||
for _, dep := range v.Deps {
|
||||
// Add namespaces to dependencies, commands and aliases
|
||||
for _, dep := range task.Deps {
|
||||
dep.Task = taskNameWithNamespace(dep.Task, namespaces...)
|
||||
}
|
||||
for _, cmd := range v.Cmds {
|
||||
for _, cmd := range task.Cmds {
|
||||
if cmd != nil && cmd.Task != "" {
|
||||
cmd.Task = taskNameWithNamespace(cmd.Task, namespaces...)
|
||||
}
|
||||
}
|
||||
for i, alias := range task.Aliases {
|
||||
task.Aliases[i] = taskNameWithNamespace(alias, namespaces...)
|
||||
}
|
||||
// Add namespace aliases
|
||||
if includedTaskfile != nil {
|
||||
for _, namespaceAlias := range includedTaskfile.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(task.Task, namespaceAlias))
|
||||
for _, alias := range v.Aliases {
|
||||
task.Aliases = append(task.Aliases, taskNameWithNamespace(alias, namespaceAlias))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the task to the merged taskfile
|
||||
t1.Tasks[taskNameWithNamespace(k, namespaces...)] = task
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -79,6 +79,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
Dir: tr.Replace(includedTask.Dir),
|
||||
Optional: includedTask.Optional,
|
||||
Internal: includedTask.Internal,
|
||||
Aliases: includedTask.Aliases,
|
||||
AdvancedImport: includedTask.AdvancedImport,
|
||||
Vars: includedTask.Vars,
|
||||
BaseDir: includedTask.BaseDir,
|
||||
@ -149,7 +150,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err = taskfile.Merge(t, includedTaskfile, includedTask.Internal, namespace); err != nil {
|
||||
if err = taskfile.Merge(t, includedTaskfile, &includedTask, namespace); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -165,7 +166,7 @@ func Taskfile(readerNode *ReaderNode) (*taskfile.Taskfile, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = taskfile.Merge(t, osTaskfile, false); err != nil {
|
||||
if err = taskfile.Merge(t, osTaskfile, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
package taskfile
|
||||
|
||||
func stringSliceContains(s []string, str string) bool {
|
||||
for _, v := range s {
|
||||
if v == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
@ -11,6 +11,7 @@ type Task struct {
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Aliases []string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
@ -56,6 +57,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
Label string
|
||||
Desc string
|
||||
Summary string
|
||||
Aliases []string
|
||||
Sources []string
|
||||
Generates []string
|
||||
Status []string
|
||||
@ -78,6 +80,7 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
t.Deps = task.Deps
|
||||
t.Label = task.Label
|
||||
t.Desc = task.Desc
|
||||
t.Aliases = task.Aliases
|
||||
t.Summary = task.Summary
|
||||
t.Sources = task.Sources
|
||||
t.Generates = task.Generates
|
||||
@ -95,3 +98,35 @@ func (t *Task) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||
t.Run = task.Run
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of Task and copies
|
||||
// data by value from the source struct.
|
||||
func (t *Task) DeepCopy() *Task {
|
||||
c := &Task{
|
||||
Task: t.Task,
|
||||
Cmds: deepCopySlice(t.Cmds),
|
||||
Deps: deepCopySlice(t.Deps),
|
||||
Label: t.Label,
|
||||
Desc: t.Desc,
|
||||
Summary: t.Summary,
|
||||
Aliases: deepCopySlice(t.Aliases),
|
||||
Sources: deepCopySlice(t.Sources),
|
||||
Generates: deepCopySlice(t.Generates),
|
||||
Status: deepCopySlice(t.Status),
|
||||
Preconditions: deepCopySlice(t.Preconditions),
|
||||
Dir: t.Dir,
|
||||
Vars: t.Vars.DeepCopy(),
|
||||
Env: t.Env.DeepCopy(),
|
||||
Silent: t.Silent,
|
||||
Interactive: t.Interactive,
|
||||
Internal: t.Internal,
|
||||
Method: t.Method,
|
||||
Prefix: t.Prefix,
|
||||
IgnoreError: t.IgnoreError,
|
||||
Run: t.Run,
|
||||
IncludeVars: t.IncludeVars.DeepCopy(),
|
||||
IncludedTaskfileVars: t.IncludedTaskfileVars.DeepCopy(),
|
||||
IncludedTaskfile: t.IncludedTaskfile.DeepCopy(),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package taskfile
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
@ -34,6 +35,18 @@ func (vs *Vars) UnmarshalYAML(node *yaml.Node) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopy creates a new instance of Vars and copies
|
||||
// data by value from the source struct.
|
||||
func (vs *Vars) DeepCopy() *Vars {
|
||||
if vs == nil {
|
||||
return nil
|
||||
}
|
||||
return &Vars{
|
||||
Keys: deepCopySlice(vs.Keys),
|
||||
Mapping: deepCopyMap(vs.Mapping),
|
||||
}
|
||||
}
|
||||
|
||||
// Merge merges the given Vars into the caller one
|
||||
func (vs *Vars) Merge(other *Vars) {
|
||||
_ = other.Range(func(key string, value Var) error {
|
||||
@ -47,7 +60,7 @@ func (vs *Vars) Set(key string, value Var) {
|
||||
if vs.Mapping == nil {
|
||||
vs.Mapping = make(map[string]Var, 1)
|
||||
}
|
||||
if !stringSliceContains(vs.Keys, key) {
|
||||
if !slices.Contains(vs.Keys, key) {
|
||||
vs.Keys = append(vs.Keys, key)
|
||||
}
|
||||
vs.Mapping[key] = value
|
||||
|
19
testdata/alias/Taskfile.yml
vendored
Normal file
19
testdata/alias/Taskfile.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
version: '3'
|
||||
|
||||
includes:
|
||||
included:
|
||||
taskfile: Taskfile2.yml
|
||||
aliases: [inc, i]
|
||||
|
||||
tasks:
|
||||
foo:
|
||||
aliases: [f, x]
|
||||
cmds:
|
||||
- echo "foo"
|
||||
- task: b
|
||||
|
||||
bar:
|
||||
aliases: [b, x]
|
||||
cmds:
|
||||
- echo "bar"
|
||||
- task: inc:q
|
7
testdata/alias/Taskfile2.yml
vendored
Normal file
7
testdata/alias/Taskfile2.yml
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
version: '3'
|
||||
|
||||
tasks:
|
||||
qux:
|
||||
aliases: [q, x]
|
||||
cmds:
|
||||
- echo "qux"
|
1
testdata/alias/alias-duplicate.txt
vendored
Normal file
1
testdata/alias/alias-duplicate.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
task: No tasks with description available. Try --list-all to list all tasks
|
11
testdata/alias/alias-summary.txt
vendored
Normal file
11
testdata/alias/alias-summary.txt
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
task: foo
|
||||
|
||||
(task does not have description or summary)
|
||||
|
||||
aliases:
|
||||
- f
|
||||
- x
|
||||
|
||||
commands:
|
||||
- echo "foo"
|
||||
- Task: b
|
6
testdata/alias/alias.txt
vendored
Normal file
6
testdata/alias/alias.txt
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
task: [foo] echo "foo"
|
||||
foo
|
||||
task: [bar] echo "bar"
|
||||
bar
|
||||
task: [included:qux] echo "qux"
|
||||
qux
|
@ -22,13 +22,12 @@ func (e *Executor) FastCompiledTask(call taskfile.Call) (*taskfile.Task, error)
|
||||
}
|
||||
|
||||
func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskfile.Task, error) {
|
||||
origTask, ok := e.Taskfile.Tasks[call.Task]
|
||||
if !ok {
|
||||
return nil, &taskNotFoundError{call.Task}
|
||||
origTask, err := e.GetTask(call)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var vars *taskfile.Vars
|
||||
var err error
|
||||
if evaluateShVars {
|
||||
vars, err = e.Compiler.GetVariables(origTask, call)
|
||||
} else {
|
||||
@ -50,6 +49,7 @@ func (e *Executor) compiledTask(call taskfile.Call, evaluateShVars bool) (*taskf
|
||||
Label: r.Replace(origTask.Label),
|
||||
Desc: r.Replace(origTask.Desc),
|
||||
Summary: r.Replace(origTask.Summary),
|
||||
Aliases: origTask.Aliases,
|
||||
Sources: r.ReplaceSlice(origTask.Sources),
|
||||
Generates: r.ReplaceSlice(origTask.Generates),
|
||||
Dir: r.Replace(origTask.Dir),
|
||||
|
Loading…
x
Reference in New Issue
Block a user