From d76ff3a0e82f49e5d319db2a48e6e4307c7719f2 Mon Sep 17 00:00:00 2001 From: <> Date: Sun, 5 Jan 2025 19:05:45 +0000 Subject: [PATCH] Deployed 3633026 with MkDocs version: 1.3.1 --- CONTRIBUTING/index.html | 23 ++++++++++------------- index.html | 2 +- search/search_index.json | 2 +- sitemap.xml | 6 +++--- sitemap.xml.gz | Bin 196 -> 196 bytes 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/CONTRIBUTING/index.html b/CONTRIBUTING/index.html index 1884dd7..14fb0d2 100644 --- a/CONTRIBUTING/index.html +++ b/CONTRIBUTING/index.html @@ -114,16 +114,13 @@

Opening Pull Requests

For features which change the spec of the configuration file, documentation should be added in docs/spec.md.

Setting Up a Development Environment

-

For local development, you will need Go version 1.11+ installed.

-

Tusk uses go modules for dependency management, so make sure to clone the -project outside of the GOPATH. If that doesn't mean anything to you, you're -probably fine.

-
git clone https://github.com/rliebz/tusk.git
-
-

If it is not already on your path, you probably also want to have the GOPATH -binary directory available for projects installed by go get and go install. -To do so, add the following to your .bash_profile or .zshrc:

-
export PATH="$PATH:$(go env GOPATH)/bin"
+

For local development, you will need Go and golangci-lint installed. The +easiest way to do this is with mise, but it is not +required.

+

If it is not already on your path, you probably want to have the GOBIN +directory available for projects installed by go install. To do so, add the +following to your .bash_profile or .zshrc:

+
export PATH="$PATH:$(go env GOBIN)"
 

To install Tusk:

go install
@@ -132,7 +129,7 @@ 

Setting Up a Development Environme against the development version installed locally. If you do not get (devel) as the version, you may need to move your Go binary path earlier in your PATH:

-
$ tusk --version
+
$ tusk --version
 (devel)
 

Making Changes

@@ -145,14 +142,14 @@

Running Tests

To run the unit tests:

tusk test
 
-

To run the full test suite, along with the linter:

+

To run the full test suite, along with golangci-lint:

tusk test -a
 

If the linter fails, execution will stop short and not actually run the unit test suite. If there is a linter error that is a false-positive, or the violation is necessary for your contribution, you can disable a specific linter for that line:

-
cmd := exec.Command("sh", "-c", command) // nolint: gosec
+
cmd := exec.Command("sh", "-c", command) //nolint:gosec
 
diff --git a/index.html b/index.html index c4ec55a..6cdf72d 100644 --- a/index.html +++ b/index.html @@ -254,5 +254,5 @@

Contributing

diff --git a/search/search_index.json b/search/search_index.json index 8cdae2e..ab7bda7 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Tusk Tusk is a yaml-based task runner. By creating a tusk.yml in the root of a repository, Tusk becomes a custom command line tool with minimal configuration. Details on the usage and configuration options can be found in the project documentation . Features Customizable : Specify your own tasks and options with support for command-line flags, environment variables, conditional logic, and more. Explorable : All the help you need to get started is available straight from the command line. Help documentation is generated dynamically, and support for Bash and Zsh tab completion is available. Accessible : Built for usability with a simple YAML configuration, familiar syntax for passing options, Bash-like variable interpolation, and a colorful terminal output. Zero Dependencies : All you need is a single binary file to get started on Linux, macOS, or Windows. Getting Started Installation Go With Go 1.21+ installed: go install github.com/rliebz/tusk@latest Homebrew On macOS, installation is also available through homebrew : brew install rliebz/tusk/tusk With Homebrew, tab completion is installed automatically. Compiled Releases The latest version can be downloaded from the releases page . To install automatically: curl -sL https://git.io/tusk | bash -s -- -b /usr/local/bin latest To pin to a specific version, replace latest with the tag for that version. To install to another directory, change the path passed to -b . Installing Tab Completion For bash: tusk --install-completion bash For fish: tusk --install-completion fish For zsh: tusk --install-completion zsh Completions can be uninstalled with the --uninstall-completion flag. Usage Create a tusk.yml file in the root of a project repository: tasks: greet: usage: Say hello to someone options: name: usage: A person to say \"Hello\" to default: World run: echo \"Hello, ${name}!\" As long as there is a tusk.yml file in the working or any parent directory, tasks can be run: $ tusk greet --name friend Running: echo \"Hello, friend!\" Hello, friend! Help messages are dynamically generated based on the YAML configuration: $ tusk --help tusk - the modern task runner Usage: tusk [global options] [task options] Tasks: greet Say hello to someone Global Options: -f, --file Set file to use as the config file -h, --help Show help and exit ... Individual tasks have help messages as well: $ tusk greet --help tusk greet - Say hello to someone Usage: tusk greet [options] Options: --name A person to say \"Hello\" to Additional information on the configuration spec can be found in the project documentation . For more detailed examples, check out the project's own tusk.yml file. Contributing Set-up instructions for a development environment and contribution guidelines can be found in CONTRIBUTING.md .","title":"Tusk"},{"location":"#tusk","text":"Tusk is a yaml-based task runner. By creating a tusk.yml in the root of a repository, Tusk becomes a custom command line tool with minimal configuration. Details on the usage and configuration options can be found in the project documentation .","title":"Tusk"},{"location":"#features","text":"Customizable : Specify your own tasks and options with support for command-line flags, environment variables, conditional logic, and more. Explorable : All the help you need to get started is available straight from the command line. Help documentation is generated dynamically, and support for Bash and Zsh tab completion is available. Accessible : Built for usability with a simple YAML configuration, familiar syntax for passing options, Bash-like variable interpolation, and a colorful terminal output. Zero Dependencies : All you need is a single binary file to get started on Linux, macOS, or Windows.","title":"Features"},{"location":"#getting-started","text":"","title":"Getting Started"},{"location":"#installation","text":"","title":"Installation"},{"location":"#go","text":"With Go 1.21+ installed: go install github.com/rliebz/tusk@latest","title":"Go"},{"location":"#homebrew","text":"On macOS, installation is also available through homebrew : brew install rliebz/tusk/tusk With Homebrew, tab completion is installed automatically.","title":"Homebrew"},{"location":"#compiled-releases","text":"The latest version can be downloaded from the releases page . To install automatically: curl -sL https://git.io/tusk | bash -s -- -b /usr/local/bin latest To pin to a specific version, replace latest with the tag for that version. To install to another directory, change the path passed to -b .","title":"Compiled Releases"},{"location":"#installing-tab-completion","text":"For bash: tusk --install-completion bash For fish: tusk --install-completion fish For zsh: tusk --install-completion zsh Completions can be uninstalled with the --uninstall-completion flag.","title":"Installing Tab Completion"},{"location":"#usage","text":"Create a tusk.yml file in the root of a project repository: tasks: greet: usage: Say hello to someone options: name: usage: A person to say \"Hello\" to default: World run: echo \"Hello, ${name}!\" As long as there is a tusk.yml file in the working or any parent directory, tasks can be run: $ tusk greet --name friend Running: echo \"Hello, friend!\" Hello, friend! Help messages are dynamically generated based on the YAML configuration: $ tusk --help tusk - the modern task runner Usage: tusk [global options] [task options] Tasks: greet Say hello to someone Global Options: -f, --file Set file to use as the config file -h, --help Show help and exit ... Individual tasks have help messages as well: $ tusk greet --help tusk greet - Say hello to someone Usage: tusk greet [options] Options: --name A person to say \"Hello\" to Additional information on the configuration spec can be found in the project documentation . For more detailed examples, check out the project's own tusk.yml file.","title":"Usage"},{"location":"#contributing","text":"Set-up instructions for a development environment and contribution guidelines can be found in CONTRIBUTING.md .","title":"Contributing"},{"location":"CONTRIBUTING/","text":"Contributing All contributions are welcome and appreciated. Feel free to open issues or pull requests for any fixes, changes, or new features, and if you are not sure about anything, open it anyway. Issues and pull requests are a great forum for discussion and a great opportunity to help improve the code as a whole. Opening Issues A great way to contribute to the project is to open GitHub issues whenever you encounter any issue, or if you have an idea on how Tusk can improve. Missing or incorrect documentation are issues too, so feel free to open one whenever you feel there is a chance to make Tusk better. When reporting a bug, make sure to include the expected behavior and steps to reproduce. The more descriptive you can be, the faster the issue can be resolved. Opening Pull Requests Always feel free to open a pull request, whether it is a fix or a new addition. For big or breaking changes, you might consider opening an issue first to check interest, but it is absolutely not required to make a contribution. Tests are run automatically on each PR, and 100% test and lint pass rate is required to get the code merged in, although it is fine to have work-in- progress pull requests open while debugging. Details on how to run the test suite can be found here . For features which change the spec of the configuration file, documentation should be added in docs/spec.md . Setting Up a Development Environment For local development, you will need Go version 1.11+ installed. Tusk uses go modules for dependency management, so make sure to clone the project outside of the GOPATH . If that doesn't mean anything to you, you're probably fine. git clone https://github.com/rliebz/tusk.git If it is not already on your path, you probably also want to have the GOPATH binary directory available for projects installed by go get and go install . To do so, add the following to your .bash_profile or .zshrc : export PATH=\"$PATH:$(go env GOPATH)/bin\" To install Tusk: go install If you have already installed tusk from another source, make sure you test against the development version installed locally. If you do not get (devel) as the version, you may need to move your Go binary path earlier in your PATH : $ tusk --version (devel) Making Changes If you have not yet done so, make sure you fork the repository so you can push your changes back to your own fork on GitHub. When starting work on a new feature, create a new branch based off the main branch. Pull requests should generally target the main branch, and releases will be cut separately. Running Tests To run the unit tests: tusk test To run the full test suite, along with the linter: tusk test -a If the linter fails, execution will stop short and not actually run the unit test suite. If there is a linter error that is a false-positive, or the violation is necessary for your contribution, you can disable a specific linter for that line: cmd := exec.Command(\"sh\", \"-c\", command) // nolint: gosec","title":"Contributing"},{"location":"CONTRIBUTING/#contributing","text":"All contributions are welcome and appreciated. Feel free to open issues or pull requests for any fixes, changes, or new features, and if you are not sure about anything, open it anyway. Issues and pull requests are a great forum for discussion and a great opportunity to help improve the code as a whole.","title":"Contributing"},{"location":"CONTRIBUTING/#opening-issues","text":"A great way to contribute to the project is to open GitHub issues whenever you encounter any issue, or if you have an idea on how Tusk can improve. Missing or incorrect documentation are issues too, so feel free to open one whenever you feel there is a chance to make Tusk better. When reporting a bug, make sure to include the expected behavior and steps to reproduce. The more descriptive you can be, the faster the issue can be resolved.","title":"Opening Issues"},{"location":"CONTRIBUTING/#opening-pull-requests","text":"Always feel free to open a pull request, whether it is a fix or a new addition. For big or breaking changes, you might consider opening an issue first to check interest, but it is absolutely not required to make a contribution. Tests are run automatically on each PR, and 100% test and lint pass rate is required to get the code merged in, although it is fine to have work-in- progress pull requests open while debugging. Details on how to run the test suite can be found here . For features which change the spec of the configuration file, documentation should be added in docs/spec.md .","title":"Opening Pull Requests"},{"location":"CONTRIBUTING/#setting-up-a-development-environment","text":"For local development, you will need Go version 1.11+ installed. Tusk uses go modules for dependency management, so make sure to clone the project outside of the GOPATH . If that doesn't mean anything to you, you're probably fine. git clone https://github.com/rliebz/tusk.git If it is not already on your path, you probably also want to have the GOPATH binary directory available for projects installed by go get and go install . To do so, add the following to your .bash_profile or .zshrc : export PATH=\"$PATH:$(go env GOPATH)/bin\" To install Tusk: go install If you have already installed tusk from another source, make sure you test against the development version installed locally. If you do not get (devel) as the version, you may need to move your Go binary path earlier in your PATH : $ tusk --version (devel)","title":"Setting Up a Development Environment"},{"location":"CONTRIBUTING/#making-changes","text":"If you have not yet done so, make sure you fork the repository so you can push your changes back to your own fork on GitHub. When starting work on a new feature, create a new branch based off the main branch. Pull requests should generally target the main branch, and releases will be cut separately.","title":"Making Changes"},{"location":"CONTRIBUTING/#running-tests","text":"To run the unit tests: tusk test To run the full test suite, along with the linter: tusk test -a If the linter fails, execution will stop short and not actually run the unit test suite. If there is a linter error that is a false-positive, or the violation is necessary for your contribution, you can disable a specific linter for that line: cmd := exec.Command(\"sh\", \"-c\", command) // nolint: gosec","title":"Running Tests"},{"location":"spec/","text":"The Spec Tasks The core of every tusk.yml file is a list of tasks. Tasks are declared at the top level of the tusk.yml file and include a list of tasks. For the following tasks: tasks: hello: run: echo \"Hello, world!\" goodbye: run: echo \"Goodbye, world!\" The commands can be run with no additional configuration: $ tusk hello Running: echo \"Hello, world!\" Hello, world! Tasks can be documented with a one-line usage string and a slightly longer description . This information will be displayed in help messages: tasks: hello: usage: Say hello to the world description: | This command will echo \"Hello, world!\" to the user. There's no surprises here. run: echo \"Hello, world!\" goodbye: run: echo \"Goodbye, world!\" Run The behavior of a task is defined in its run clause. A run clause can be used for commands, sub-tasks, or setting environment variables. Although each run item can only perform one of these actions, they can be run in succession to handle complex scenarios. In its simplest form, run can be given a string or list of strings to be executed serially as shell commands: tasks: hello: run: echo \"Hello!\" This is a shorthand syntax for the following: tasks: hello: run: - command: exec: echo \"Hello!\" The run clause tasks a list of run items, which allow executing shell commands with command , setting or unsetting environment variables with set-environment , running other tasks with task , and controlling conditional execution with when . Command The command clause is the most common thing to do during a run , so for convenience, passing a string or single item will be correctly interpreted. Here are several examples of equivalent run clauses: run: echo \"Hello!\" run: - echo \"Hello!\" run: command: echo \"Hello!\" run: - command: echo \"Hello!\" run: - command: exec: echo \"Hello!\" While the interpreter cannot be set for an individual command, it is possible to set them globally using the interpreter clause . Exec The exec clause contains the actual shell command to be performed. If any of the run commands execute with a non-zero exit code, Tusk will immediately exit with the same exit code without executing any other commands. Each command in a run clause gets its own sub-shell, so things like declaring functions and environment variables will not be available across separate run commmands, although it is possible to run the set-environment clause or use a multi-line shell command. When using POSIX interpreters with multi-line scripts, it is recommend to run set -e at the top of the script, to preserve the exit-on-error behavior. tasks: hello: run: | set -e errcho() { >&2 echo \"$@\" } errcho \"Hello, world!\" errcho \"Goodbye, world!\" Print Sometimes it may not be desirable to print the exact command run, for example, if it's overly verbose or contains secrets. In that case, the command clause can be passed a print string to use as an alternative: tasks: hello: run: command: exec: echo \"SECRET_VALUE\" print: echo \"*****\" Quiet Sometimes you may not want to print the command-to-be-run at all. In that case, the quiet clause can be used. This is comparable to the global -q / --quiet command-line flag in that it silence's Tusk's logging without silencing the command output: tasks: hello: run: command: exec: curl http://example.com quiet: true This property can also be set for an entire task and is inherited by any sub-task. In both of these cases the executed commands are not printed: tasks: quiet-parent: quiet: true run: task: normal-child normal-child: run: curl http://example.com normal-parent: run: task: quiet-child quiet-child: quiet: true run: curl http://example.com Dir The dir clause sets the working directory for a specific command: tasks: hello: run: command: exec: echo \"Hello from $PWD!\" dir: ./subdir Set Environment To set or unset environment variables, simply define a map of environment variable names to their desired values: tasks: hello: options: proxy-url: default: http://proxy.example.com run: - set-environment: http_proxy: ${proxy-url} https_proxy: ${proxy-url} no_proxy: ~ - command: curl http://example.com Passing ~ or null to an environment variable will explicitly unset it, while passing an empty string will set it to an empty string. Environment variables once modified will persist until Tusk exits. Sub-Tasks Run can also execute previously-defined tasks: tasks: one: run: echo \"Inside one\" two: run: - task: one - command: echo \"Inside two\" For any arg or option that a sub-task defines, the parent task can pass a value, which is treated the same way as passing by command-line would be. Args are passed in as a list, while options are a map from flag name to value. To pass values, use the long definition of a sub-task: tasks: greet: args: name: usage: The person to greet options: greeting: default: Hello run: echo \"${greeting}, ${person}!\" greet-myself: run: task: name: greet args: - me options: greeting: Howdy In cases where a sub-task may not be useful on its own, define it as private to prevent it from being invoked directly from the command-line. For example: tasks: configure-environment: private: true run: set-environment: { APP_ENV: dev } serve: run: - task: configure-environment - command: python main.py When For conditional execution, when clauses are available. run: when: os: linux command: echo \"This is a linux machine\" In a run clause, any item with a true when clause will execute. There are five different checks supported: command (list): Execute if any command runs with an exit code of 0 . Commands will execute in the order defined and stop execution at the first successful command. exists (list): Execute if any of the listed files exists. not-exists (list): Execute if any of the listed files doesn't exist. os (list): Execute if the operating system matches any one from the list. environment (map[string -> list]): Execute if the environment variable matches any of the values it maps to. To check if a variable is not set, the value should be ~ or null . equal (map[string -> list]): Execute if the given option equals any of the values it maps to. not-equal (map[string -> list]): Execute if the given option is not equal to any one of the values it maps to. The when clause supports any number of different checks as a list, where each check must pass individually for the clause to evaluate to true. Here is a more complicated example of how when can be used: tasks: echo: options: cat: usage: Cat a file run: - when: os: - linux - darwin command: echo \"This is a unix machine\" - when: - exists: my_file.txt - equal: { cat: true } - command: command -v cat command: cat my_file.txt Short Form Because it's common to check if a boolean flag is set to true, when clauses also accept strings as shorthand. Consider the following example, which checks to see if some option foo has been set to true : when: equal: { foo: true } This can be expressed more succinctly as the following: when: foo When Any/All Logic A when clause takes a list of items, where each item can have multiple checks. Each when item will pass if any of the checks pass, while the whole clause will only pass if all of the items pass. For example: tasks: exists: run: - when: # There is a single `when` item with two checks exists: - file_one.txt - file_two.txt command: echo \"At least one file exists\" - when: # There are two separate `when` items with one check each - exists: file_one.txt - exists: file_two.txt command: echo \"Both files exist\" These properties can be combined for more complicated logic: tasks: echo: options: verbose: type: bool ignore-os: type: bool run: - when: # (OS is linux OR darwin OR ignore OS is true) AND (verbose is true) - os: - linux - darwin equal: { ignore-os: true } - equal: { verbose: true } command: echo \"This is a unix machine\" Args Tasks may have args that are passed directly as inputs. Any arg that is defined is required for the task to execute. tasks: greet: args: name: usage: The person to greet run: echo \"Hello, ${name}!\" The task can be invoked as such: $ tusk greet friend Hello, friend! Arg Types Args can be of the types string , integer , float , or boolean . Args without types specified are considered strings. tasks: add: args: a: type: int b: type: int run: echo $((${a} + ${b})) Arg Values Args can specify which values are considered valid: tasks: greet: args: name: values: - Abby - Bobby - Carl Any value passed by command-line must be one of the listed values, or the command will fail to execute. Options Tasks may have options that are passed as GNU-style flags. The following configuration will provide -n, --name flags to the CLI and help documentation, which will then be interpolated: tasks: greet: options: name: usage: The person to greet short: n environment: GREET_NAME default: World run: echo \"Hello, ${name}!\" The above configuration will evaluate the value of name in order of highest priority: The value passed by command line flags ( -n or --name ) The value of the environment variable ( GREET_NAME ), if set The value set in default For short flag names, values can be combined such that tusk foo -ab is exactly equivalent to tusk foo -a -b . Option Types Options can be of the types string , integer , float , or boolean , using the zero-value of that type as the default if not set. Options without types specified are considered strings. For boolean values, the flag should be passed by command line without any arugments. In the following example: tasks: greet: options: loud: type: bool run: - when: equal: { loud: true } command: echo \"HELLO!\" - when: equal: { loud: false } command: echo \"Hello.\" The flag should be passed as such: tusk greet --loud This means that for an option that is true by default, the only way to disable it is with the following syntax: tusk greet --loud=false Of course, options can always be defined in the reverse manner to avoid this issue: options: no-loud: type: bool Option Defaults Much like run clauses accept a shorthand form, passing a string to default is shorthand. The following options are exactly equivalent: options: short: default: foo long: default: - value: foo A default clause can also register the stdout of a command as its value: options: os: default: command: uname -s A default clause also accepts a list of possible values with a corresponding when clause. The first when that evaluates to true will be used as the default value, with an omitted when always considered true. In this example, linux users will have the name Linux User , while the default for all other OSes is User : options: name: default: - when: os: linux value: Linux User - value: User Option Values Like args, an option can specify which values are considered valid: options: number: default: zero values: - one - two - three Any value passed by command-line flags or environment variables must be one of the listed values. Default values, including commands, are excluded from this requirement. Required Options Options may be required if there is no sane default value. For a required flag, the task will not execute unless the flag is passed: options: file: required: true A required option cannot be private or have any default values. Private Options Sometimes it may be desirable to have a variable that cannot be directly modified through command-line flags. In this case, use the private option: options: user: private: true default: command: whoami A private option will not accept environment variables or command line flags, and it will not appear in the help documentation. Option Rewrite Boolean values are convenient as CLI inputs, but the interpolated output of true or false is often not. Use the rewrite clause to change the interpolation behavior from true / false to a conditional specified string: tasks: greet: options: verbose: type: boolean rewrite: --level=verbose run: mycli greet ${verbose} The above will interpolate to eitehr mycli greet or mycli greet --level=verbose depending on whether the --verbose flag is passed. Note that once a boolean option has been rewritten, the output is no longer true / false , which means when: verbose in the above example would never evaluate to true. Shared Options Options may also be defined at the root of the config file to be shared between tasks: options: name: usage: The person to greet default: World tasks: hello: run: echo \"Hello, ${name}!\" goodbye: run: echo \"Goodbye, ${name}!\" Any shared variables referenced by a task will be exposed by command-line when invoking that task. Shared variables referenced by a sub-task will be evaluated as needed, but not exposed by command-line. Tasks that define an argument or option with the same name as a shared task will overwrite the value of the shared option for the length of that task, not including sub-tasks. Finally The finally clause is run after a task's run logic has completed, whether or not that task was successful. This can be useful for clean-up logic. A finally clause has the same format as a run clause: tasks: hello: run: - echo \"Hello\" - exit 1 # `run` clause stops here - echo \"Oops!\" # Never prints finally: - echo \"Goodbye\" # Always prints - task: cleanup # ... If the finally clause runs an unsuccessful command, it will terminate early the same way that a run clause would. The exit code is still passed back to the command line. However, if both the run clause and finally clause fail, the exit code from the run clause takes precedence. Include In some cases it may be desirable to split the task definition into a separate file. The include clause serves this purpose. At the top-level of a task, a task may optionally be specified using just the include key, which maps to a separate file where there task definition is stored. For example, tusk.yml could be written like this: tasks: hello: include: .tusk/hello.yml With a .tusk/hello.yml that looks like this: options: name: usage: The person to greet default: World run: echo \"Hello, ${name}!\" It is invalid to split the configuration; if the include clause is used, no other keys can be specified in the tusk.yml , and the full task must be defined in the included file. Environment Files Environment variables are also automatically read from a .env file in the same directory as tusk.yml before task execution. This file is optional by default, and supports typical \"dotenv\" extended syntax such as quotation marks, comments, variable substitution, and the export keyword. A typical file might look like this: FOO=foovalue BAR=barvalue Environment files can be explicitly specified as configuration at the top-level: env-file: .local.env This is shorthand syntax for the following: env-file: - path: .local.env required: true Multiple environment files can be specified. Entries are evaluted in order, so environment variables from later files override values specified in previous entries. Specifying any value for env-file will disable the default behavior of auto-loading an optional .env . To re-enable it, specify it explicitly: env-file: - path: .env required: false - .local.env To disable loading environment files completely, pass [] or /dev/null : env-file: [] Interpreter By default, any command run will default to using sh -c as its interpreter. This can optionally be configured using the interpreter clause. The interpreter is specified as an executable, which can either be an absolute path or available on the user's PATH, followed by a series of optional arguments: interpreter: node -e tasks: hello: run: console.log(\"Hello!\") The commands specified in individual tasks will be passed as the final argument. The above example is effectively equivalent to the following: node -e 'console.log(\"Hello!\")' CLI Metadata It is also possible to create a custom CLI tool for use outside of a project's directory by using shell aliases: alias mycli=\"tusk -f /path/to/tusk.yml\" In that case, it may be useful to override the tool name and usage text that are provided as part of the help documentation: name: mycli usage: A custom aliased command-line application tasks: # ... The example above will produce the following help documentation: $ tusk --help mycli - A custom aliased command-line application Usage: mycli [global options] [task options] Tasks: ... Interpolation The interpolation syntax for a variable foo is ${foo} , meaning any instances of ${foo} in the configuration file will be replaced with the value of foo during execution. Interpolation is done on a task-by-task basis, meaning args and options defined in one task will not interpolate to any other tasks. Shared options, on the other hand, will only be evaluated once per execution. The execution order is as followed: Shared options are interpolated first, in the order defined by the config file. The results of global interpolation are cached and not re-run. The args for the current task being run are interpolated, in order. The options for the current task being run are interpolated, in order. For each call to a sub-task, the process is repeated, ignoring the task- specific interpolations for parent tasks, using the cached shared options. This means that options can reference other options or args: options: name: default: World greeting: default: Hello, ${name} tasks: greet: run: echo \"${greeting}\" Because interpolation is not always desirable, as in the case of environment variables, $$ will escape to $ and ignore interpolation. It is also possible to use alternative syntax such as $foo to avoid interpolation as well. The following two tasks will both use environment variables and not attempt interpolation: tasks: one: run: Hello, $${USER} two: run: Hello, $USER Interpolation works by substituting the value in the yaml config file, then parsing the file after interpolation. This means that variable values with newlines or other characters that are relevant to the yaml spec or the sh interpreter will need to be considered by the user. This can be as simple as using quotes when appropriate.","title":"The Spec"},{"location":"spec/#the-spec","text":"","title":"The Spec"},{"location":"spec/#tasks","text":"The core of every tusk.yml file is a list of tasks. Tasks are declared at the top level of the tusk.yml file and include a list of tasks. For the following tasks: tasks: hello: run: echo \"Hello, world!\" goodbye: run: echo \"Goodbye, world!\" The commands can be run with no additional configuration: $ tusk hello Running: echo \"Hello, world!\" Hello, world! Tasks can be documented with a one-line usage string and a slightly longer description . This information will be displayed in help messages: tasks: hello: usage: Say hello to the world description: | This command will echo \"Hello, world!\" to the user. There's no surprises here. run: echo \"Hello, world!\" goodbye: run: echo \"Goodbye, world!\"","title":"Tasks"},{"location":"spec/#run","text":"The behavior of a task is defined in its run clause. A run clause can be used for commands, sub-tasks, or setting environment variables. Although each run item can only perform one of these actions, they can be run in succession to handle complex scenarios. In its simplest form, run can be given a string or list of strings to be executed serially as shell commands: tasks: hello: run: echo \"Hello!\" This is a shorthand syntax for the following: tasks: hello: run: - command: exec: echo \"Hello!\" The run clause tasks a list of run items, which allow executing shell commands with command , setting or unsetting environment variables with set-environment , running other tasks with task , and controlling conditional execution with when .","title":"Run"},{"location":"spec/#command","text":"The command clause is the most common thing to do during a run , so for convenience, passing a string or single item will be correctly interpreted. Here are several examples of equivalent run clauses: run: echo \"Hello!\" run: - echo \"Hello!\" run: command: echo \"Hello!\" run: - command: echo \"Hello!\" run: - command: exec: echo \"Hello!\" While the interpreter cannot be set for an individual command, it is possible to set them globally using the interpreter clause .","title":"Command"},{"location":"spec/#exec","text":"The exec clause contains the actual shell command to be performed. If any of the run commands execute with a non-zero exit code, Tusk will immediately exit with the same exit code without executing any other commands. Each command in a run clause gets its own sub-shell, so things like declaring functions and environment variables will not be available across separate run commmands, although it is possible to run the set-environment clause or use a multi-line shell command. When using POSIX interpreters with multi-line scripts, it is recommend to run set -e at the top of the script, to preserve the exit-on-error behavior. tasks: hello: run: | set -e errcho() { >&2 echo \"$@\" } errcho \"Hello, world!\" errcho \"Goodbye, world!\"","title":"Exec"},{"location":"spec/#print","text":"Sometimes it may not be desirable to print the exact command run, for example, if it's overly verbose or contains secrets. In that case, the command clause can be passed a print string to use as an alternative: tasks: hello: run: command: exec: echo \"SECRET_VALUE\" print: echo \"*****\"","title":"Print"},{"location":"spec/#quiet","text":"Sometimes you may not want to print the command-to-be-run at all. In that case, the quiet clause can be used. This is comparable to the global -q / --quiet command-line flag in that it silence's Tusk's logging without silencing the command output: tasks: hello: run: command: exec: curl http://example.com quiet: true This property can also be set for an entire task and is inherited by any sub-task. In both of these cases the executed commands are not printed: tasks: quiet-parent: quiet: true run: task: normal-child normal-child: run: curl http://example.com normal-parent: run: task: quiet-child quiet-child: quiet: true run: curl http://example.com","title":"Quiet"},{"location":"spec/#dir","text":"The dir clause sets the working directory for a specific command: tasks: hello: run: command: exec: echo \"Hello from $PWD!\" dir: ./subdir","title":"Dir"},{"location":"spec/#set-environment","text":"To set or unset environment variables, simply define a map of environment variable names to their desired values: tasks: hello: options: proxy-url: default: http://proxy.example.com run: - set-environment: http_proxy: ${proxy-url} https_proxy: ${proxy-url} no_proxy: ~ - command: curl http://example.com Passing ~ or null to an environment variable will explicitly unset it, while passing an empty string will set it to an empty string. Environment variables once modified will persist until Tusk exits.","title":"Set Environment"},{"location":"spec/#sub-tasks","text":"Run can also execute previously-defined tasks: tasks: one: run: echo \"Inside one\" two: run: - task: one - command: echo \"Inside two\" For any arg or option that a sub-task defines, the parent task can pass a value, which is treated the same way as passing by command-line would be. Args are passed in as a list, while options are a map from flag name to value. To pass values, use the long definition of a sub-task: tasks: greet: args: name: usage: The person to greet options: greeting: default: Hello run: echo \"${greeting}, ${person}!\" greet-myself: run: task: name: greet args: - me options: greeting: Howdy In cases where a sub-task may not be useful on its own, define it as private to prevent it from being invoked directly from the command-line. For example: tasks: configure-environment: private: true run: set-environment: { APP_ENV: dev } serve: run: - task: configure-environment - command: python main.py","title":"Sub-Tasks"},{"location":"spec/#when","text":"For conditional execution, when clauses are available. run: when: os: linux command: echo \"This is a linux machine\" In a run clause, any item with a true when clause will execute. There are five different checks supported: command (list): Execute if any command runs with an exit code of 0 . Commands will execute in the order defined and stop execution at the first successful command. exists (list): Execute if any of the listed files exists. not-exists (list): Execute if any of the listed files doesn't exist. os (list): Execute if the operating system matches any one from the list. environment (map[string -> list]): Execute if the environment variable matches any of the values it maps to. To check if a variable is not set, the value should be ~ or null . equal (map[string -> list]): Execute if the given option equals any of the values it maps to. not-equal (map[string -> list]): Execute if the given option is not equal to any one of the values it maps to. The when clause supports any number of different checks as a list, where each check must pass individually for the clause to evaluate to true. Here is a more complicated example of how when can be used: tasks: echo: options: cat: usage: Cat a file run: - when: os: - linux - darwin command: echo \"This is a unix machine\" - when: - exists: my_file.txt - equal: { cat: true } - command: command -v cat command: cat my_file.txt","title":"When"},{"location":"spec/#short-form","text":"Because it's common to check if a boolean flag is set to true, when clauses also accept strings as shorthand. Consider the following example, which checks to see if some option foo has been set to true : when: equal: { foo: true } This can be expressed more succinctly as the following: when: foo","title":"Short Form"},{"location":"spec/#when-anyall-logic","text":"A when clause takes a list of items, where each item can have multiple checks. Each when item will pass if any of the checks pass, while the whole clause will only pass if all of the items pass. For example: tasks: exists: run: - when: # There is a single `when` item with two checks exists: - file_one.txt - file_two.txt command: echo \"At least one file exists\" - when: # There are two separate `when` items with one check each - exists: file_one.txt - exists: file_two.txt command: echo \"Both files exist\" These properties can be combined for more complicated logic: tasks: echo: options: verbose: type: bool ignore-os: type: bool run: - when: # (OS is linux OR darwin OR ignore OS is true) AND (verbose is true) - os: - linux - darwin equal: { ignore-os: true } - equal: { verbose: true } command: echo \"This is a unix machine\"","title":"When Any/All Logic"},{"location":"spec/#args","text":"Tasks may have args that are passed directly as inputs. Any arg that is defined is required for the task to execute. tasks: greet: args: name: usage: The person to greet run: echo \"Hello, ${name}!\" The task can be invoked as such: $ tusk greet friend Hello, friend!","title":"Args"},{"location":"spec/#arg-types","text":"Args can be of the types string , integer , float , or boolean . Args without types specified are considered strings. tasks: add: args: a: type: int b: type: int run: echo $((${a} + ${b}))","title":"Arg Types"},{"location":"spec/#arg-values","text":"Args can specify which values are considered valid: tasks: greet: args: name: values: - Abby - Bobby - Carl Any value passed by command-line must be one of the listed values, or the command will fail to execute.","title":"Arg Values"},{"location":"spec/#options","text":"Tasks may have options that are passed as GNU-style flags. The following configuration will provide -n, --name flags to the CLI and help documentation, which will then be interpolated: tasks: greet: options: name: usage: The person to greet short: n environment: GREET_NAME default: World run: echo \"Hello, ${name}!\" The above configuration will evaluate the value of name in order of highest priority: The value passed by command line flags ( -n or --name ) The value of the environment variable ( GREET_NAME ), if set The value set in default For short flag names, values can be combined such that tusk foo -ab is exactly equivalent to tusk foo -a -b .","title":"Options"},{"location":"spec/#option-types","text":"Options can be of the types string , integer , float , or boolean , using the zero-value of that type as the default if not set. Options without types specified are considered strings. For boolean values, the flag should be passed by command line without any arugments. In the following example: tasks: greet: options: loud: type: bool run: - when: equal: { loud: true } command: echo \"HELLO!\" - when: equal: { loud: false } command: echo \"Hello.\" The flag should be passed as such: tusk greet --loud This means that for an option that is true by default, the only way to disable it is with the following syntax: tusk greet --loud=false Of course, options can always be defined in the reverse manner to avoid this issue: options: no-loud: type: bool","title":"Option Types"},{"location":"spec/#option-defaults","text":"Much like run clauses accept a shorthand form, passing a string to default is shorthand. The following options are exactly equivalent: options: short: default: foo long: default: - value: foo A default clause can also register the stdout of a command as its value: options: os: default: command: uname -s A default clause also accepts a list of possible values with a corresponding when clause. The first when that evaluates to true will be used as the default value, with an omitted when always considered true. In this example, linux users will have the name Linux User , while the default for all other OSes is User : options: name: default: - when: os: linux value: Linux User - value: User","title":"Option Defaults"},{"location":"spec/#option-values","text":"Like args, an option can specify which values are considered valid: options: number: default: zero values: - one - two - three Any value passed by command-line flags or environment variables must be one of the listed values. Default values, including commands, are excluded from this requirement.","title":"Option Values"},{"location":"spec/#required-options","text":"Options may be required if there is no sane default value. For a required flag, the task will not execute unless the flag is passed: options: file: required: true A required option cannot be private or have any default values.","title":"Required Options"},{"location":"spec/#private-options","text":"Sometimes it may be desirable to have a variable that cannot be directly modified through command-line flags. In this case, use the private option: options: user: private: true default: command: whoami A private option will not accept environment variables or command line flags, and it will not appear in the help documentation.","title":"Private Options"},{"location":"spec/#option-rewrite","text":"Boolean values are convenient as CLI inputs, but the interpolated output of true or false is often not. Use the rewrite clause to change the interpolation behavior from true / false to a conditional specified string: tasks: greet: options: verbose: type: boolean rewrite: --level=verbose run: mycli greet ${verbose} The above will interpolate to eitehr mycli greet or mycli greet --level=verbose depending on whether the --verbose flag is passed. Note that once a boolean option has been rewritten, the output is no longer true / false , which means when: verbose in the above example would never evaluate to true.","title":"Option Rewrite"},{"location":"spec/#shared-options","text":"Options may also be defined at the root of the config file to be shared between tasks: options: name: usage: The person to greet default: World tasks: hello: run: echo \"Hello, ${name}!\" goodbye: run: echo \"Goodbye, ${name}!\" Any shared variables referenced by a task will be exposed by command-line when invoking that task. Shared variables referenced by a sub-task will be evaluated as needed, but not exposed by command-line. Tasks that define an argument or option with the same name as a shared task will overwrite the value of the shared option for the length of that task, not including sub-tasks.","title":"Shared Options"},{"location":"spec/#finally","text":"The finally clause is run after a task's run logic has completed, whether or not that task was successful. This can be useful for clean-up logic. A finally clause has the same format as a run clause: tasks: hello: run: - echo \"Hello\" - exit 1 # `run` clause stops here - echo \"Oops!\" # Never prints finally: - echo \"Goodbye\" # Always prints - task: cleanup # ... If the finally clause runs an unsuccessful command, it will terminate early the same way that a run clause would. The exit code is still passed back to the command line. However, if both the run clause and finally clause fail, the exit code from the run clause takes precedence.","title":"Finally"},{"location":"spec/#include","text":"In some cases it may be desirable to split the task definition into a separate file. The include clause serves this purpose. At the top-level of a task, a task may optionally be specified using just the include key, which maps to a separate file where there task definition is stored. For example, tusk.yml could be written like this: tasks: hello: include: .tusk/hello.yml With a .tusk/hello.yml that looks like this: options: name: usage: The person to greet default: World run: echo \"Hello, ${name}!\" It is invalid to split the configuration; if the include clause is used, no other keys can be specified in the tusk.yml , and the full task must be defined in the included file.","title":"Include"},{"location":"spec/#environment-files","text":"Environment variables are also automatically read from a .env file in the same directory as tusk.yml before task execution. This file is optional by default, and supports typical \"dotenv\" extended syntax such as quotation marks, comments, variable substitution, and the export keyword. A typical file might look like this: FOO=foovalue BAR=barvalue Environment files can be explicitly specified as configuration at the top-level: env-file: .local.env This is shorthand syntax for the following: env-file: - path: .local.env required: true Multiple environment files can be specified. Entries are evaluted in order, so environment variables from later files override values specified in previous entries. Specifying any value for env-file will disable the default behavior of auto-loading an optional .env . To re-enable it, specify it explicitly: env-file: - path: .env required: false - .local.env To disable loading environment files completely, pass [] or /dev/null : env-file: []","title":"Environment Files"},{"location":"spec/#interpreter","text":"By default, any command run will default to using sh -c as its interpreter. This can optionally be configured using the interpreter clause. The interpreter is specified as an executable, which can either be an absolute path or available on the user's PATH, followed by a series of optional arguments: interpreter: node -e tasks: hello: run: console.log(\"Hello!\") The commands specified in individual tasks will be passed as the final argument. The above example is effectively equivalent to the following: node -e 'console.log(\"Hello!\")'","title":"Interpreter"},{"location":"spec/#cli-metadata","text":"It is also possible to create a custom CLI tool for use outside of a project's directory by using shell aliases: alias mycli=\"tusk -f /path/to/tusk.yml\" In that case, it may be useful to override the tool name and usage text that are provided as part of the help documentation: name: mycli usage: A custom aliased command-line application tasks: # ... The example above will produce the following help documentation: $ tusk --help mycli - A custom aliased command-line application Usage: mycli [global options] [task options] Tasks: ...","title":"CLI Metadata"},{"location":"spec/#interpolation","text":"The interpolation syntax for a variable foo is ${foo} , meaning any instances of ${foo} in the configuration file will be replaced with the value of foo during execution. Interpolation is done on a task-by-task basis, meaning args and options defined in one task will not interpolate to any other tasks. Shared options, on the other hand, will only be evaluated once per execution. The execution order is as followed: Shared options are interpolated first, in the order defined by the config file. The results of global interpolation are cached and not re-run. The args for the current task being run are interpolated, in order. The options for the current task being run are interpolated, in order. For each call to a sub-task, the process is repeated, ignoring the task- specific interpolations for parent tasks, using the cached shared options. This means that options can reference other options or args: options: name: default: World greeting: default: Hello, ${name} tasks: greet: run: echo \"${greeting}\" Because interpolation is not always desirable, as in the case of environment variables, $$ will escape to $ and ignore interpolation. It is also possible to use alternative syntax such as $foo to avoid interpolation as well. The following two tasks will both use environment variables and not attempt interpolation: tasks: one: run: Hello, $${USER} two: run: Hello, $USER Interpolation works by substituting the value in the yaml config file, then parsing the file after interpolation. This means that variable values with newlines or other characters that are relevant to the yaml spec or the sh interpreter will need to be considered by the user. This can be as simple as using quotes when appropriate.","title":"Interpolation"}]} \ No newline at end of file +{"config":{"indexing":"full","lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"Tusk Tusk is a yaml-based task runner. By creating a tusk.yml in the root of a repository, Tusk becomes a custom command line tool with minimal configuration. Details on the usage and configuration options can be found in the project documentation . Features Customizable : Specify your own tasks and options with support for command-line flags, environment variables, conditional logic, and more. Explorable : All the help you need to get started is available straight from the command line. Help documentation is generated dynamically, and support for Bash and Zsh tab completion is available. Accessible : Built for usability with a simple YAML configuration, familiar syntax for passing options, Bash-like variable interpolation, and a colorful terminal output. Zero Dependencies : All you need is a single binary file to get started on Linux, macOS, or Windows. Getting Started Installation Go With Go 1.21+ installed: go install github.com/rliebz/tusk@latest Homebrew On macOS, installation is also available through homebrew : brew install rliebz/tusk/tusk With Homebrew, tab completion is installed automatically. Compiled Releases The latest version can be downloaded from the releases page . To install automatically: curl -sL https://git.io/tusk | bash -s -- -b /usr/local/bin latest To pin to a specific version, replace latest with the tag for that version. To install to another directory, change the path passed to -b . Installing Tab Completion For bash: tusk --install-completion bash For fish: tusk --install-completion fish For zsh: tusk --install-completion zsh Completions can be uninstalled with the --uninstall-completion flag. Usage Create a tusk.yml file in the root of a project repository: tasks: greet: usage: Say hello to someone options: name: usage: A person to say \"Hello\" to default: World run: echo \"Hello, ${name}!\" As long as there is a tusk.yml file in the working or any parent directory, tasks can be run: $ tusk greet --name friend Running: echo \"Hello, friend!\" Hello, friend! Help messages are dynamically generated based on the YAML configuration: $ tusk --help tusk - the modern task runner Usage: tusk [global options] [task options] Tasks: greet Say hello to someone Global Options: -f, --file Set file to use as the config file -h, --help Show help and exit ... Individual tasks have help messages as well: $ tusk greet --help tusk greet - Say hello to someone Usage: tusk greet [options] Options: --name A person to say \"Hello\" to Additional information on the configuration spec can be found in the project documentation . For more detailed examples, check out the project's own tusk.yml file. Contributing Set-up instructions for a development environment and contribution guidelines can be found in CONTRIBUTING.md .","title":"Tusk"},{"location":"#tusk","text":"Tusk is a yaml-based task runner. By creating a tusk.yml in the root of a repository, Tusk becomes a custom command line tool with minimal configuration. Details on the usage and configuration options can be found in the project documentation .","title":"Tusk"},{"location":"#features","text":"Customizable : Specify your own tasks and options with support for command-line flags, environment variables, conditional logic, and more. Explorable : All the help you need to get started is available straight from the command line. Help documentation is generated dynamically, and support for Bash and Zsh tab completion is available. Accessible : Built for usability with a simple YAML configuration, familiar syntax for passing options, Bash-like variable interpolation, and a colorful terminal output. Zero Dependencies : All you need is a single binary file to get started on Linux, macOS, or Windows.","title":"Features"},{"location":"#getting-started","text":"","title":"Getting Started"},{"location":"#installation","text":"","title":"Installation"},{"location":"#go","text":"With Go 1.21+ installed: go install github.com/rliebz/tusk@latest","title":"Go"},{"location":"#homebrew","text":"On macOS, installation is also available through homebrew : brew install rliebz/tusk/tusk With Homebrew, tab completion is installed automatically.","title":"Homebrew"},{"location":"#compiled-releases","text":"The latest version can be downloaded from the releases page . To install automatically: curl -sL https://git.io/tusk | bash -s -- -b /usr/local/bin latest To pin to a specific version, replace latest with the tag for that version. To install to another directory, change the path passed to -b .","title":"Compiled Releases"},{"location":"#installing-tab-completion","text":"For bash: tusk --install-completion bash For fish: tusk --install-completion fish For zsh: tusk --install-completion zsh Completions can be uninstalled with the --uninstall-completion flag.","title":"Installing Tab Completion"},{"location":"#usage","text":"Create a tusk.yml file in the root of a project repository: tasks: greet: usage: Say hello to someone options: name: usage: A person to say \"Hello\" to default: World run: echo \"Hello, ${name}!\" As long as there is a tusk.yml file in the working or any parent directory, tasks can be run: $ tusk greet --name friend Running: echo \"Hello, friend!\" Hello, friend! Help messages are dynamically generated based on the YAML configuration: $ tusk --help tusk - the modern task runner Usage: tusk [global options] [task options] Tasks: greet Say hello to someone Global Options: -f, --file Set file to use as the config file -h, --help Show help and exit ... Individual tasks have help messages as well: $ tusk greet --help tusk greet - Say hello to someone Usage: tusk greet [options] Options: --name A person to say \"Hello\" to Additional information on the configuration spec can be found in the project documentation . For more detailed examples, check out the project's own tusk.yml file.","title":"Usage"},{"location":"#contributing","text":"Set-up instructions for a development environment and contribution guidelines can be found in CONTRIBUTING.md .","title":"Contributing"},{"location":"CONTRIBUTING/","text":"Contributing All contributions are welcome and appreciated. Feel free to open issues or pull requests for any fixes, changes, or new features, and if you are not sure about anything, open it anyway. Issues and pull requests are a great forum for discussion and a great opportunity to help improve the code as a whole. Opening Issues A great way to contribute to the project is to open GitHub issues whenever you encounter any issue, or if you have an idea on how Tusk can improve. Missing or incorrect documentation are issues too, so feel free to open one whenever you feel there is a chance to make Tusk better. When reporting a bug, make sure to include the expected behavior and steps to reproduce. The more descriptive you can be, the faster the issue can be resolved. Opening Pull Requests Always feel free to open a pull request, whether it is a fix or a new addition. For big or breaking changes, you might consider opening an issue first to check interest, but it is absolutely not required to make a contribution. Tests are run automatically on each PR, and 100% test and lint pass rate is required to get the code merged in, although it is fine to have work-in- progress pull requests open while debugging. Details on how to run the test suite can be found here . For features which change the spec of the configuration file, documentation should be added in docs/spec.md . Setting Up a Development Environment For local development, you will need Go and golangci-lint installed. The easiest way to do this is with mise , but it is not required. If it is not already on your path, you probably want to have the GOBIN directory available for projects installed by go install . To do so, add the following to your .bash_profile or .zshrc : export PATH=\"$PATH:$(go env GOBIN)\" To install Tusk: go install If you have already installed tusk from another source, make sure you test against the development version installed locally. If you do not get (devel) as the version, you may need to move your Go binary path earlier in your PATH : $ tusk --version (devel) Making Changes If you have not yet done so, make sure you fork the repository so you can push your changes back to your own fork on GitHub. When starting work on a new feature, create a new branch based off the main branch. Pull requests should generally target the main branch, and releases will be cut separately. Running Tests To run the unit tests: tusk test To run the full test suite, along with golangci-lint: tusk test -a If the linter fails, execution will stop short and not actually run the unit test suite. If there is a linter error that is a false-positive, or the violation is necessary for your contribution, you can disable a specific linter for that line: cmd := exec.Command(\"sh\", \"-c\", command) //nolint:gosec","title":"Contributing"},{"location":"CONTRIBUTING/#contributing","text":"All contributions are welcome and appreciated. Feel free to open issues or pull requests for any fixes, changes, or new features, and if you are not sure about anything, open it anyway. Issues and pull requests are a great forum for discussion and a great opportunity to help improve the code as a whole.","title":"Contributing"},{"location":"CONTRIBUTING/#opening-issues","text":"A great way to contribute to the project is to open GitHub issues whenever you encounter any issue, or if you have an idea on how Tusk can improve. Missing or incorrect documentation are issues too, so feel free to open one whenever you feel there is a chance to make Tusk better. When reporting a bug, make sure to include the expected behavior and steps to reproduce. The more descriptive you can be, the faster the issue can be resolved.","title":"Opening Issues"},{"location":"CONTRIBUTING/#opening-pull-requests","text":"Always feel free to open a pull request, whether it is a fix or a new addition. For big or breaking changes, you might consider opening an issue first to check interest, but it is absolutely not required to make a contribution. Tests are run automatically on each PR, and 100% test and lint pass rate is required to get the code merged in, although it is fine to have work-in- progress pull requests open while debugging. Details on how to run the test suite can be found here . For features which change the spec of the configuration file, documentation should be added in docs/spec.md .","title":"Opening Pull Requests"},{"location":"CONTRIBUTING/#setting-up-a-development-environment","text":"For local development, you will need Go and golangci-lint installed. The easiest way to do this is with mise , but it is not required. If it is not already on your path, you probably want to have the GOBIN directory available for projects installed by go install . To do so, add the following to your .bash_profile or .zshrc : export PATH=\"$PATH:$(go env GOBIN)\" To install Tusk: go install If you have already installed tusk from another source, make sure you test against the development version installed locally. If you do not get (devel) as the version, you may need to move your Go binary path earlier in your PATH : $ tusk --version (devel)","title":"Setting Up a Development Environment"},{"location":"CONTRIBUTING/#making-changes","text":"If you have not yet done so, make sure you fork the repository so you can push your changes back to your own fork on GitHub. When starting work on a new feature, create a new branch based off the main branch. Pull requests should generally target the main branch, and releases will be cut separately.","title":"Making Changes"},{"location":"CONTRIBUTING/#running-tests","text":"To run the unit tests: tusk test To run the full test suite, along with golangci-lint: tusk test -a If the linter fails, execution will stop short and not actually run the unit test suite. If there is a linter error that is a false-positive, or the violation is necessary for your contribution, you can disable a specific linter for that line: cmd := exec.Command(\"sh\", \"-c\", command) //nolint:gosec","title":"Running Tests"},{"location":"spec/","text":"The Spec Tasks The core of every tusk.yml file is a list of tasks. Tasks are declared at the top level of the tusk.yml file and include a list of tasks. For the following tasks: tasks: hello: run: echo \"Hello, world!\" goodbye: run: echo \"Goodbye, world!\" The commands can be run with no additional configuration: $ tusk hello Running: echo \"Hello, world!\" Hello, world! Tasks can be documented with a one-line usage string and a slightly longer description . This information will be displayed in help messages: tasks: hello: usage: Say hello to the world description: | This command will echo \"Hello, world!\" to the user. There's no surprises here. run: echo \"Hello, world!\" goodbye: run: echo \"Goodbye, world!\" Run The behavior of a task is defined in its run clause. A run clause can be used for commands, sub-tasks, or setting environment variables. Although each run item can only perform one of these actions, they can be run in succession to handle complex scenarios. In its simplest form, run can be given a string or list of strings to be executed serially as shell commands: tasks: hello: run: echo \"Hello!\" This is a shorthand syntax for the following: tasks: hello: run: - command: exec: echo \"Hello!\" The run clause tasks a list of run items, which allow executing shell commands with command , setting or unsetting environment variables with set-environment , running other tasks with task , and controlling conditional execution with when . Command The command clause is the most common thing to do during a run , so for convenience, passing a string or single item will be correctly interpreted. Here are several examples of equivalent run clauses: run: echo \"Hello!\" run: - echo \"Hello!\" run: command: echo \"Hello!\" run: - command: echo \"Hello!\" run: - command: exec: echo \"Hello!\" While the interpreter cannot be set for an individual command, it is possible to set them globally using the interpreter clause . Exec The exec clause contains the actual shell command to be performed. If any of the run commands execute with a non-zero exit code, Tusk will immediately exit with the same exit code without executing any other commands. Each command in a run clause gets its own sub-shell, so things like declaring functions and environment variables will not be available across separate run commmands, although it is possible to run the set-environment clause or use a multi-line shell command. When using POSIX interpreters with multi-line scripts, it is recommend to run set -e at the top of the script, to preserve the exit-on-error behavior. tasks: hello: run: | set -e errcho() { >&2 echo \"$@\" } errcho \"Hello, world!\" errcho \"Goodbye, world!\" Print Sometimes it may not be desirable to print the exact command run, for example, if it's overly verbose or contains secrets. In that case, the command clause can be passed a print string to use as an alternative: tasks: hello: run: command: exec: echo \"SECRET_VALUE\" print: echo \"*****\" Quiet Sometimes you may not want to print the command-to-be-run at all. In that case, the quiet clause can be used. This is comparable to the global -q / --quiet command-line flag in that it silence's Tusk's logging without silencing the command output: tasks: hello: run: command: exec: curl http://example.com quiet: true This property can also be set for an entire task and is inherited by any sub-task. In both of these cases the executed commands are not printed: tasks: quiet-parent: quiet: true run: task: normal-child normal-child: run: curl http://example.com normal-parent: run: task: quiet-child quiet-child: quiet: true run: curl http://example.com Dir The dir clause sets the working directory for a specific command: tasks: hello: run: command: exec: echo \"Hello from $PWD!\" dir: ./subdir Set Environment To set or unset environment variables, simply define a map of environment variable names to their desired values: tasks: hello: options: proxy-url: default: http://proxy.example.com run: - set-environment: http_proxy: ${proxy-url} https_proxy: ${proxy-url} no_proxy: ~ - command: curl http://example.com Passing ~ or null to an environment variable will explicitly unset it, while passing an empty string will set it to an empty string. Environment variables once modified will persist until Tusk exits. Sub-Tasks Run can also execute previously-defined tasks: tasks: one: run: echo \"Inside one\" two: run: - task: one - command: echo \"Inside two\" For any arg or option that a sub-task defines, the parent task can pass a value, which is treated the same way as passing by command-line would be. Args are passed in as a list, while options are a map from flag name to value. To pass values, use the long definition of a sub-task: tasks: greet: args: name: usage: The person to greet options: greeting: default: Hello run: echo \"${greeting}, ${person}!\" greet-myself: run: task: name: greet args: - me options: greeting: Howdy In cases where a sub-task may not be useful on its own, define it as private to prevent it from being invoked directly from the command-line. For example: tasks: configure-environment: private: true run: set-environment: { APP_ENV: dev } serve: run: - task: configure-environment - command: python main.py When For conditional execution, when clauses are available. run: when: os: linux command: echo \"This is a linux machine\" In a run clause, any item with a true when clause will execute. There are five different checks supported: command (list): Execute if any command runs with an exit code of 0 . Commands will execute in the order defined and stop execution at the first successful command. exists (list): Execute if any of the listed files exists. not-exists (list): Execute if any of the listed files doesn't exist. os (list): Execute if the operating system matches any one from the list. environment (map[string -> list]): Execute if the environment variable matches any of the values it maps to. To check if a variable is not set, the value should be ~ or null . equal (map[string -> list]): Execute if the given option equals any of the values it maps to. not-equal (map[string -> list]): Execute if the given option is not equal to any one of the values it maps to. The when clause supports any number of different checks as a list, where each check must pass individually for the clause to evaluate to true. Here is a more complicated example of how when can be used: tasks: echo: options: cat: usage: Cat a file run: - when: os: - linux - darwin command: echo \"This is a unix machine\" - when: - exists: my_file.txt - equal: { cat: true } - command: command -v cat command: cat my_file.txt Short Form Because it's common to check if a boolean flag is set to true, when clauses also accept strings as shorthand. Consider the following example, which checks to see if some option foo has been set to true : when: equal: { foo: true } This can be expressed more succinctly as the following: when: foo When Any/All Logic A when clause takes a list of items, where each item can have multiple checks. Each when item will pass if any of the checks pass, while the whole clause will only pass if all of the items pass. For example: tasks: exists: run: - when: # There is a single `when` item with two checks exists: - file_one.txt - file_two.txt command: echo \"At least one file exists\" - when: # There are two separate `when` items with one check each - exists: file_one.txt - exists: file_two.txt command: echo \"Both files exist\" These properties can be combined for more complicated logic: tasks: echo: options: verbose: type: bool ignore-os: type: bool run: - when: # (OS is linux OR darwin OR ignore OS is true) AND (verbose is true) - os: - linux - darwin equal: { ignore-os: true } - equal: { verbose: true } command: echo \"This is a unix machine\" Args Tasks may have args that are passed directly as inputs. Any arg that is defined is required for the task to execute. tasks: greet: args: name: usage: The person to greet run: echo \"Hello, ${name}!\" The task can be invoked as such: $ tusk greet friend Hello, friend! Arg Types Args can be of the types string , integer , float , or boolean . Args without types specified are considered strings. tasks: add: args: a: type: int b: type: int run: echo $((${a} + ${b})) Arg Values Args can specify which values are considered valid: tasks: greet: args: name: values: - Abby - Bobby - Carl Any value passed by command-line must be one of the listed values, or the command will fail to execute. Options Tasks may have options that are passed as GNU-style flags. The following configuration will provide -n, --name flags to the CLI and help documentation, which will then be interpolated: tasks: greet: options: name: usage: The person to greet short: n environment: GREET_NAME default: World run: echo \"Hello, ${name}!\" The above configuration will evaluate the value of name in order of highest priority: The value passed by command line flags ( -n or --name ) The value of the environment variable ( GREET_NAME ), if set The value set in default For short flag names, values can be combined such that tusk foo -ab is exactly equivalent to tusk foo -a -b . Option Types Options can be of the types string , integer , float , or boolean , using the zero-value of that type as the default if not set. Options without types specified are considered strings. For boolean values, the flag should be passed by command line without any arugments. In the following example: tasks: greet: options: loud: type: bool run: - when: equal: { loud: true } command: echo \"HELLO!\" - when: equal: { loud: false } command: echo \"Hello.\" The flag should be passed as such: tusk greet --loud This means that for an option that is true by default, the only way to disable it is with the following syntax: tusk greet --loud=false Of course, options can always be defined in the reverse manner to avoid this issue: options: no-loud: type: bool Option Defaults Much like run clauses accept a shorthand form, passing a string to default is shorthand. The following options are exactly equivalent: options: short: default: foo long: default: - value: foo A default clause can also register the stdout of a command as its value: options: os: default: command: uname -s A default clause also accepts a list of possible values with a corresponding when clause. The first when that evaluates to true will be used as the default value, with an omitted when always considered true. In this example, linux users will have the name Linux User , while the default for all other OSes is User : options: name: default: - when: os: linux value: Linux User - value: User Option Values Like args, an option can specify which values are considered valid: options: number: default: zero values: - one - two - three Any value passed by command-line flags or environment variables must be one of the listed values. Default values, including commands, are excluded from this requirement. Required Options Options may be required if there is no sane default value. For a required flag, the task will not execute unless the flag is passed: options: file: required: true A required option cannot be private or have any default values. Private Options Sometimes it may be desirable to have a variable that cannot be directly modified through command-line flags. In this case, use the private option: options: user: private: true default: command: whoami A private option will not accept environment variables or command line flags, and it will not appear in the help documentation. Option Rewrite Boolean values are convenient as CLI inputs, but the interpolated output of true or false is often not. Use the rewrite clause to change the interpolation behavior from true / false to a conditional specified string: tasks: greet: options: verbose: type: boolean rewrite: --level=verbose run: mycli greet ${verbose} The above will interpolate to eitehr mycli greet or mycli greet --level=verbose depending on whether the --verbose flag is passed. Note that once a boolean option has been rewritten, the output is no longer true / false , which means when: verbose in the above example would never evaluate to true. Shared Options Options may also be defined at the root of the config file to be shared between tasks: options: name: usage: The person to greet default: World tasks: hello: run: echo \"Hello, ${name}!\" goodbye: run: echo \"Goodbye, ${name}!\" Any shared variables referenced by a task will be exposed by command-line when invoking that task. Shared variables referenced by a sub-task will be evaluated as needed, but not exposed by command-line. Tasks that define an argument or option with the same name as a shared task will overwrite the value of the shared option for the length of that task, not including sub-tasks. Finally The finally clause is run after a task's run logic has completed, whether or not that task was successful. This can be useful for clean-up logic. A finally clause has the same format as a run clause: tasks: hello: run: - echo \"Hello\" - exit 1 # `run` clause stops here - echo \"Oops!\" # Never prints finally: - echo \"Goodbye\" # Always prints - task: cleanup # ... If the finally clause runs an unsuccessful command, it will terminate early the same way that a run clause would. The exit code is still passed back to the command line. However, if both the run clause and finally clause fail, the exit code from the run clause takes precedence. Include In some cases it may be desirable to split the task definition into a separate file. The include clause serves this purpose. At the top-level of a task, a task may optionally be specified using just the include key, which maps to a separate file where there task definition is stored. For example, tusk.yml could be written like this: tasks: hello: include: .tusk/hello.yml With a .tusk/hello.yml that looks like this: options: name: usage: The person to greet default: World run: echo \"Hello, ${name}!\" It is invalid to split the configuration; if the include clause is used, no other keys can be specified in the tusk.yml , and the full task must be defined in the included file. Environment Files Environment variables are also automatically read from a .env file in the same directory as tusk.yml before task execution. This file is optional by default, and supports typical \"dotenv\" extended syntax such as quotation marks, comments, variable substitution, and the export keyword. A typical file might look like this: FOO=foovalue BAR=barvalue Environment files can be explicitly specified as configuration at the top-level: env-file: .local.env This is shorthand syntax for the following: env-file: - path: .local.env required: true Multiple environment files can be specified. Entries are evaluted in order, so environment variables from later files override values specified in previous entries. Specifying any value for env-file will disable the default behavior of auto-loading an optional .env . To re-enable it, specify it explicitly: env-file: - path: .env required: false - .local.env To disable loading environment files completely, pass [] or /dev/null : env-file: [] Interpreter By default, any command run will default to using sh -c as its interpreter. This can optionally be configured using the interpreter clause. The interpreter is specified as an executable, which can either be an absolute path or available on the user's PATH, followed by a series of optional arguments: interpreter: node -e tasks: hello: run: console.log(\"Hello!\") The commands specified in individual tasks will be passed as the final argument. The above example is effectively equivalent to the following: node -e 'console.log(\"Hello!\")' CLI Metadata It is also possible to create a custom CLI tool for use outside of a project's directory by using shell aliases: alias mycli=\"tusk -f /path/to/tusk.yml\" In that case, it may be useful to override the tool name and usage text that are provided as part of the help documentation: name: mycli usage: A custom aliased command-line application tasks: # ... The example above will produce the following help documentation: $ tusk --help mycli - A custom aliased command-line application Usage: mycli [global options] [task options] Tasks: ... Interpolation The interpolation syntax for a variable foo is ${foo} , meaning any instances of ${foo} in the configuration file will be replaced with the value of foo during execution. Interpolation is done on a task-by-task basis, meaning args and options defined in one task will not interpolate to any other tasks. Shared options, on the other hand, will only be evaluated once per execution. The execution order is as followed: Shared options are interpolated first, in the order defined by the config file. The results of global interpolation are cached and not re-run. The args for the current task being run are interpolated, in order. The options for the current task being run are interpolated, in order. For each call to a sub-task, the process is repeated, ignoring the task- specific interpolations for parent tasks, using the cached shared options. This means that options can reference other options or args: options: name: default: World greeting: default: Hello, ${name} tasks: greet: run: echo \"${greeting}\" Because interpolation is not always desirable, as in the case of environment variables, $$ will escape to $ and ignore interpolation. It is also possible to use alternative syntax such as $foo to avoid interpolation as well. The following two tasks will both use environment variables and not attempt interpolation: tasks: one: run: Hello, $${USER} two: run: Hello, $USER Interpolation works by substituting the value in the yaml config file, then parsing the file after interpolation. This means that variable values with newlines or other characters that are relevant to the yaml spec or the sh interpreter will need to be considered by the user. This can be as simple as using quotes when appropriate.","title":"The Spec"},{"location":"spec/#the-spec","text":"","title":"The Spec"},{"location":"spec/#tasks","text":"The core of every tusk.yml file is a list of tasks. Tasks are declared at the top level of the tusk.yml file and include a list of tasks. For the following tasks: tasks: hello: run: echo \"Hello, world!\" goodbye: run: echo \"Goodbye, world!\" The commands can be run with no additional configuration: $ tusk hello Running: echo \"Hello, world!\" Hello, world! Tasks can be documented with a one-line usage string and a slightly longer description . This information will be displayed in help messages: tasks: hello: usage: Say hello to the world description: | This command will echo \"Hello, world!\" to the user. There's no surprises here. run: echo \"Hello, world!\" goodbye: run: echo \"Goodbye, world!\"","title":"Tasks"},{"location":"spec/#run","text":"The behavior of a task is defined in its run clause. A run clause can be used for commands, sub-tasks, or setting environment variables. Although each run item can only perform one of these actions, they can be run in succession to handle complex scenarios. In its simplest form, run can be given a string or list of strings to be executed serially as shell commands: tasks: hello: run: echo \"Hello!\" This is a shorthand syntax for the following: tasks: hello: run: - command: exec: echo \"Hello!\" The run clause tasks a list of run items, which allow executing shell commands with command , setting or unsetting environment variables with set-environment , running other tasks with task , and controlling conditional execution with when .","title":"Run"},{"location":"spec/#command","text":"The command clause is the most common thing to do during a run , so for convenience, passing a string or single item will be correctly interpreted. Here are several examples of equivalent run clauses: run: echo \"Hello!\" run: - echo \"Hello!\" run: command: echo \"Hello!\" run: - command: echo \"Hello!\" run: - command: exec: echo \"Hello!\" While the interpreter cannot be set for an individual command, it is possible to set them globally using the interpreter clause .","title":"Command"},{"location":"spec/#exec","text":"The exec clause contains the actual shell command to be performed. If any of the run commands execute with a non-zero exit code, Tusk will immediately exit with the same exit code without executing any other commands. Each command in a run clause gets its own sub-shell, so things like declaring functions and environment variables will not be available across separate run commmands, although it is possible to run the set-environment clause or use a multi-line shell command. When using POSIX interpreters with multi-line scripts, it is recommend to run set -e at the top of the script, to preserve the exit-on-error behavior. tasks: hello: run: | set -e errcho() { >&2 echo \"$@\" } errcho \"Hello, world!\" errcho \"Goodbye, world!\"","title":"Exec"},{"location":"spec/#print","text":"Sometimes it may not be desirable to print the exact command run, for example, if it's overly verbose or contains secrets. In that case, the command clause can be passed a print string to use as an alternative: tasks: hello: run: command: exec: echo \"SECRET_VALUE\" print: echo \"*****\"","title":"Print"},{"location":"spec/#quiet","text":"Sometimes you may not want to print the command-to-be-run at all. In that case, the quiet clause can be used. This is comparable to the global -q / --quiet command-line flag in that it silence's Tusk's logging without silencing the command output: tasks: hello: run: command: exec: curl http://example.com quiet: true This property can also be set for an entire task and is inherited by any sub-task. In both of these cases the executed commands are not printed: tasks: quiet-parent: quiet: true run: task: normal-child normal-child: run: curl http://example.com normal-parent: run: task: quiet-child quiet-child: quiet: true run: curl http://example.com","title":"Quiet"},{"location":"spec/#dir","text":"The dir clause sets the working directory for a specific command: tasks: hello: run: command: exec: echo \"Hello from $PWD!\" dir: ./subdir","title":"Dir"},{"location":"spec/#set-environment","text":"To set or unset environment variables, simply define a map of environment variable names to their desired values: tasks: hello: options: proxy-url: default: http://proxy.example.com run: - set-environment: http_proxy: ${proxy-url} https_proxy: ${proxy-url} no_proxy: ~ - command: curl http://example.com Passing ~ or null to an environment variable will explicitly unset it, while passing an empty string will set it to an empty string. Environment variables once modified will persist until Tusk exits.","title":"Set Environment"},{"location":"spec/#sub-tasks","text":"Run can also execute previously-defined tasks: tasks: one: run: echo \"Inside one\" two: run: - task: one - command: echo \"Inside two\" For any arg or option that a sub-task defines, the parent task can pass a value, which is treated the same way as passing by command-line would be. Args are passed in as a list, while options are a map from flag name to value. To pass values, use the long definition of a sub-task: tasks: greet: args: name: usage: The person to greet options: greeting: default: Hello run: echo \"${greeting}, ${person}!\" greet-myself: run: task: name: greet args: - me options: greeting: Howdy In cases where a sub-task may not be useful on its own, define it as private to prevent it from being invoked directly from the command-line. For example: tasks: configure-environment: private: true run: set-environment: { APP_ENV: dev } serve: run: - task: configure-environment - command: python main.py","title":"Sub-Tasks"},{"location":"spec/#when","text":"For conditional execution, when clauses are available. run: when: os: linux command: echo \"This is a linux machine\" In a run clause, any item with a true when clause will execute. There are five different checks supported: command (list): Execute if any command runs with an exit code of 0 . Commands will execute in the order defined and stop execution at the first successful command. exists (list): Execute if any of the listed files exists. not-exists (list): Execute if any of the listed files doesn't exist. os (list): Execute if the operating system matches any one from the list. environment (map[string -> list]): Execute if the environment variable matches any of the values it maps to. To check if a variable is not set, the value should be ~ or null . equal (map[string -> list]): Execute if the given option equals any of the values it maps to. not-equal (map[string -> list]): Execute if the given option is not equal to any one of the values it maps to. The when clause supports any number of different checks as a list, where each check must pass individually for the clause to evaluate to true. Here is a more complicated example of how when can be used: tasks: echo: options: cat: usage: Cat a file run: - when: os: - linux - darwin command: echo \"This is a unix machine\" - when: - exists: my_file.txt - equal: { cat: true } - command: command -v cat command: cat my_file.txt","title":"When"},{"location":"spec/#short-form","text":"Because it's common to check if a boolean flag is set to true, when clauses also accept strings as shorthand. Consider the following example, which checks to see if some option foo has been set to true : when: equal: { foo: true } This can be expressed more succinctly as the following: when: foo","title":"Short Form"},{"location":"spec/#when-anyall-logic","text":"A when clause takes a list of items, where each item can have multiple checks. Each when item will pass if any of the checks pass, while the whole clause will only pass if all of the items pass. For example: tasks: exists: run: - when: # There is a single `when` item with two checks exists: - file_one.txt - file_two.txt command: echo \"At least one file exists\" - when: # There are two separate `when` items with one check each - exists: file_one.txt - exists: file_two.txt command: echo \"Both files exist\" These properties can be combined for more complicated logic: tasks: echo: options: verbose: type: bool ignore-os: type: bool run: - when: # (OS is linux OR darwin OR ignore OS is true) AND (verbose is true) - os: - linux - darwin equal: { ignore-os: true } - equal: { verbose: true } command: echo \"This is a unix machine\"","title":"When Any/All Logic"},{"location":"spec/#args","text":"Tasks may have args that are passed directly as inputs. Any arg that is defined is required for the task to execute. tasks: greet: args: name: usage: The person to greet run: echo \"Hello, ${name}!\" The task can be invoked as such: $ tusk greet friend Hello, friend!","title":"Args"},{"location":"spec/#arg-types","text":"Args can be of the types string , integer , float , or boolean . Args without types specified are considered strings. tasks: add: args: a: type: int b: type: int run: echo $((${a} + ${b}))","title":"Arg Types"},{"location":"spec/#arg-values","text":"Args can specify which values are considered valid: tasks: greet: args: name: values: - Abby - Bobby - Carl Any value passed by command-line must be one of the listed values, or the command will fail to execute.","title":"Arg Values"},{"location":"spec/#options","text":"Tasks may have options that are passed as GNU-style flags. The following configuration will provide -n, --name flags to the CLI and help documentation, which will then be interpolated: tasks: greet: options: name: usage: The person to greet short: n environment: GREET_NAME default: World run: echo \"Hello, ${name}!\" The above configuration will evaluate the value of name in order of highest priority: The value passed by command line flags ( -n or --name ) The value of the environment variable ( GREET_NAME ), if set The value set in default For short flag names, values can be combined such that tusk foo -ab is exactly equivalent to tusk foo -a -b .","title":"Options"},{"location":"spec/#option-types","text":"Options can be of the types string , integer , float , or boolean , using the zero-value of that type as the default if not set. Options without types specified are considered strings. For boolean values, the flag should be passed by command line without any arugments. In the following example: tasks: greet: options: loud: type: bool run: - when: equal: { loud: true } command: echo \"HELLO!\" - when: equal: { loud: false } command: echo \"Hello.\" The flag should be passed as such: tusk greet --loud This means that for an option that is true by default, the only way to disable it is with the following syntax: tusk greet --loud=false Of course, options can always be defined in the reverse manner to avoid this issue: options: no-loud: type: bool","title":"Option Types"},{"location":"spec/#option-defaults","text":"Much like run clauses accept a shorthand form, passing a string to default is shorthand. The following options are exactly equivalent: options: short: default: foo long: default: - value: foo A default clause can also register the stdout of a command as its value: options: os: default: command: uname -s A default clause also accepts a list of possible values with a corresponding when clause. The first when that evaluates to true will be used as the default value, with an omitted when always considered true. In this example, linux users will have the name Linux User , while the default for all other OSes is User : options: name: default: - when: os: linux value: Linux User - value: User","title":"Option Defaults"},{"location":"spec/#option-values","text":"Like args, an option can specify which values are considered valid: options: number: default: zero values: - one - two - three Any value passed by command-line flags or environment variables must be one of the listed values. Default values, including commands, are excluded from this requirement.","title":"Option Values"},{"location":"spec/#required-options","text":"Options may be required if there is no sane default value. For a required flag, the task will not execute unless the flag is passed: options: file: required: true A required option cannot be private or have any default values.","title":"Required Options"},{"location":"spec/#private-options","text":"Sometimes it may be desirable to have a variable that cannot be directly modified through command-line flags. In this case, use the private option: options: user: private: true default: command: whoami A private option will not accept environment variables or command line flags, and it will not appear in the help documentation.","title":"Private Options"},{"location":"spec/#option-rewrite","text":"Boolean values are convenient as CLI inputs, but the interpolated output of true or false is often not. Use the rewrite clause to change the interpolation behavior from true / false to a conditional specified string: tasks: greet: options: verbose: type: boolean rewrite: --level=verbose run: mycli greet ${verbose} The above will interpolate to eitehr mycli greet or mycli greet --level=verbose depending on whether the --verbose flag is passed. Note that once a boolean option has been rewritten, the output is no longer true / false , which means when: verbose in the above example would never evaluate to true.","title":"Option Rewrite"},{"location":"spec/#shared-options","text":"Options may also be defined at the root of the config file to be shared between tasks: options: name: usage: The person to greet default: World tasks: hello: run: echo \"Hello, ${name}!\" goodbye: run: echo \"Goodbye, ${name}!\" Any shared variables referenced by a task will be exposed by command-line when invoking that task. Shared variables referenced by a sub-task will be evaluated as needed, but not exposed by command-line. Tasks that define an argument or option with the same name as a shared task will overwrite the value of the shared option for the length of that task, not including sub-tasks.","title":"Shared Options"},{"location":"spec/#finally","text":"The finally clause is run after a task's run logic has completed, whether or not that task was successful. This can be useful for clean-up logic. A finally clause has the same format as a run clause: tasks: hello: run: - echo \"Hello\" - exit 1 # `run` clause stops here - echo \"Oops!\" # Never prints finally: - echo \"Goodbye\" # Always prints - task: cleanup # ... If the finally clause runs an unsuccessful command, it will terminate early the same way that a run clause would. The exit code is still passed back to the command line. However, if both the run clause and finally clause fail, the exit code from the run clause takes precedence.","title":"Finally"},{"location":"spec/#include","text":"In some cases it may be desirable to split the task definition into a separate file. The include clause serves this purpose. At the top-level of a task, a task may optionally be specified using just the include key, which maps to a separate file where there task definition is stored. For example, tusk.yml could be written like this: tasks: hello: include: .tusk/hello.yml With a .tusk/hello.yml that looks like this: options: name: usage: The person to greet default: World run: echo \"Hello, ${name}!\" It is invalid to split the configuration; if the include clause is used, no other keys can be specified in the tusk.yml , and the full task must be defined in the included file.","title":"Include"},{"location":"spec/#environment-files","text":"Environment variables are also automatically read from a .env file in the same directory as tusk.yml before task execution. This file is optional by default, and supports typical \"dotenv\" extended syntax such as quotation marks, comments, variable substitution, and the export keyword. A typical file might look like this: FOO=foovalue BAR=barvalue Environment files can be explicitly specified as configuration at the top-level: env-file: .local.env This is shorthand syntax for the following: env-file: - path: .local.env required: true Multiple environment files can be specified. Entries are evaluted in order, so environment variables from later files override values specified in previous entries. Specifying any value for env-file will disable the default behavior of auto-loading an optional .env . To re-enable it, specify it explicitly: env-file: - path: .env required: false - .local.env To disable loading environment files completely, pass [] or /dev/null : env-file: []","title":"Environment Files"},{"location":"spec/#interpreter","text":"By default, any command run will default to using sh -c as its interpreter. This can optionally be configured using the interpreter clause. The interpreter is specified as an executable, which can either be an absolute path or available on the user's PATH, followed by a series of optional arguments: interpreter: node -e tasks: hello: run: console.log(\"Hello!\") The commands specified in individual tasks will be passed as the final argument. The above example is effectively equivalent to the following: node -e 'console.log(\"Hello!\")'","title":"Interpreter"},{"location":"spec/#cli-metadata","text":"It is also possible to create a custom CLI tool for use outside of a project's directory by using shell aliases: alias mycli=\"tusk -f /path/to/tusk.yml\" In that case, it may be useful to override the tool name and usage text that are provided as part of the help documentation: name: mycli usage: A custom aliased command-line application tasks: # ... The example above will produce the following help documentation: $ tusk --help mycli - A custom aliased command-line application Usage: mycli [global options] [task options] Tasks: ...","title":"CLI Metadata"},{"location":"spec/#interpolation","text":"The interpolation syntax for a variable foo is ${foo} , meaning any instances of ${foo} in the configuration file will be replaced with the value of foo during execution. Interpolation is done on a task-by-task basis, meaning args and options defined in one task will not interpolate to any other tasks. Shared options, on the other hand, will only be evaluated once per execution. The execution order is as followed: Shared options are interpolated first, in the order defined by the config file. The results of global interpolation are cached and not re-run. The args for the current task being run are interpolated, in order. The options for the current task being run are interpolated, in order. For each call to a sub-task, the process is repeated, ignoring the task- specific interpolations for parent tasks, using the cached shared options. This means that options can reference other options or args: options: name: default: World greeting: default: Hello, ${name} tasks: greet: run: echo \"${greeting}\" Because interpolation is not always desirable, as in the case of environment variables, $$ will escape to $ and ignore interpolation. It is also possible to use alternative syntax such as $foo to avoid interpolation as well. The following two tasks will both use environment variables and not attempt interpolation: tasks: one: run: Hello, $${USER} two: run: Hello, $USER Interpolation works by substituting the value in the yaml config file, then parsing the file after interpolation. This means that variable values with newlines or other characters that are relevant to the yaml spec or the sh interpreter will need to be considered by the user. This can be as simple as using quotes when appropriate.","title":"Interpolation"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml index 7da293d..6e35a91 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,17 +2,17 @@ None - 2024-08-07 + 2025-01-05 daily None - 2024-08-07 + 2025-01-05 daily None - 2024-08-07 + 2025-01-05 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 4dd2ad5466c355fb362af79be02feef9d6ce0ddc..1e634f26cb10c9879bfc7fabaee4d08faaa83e2e 100644 GIT binary patch literal 196 zcmV;#06YI5iwFn_*m`FI|8r?{Wo=<_E_iKh0Oe834#FT1y!R^z_X2IwL=B}kkDl}c z2*s9!LS@nF-%B4c{Rii=v$K<#!}8`mSoG2XjCVCI2*XG_F40Zo0RRAe6j@{d literal 196 zcmV;#06YI5iwFoRc(Y~#|8r?{Wo=<_E_iKh0Oe834#FT1y!R^z_p;cSSPi8&kDl}c z2*s9!LIt$?_tHm9|G~NJ?CfObu&jRf2ECLI+Sv;42*pTQ=^Aa@3g52hyu>xn*wdS! zB1&c(D%{05oB+mgBtb{jiy;u_TL`jCOMq1bN{*OkC}9d%*bHWz