--- layout: layouts/base.njk --- # Usage Running the task file requires a shell alias: `alias task='./task'`\ Show the available commands with `task help`.\ From the specification the project can be installed with `task install`.\ Execute all commands with `task all`. ## Image See [taskfile.build/image](/image) for details. ## Naming The naming of functions is important. There are basically two styles: 1. Action + Object 2. Object + Action The task file functions use the first style. The name of the function starts with an action followed by an object. The object name can be singular or plural. **Examples for actions**: activate, install, dev, develop, init, build, start, update, remove, delete, enable, disable, template, convert, create, edit, change, get, set, patch, fetch, generate, push, pull, import, export, list, publish, release, test, setup, prepare, restart, stop, store, restore, translate, upgrade, zip, visualize, sync, switch, run, reset, load, dump, checkout, commit, drop, deploy, handle, trigger, render, lint, uninstall, split, parse, fix, refactor, transform, cat, ls, rm, serve, help, show, filter, login, logout, encrypt, decrypt, upload, download, analyse, transpile, compile, minify, copy **Examples for objects**: env, venv, submodule, container, database, snippet, model, module, repo, mail, doc, dependency, view, user, vault, file, host, node, log, password, hash, script, requirement, part, component, system, workspace, image, process, state, platform, dir, folder, readme, overview, lang, level, request, response, result, worker, server, proxy, workflow, volume, network, package, field, value, secret, chart, node, edge, function, method, firewall, html, css, image, svg, style, query, native, group, notebook **Objects can be tools**: odoo, vupress, nodejs, zsh, bash, fish, podman, kind, minikube, helm, nvim, docker, podman, rust, python, tmux, vim, helix, system, git, pass, llm, sql, dotenv, javascript, vue, vite, astro, typescript, turbo, pnpm, eslint, jenkins, k8s, nextcloud, postgres, metabase, ansible, prometheus, grafana, hugo, deno, bun, babel, panda, gulp, grunt, electron, react, express, mongodb, angular, ionic, meteor, webpack, bower, jupyter ## Style The bash scripts follow the [Bash Style Guide | ysap.sh](https://style.ysap.sh/). Have a look at the [AGENTS.md](/AGENTS) for details on the applied styling. ## Patterns The task file showed above is very basic. Commands can have parameters and call each other. The following is a collection of more complex task file patterns. ### Set default parameter Fallback to a default value for a parameter. ```bash build() { local platform="amd64" if [[ -n "$1" ]]; then platform="$1" fi ``` ### Ensure parameter is not empty Check the first param and exit if it is empty. ```bash deploy() { if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi ``` ### Prompt for input Use `read` to ask for inputs. ```bash if [[ -z "$2" ]]; then read -p "Enter the task description: " task_description else task_description="$2" fi ``` ### Setup local env vars Define env vars at the beginning of the task file. ```bash CONFIGURATION_FILE="file.conf" GIT_BRANCH=$(git symbolic-ref --short -q HEAD) ``` ### Template with env vars Create a parameterized file from a template. Requires `envsubst`. ```bash template-with-env() { echo "Template $CONFIGURATION_FILE" export CONFIGURATION_1 export CONFIGURATION_2=${CONFIGURATION_2:="value"} envsubst < "file.conf.template" > "$CONFIGURATION_FILE" } ``` ### Create Python virtual env Initialize Python virtual env with uv. ```bash init-venv() { if [[ ! -d "venv$GIT_BRANCH" ]]; then echo "Init venv$GIT_BRANCH with $(uv --version)." uv venv "venv$GIT_BRANCH" fi } ``` ### Activate Python virtual env ```bash activate-venv() { echo "Source virtualenv venv$GIT_BRANCH." source "venv$GIT_BRANCH/bin/activate" echo "$(python --version) is active." } ``` ### Call a Python script Run a Python script. ```bash generate-password-hash() { activate-venv if [[ -z "$1" ]]; then echo "\$1 is empty."; exit; fi export PASSWORD_PLAIN="$1" bin/password-hash } ``` The Python script: `bin/password-hash` ```python #!/usr/bin/env python3 import os from passlib.context import CryptContext crypt_context = CryptContext(schemes=['pbkdf2_sha512', 'plaintext'], deprecated=['plaintext']) password = os.environ.get('PASSWORD_PLAIN') print(crypt_context.hash(password)) ``` ### Command with named parameters Assuming you have a `docker-compose.yml` and would like to start selected or all containers. ```bash start() { if [[ "$1" =~ "db" ]]; then docker compose up -d db fi if [[ "$1" =~ "admin" ]]; then docker compose up -d admin echo "Open http://localhost:8000 url in your browser." fi if [[ "$1" =~ "odoo" ]]; then docker compose up -d odoo echo "Open http://localhost:8069 url in your browser." fi if [[ "$1" =~ "mail" ]]; then docker compose up -d mail echo "Open http://localhost:8025 url in your browser." fi } ``` Start all containers with `task start` and selected with `task start db,admin`. ### Run commands in container Use `docker exec -i` to run commands in a container. ```bash drop-db() { local database="$1" if [[ -z "$database" ]]; then database="example" fi docker exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = '$database';" docker exec -i db psql "postgres://odoo:odoo@localhost:5432/postgres" -c "DROP DATABASE \"$database\";" } ``` ### Loop over files and folders Use `for` to loop over file, folders or arrays. ```bash render() { echo "Update index.html for all folders" for folder in ./*; do if [[ -f "$folder/README.md" ]]; then cd "$folder" || exit md2html README.md _site/index.html cd .. || exit fi done } ``` ### Parse and convert a file With this function you can split a file into multiple parts whenever a specific keyword is matching. In this example the keyword is `!vault`. ```bash convert-vault-file() { local file_path="$1" local temp_file=$(mktemp) local temp_part_file=$(mktemp) local write_finished=false local current_key="" while IFS= read -r line; do # Check for keyword if [[ "$line" =~ "!vault" ]]; then # Process previous vault entry if it exists if [[ "$write_finished" = true ]] && [[ -n "$current_key" ]] && [[ -s "$temp_part_file" ]]; then # Decrypt part file and write to assemble file ansible-vault decrypt "$temp_part_file" local value=$(cat "$temp_part_file") echo "$current_key: $value" >> "$temp_file" fi # Set up for new vault entry current_key=$(echo "$line" | cut -d':' -f1) # Clear the file : > "$temp_part_file" # Flag as ready to write write_finished=true else if [[ "$write_finished" = true ]]; then # Pipe into part file echo "$line" >> "$temp_part_file" fi done done < "$file_path" # Process the final vault entry if [[ "$write_finished" = true ]] && [[ -n "$current_key" ]] && [[ -s "$temp_part_file" ]]; then ansible-vault decrypt "$temp_part_file" local value=$(cat "$temp_part_file") echo "$current_key: $value" >> "$temp_file" fi # Output assembled file cat "$temp_file" # Cleanup temp files rm -f "$temp_file" rm -f "$temp_part_file" } ``` ### Process data This pattern tries to match the worklow of a jupyter notebook. Create a folder with the data processing scripts: ``` scripts ├── 01_import-mail-data ├── 02_transform-mail-data └── 03_export-mail-data ``` The following function lists the scripts and asks for the number of scripts to run. Every script until the entered number is concatenated and executed. ```bash process-data() { while true; do local scripts=($(ls scripts)) echo -e "Available scripts:\n" for index in "${!scripts[@]}"; do echo "$((index+1)): ${scripts[$index]}" done echo "" read -p "Enter the number of scripts to run (or 'q' to quit): " user_input if [[ "$user_input" = "q" ]]; then break fi local temp_file=$(mktemp) for index in "${!scripts[@]}"; do if (( $user_input >= $((index+1)) )); then cat "scripts/${scripts[$index]}" >> "$temp_file" echo >> "$temp_file" fi done clear echo -e "\nPython Output:\n\n---" python "$temp_file" local exit_code=$? echo -e "---\n" echo -e "Exit code: $exit_code\n" rm "$temp_file" done } ``` ## Integration The task file is intended to be integrated into the shell setup. Where applications provides a domain specific language (DSL) ensure that the DSL only calls the task file. ### Completion With the output of `task help` the task commands can be completed. Completion for bash: `/etc/bash_completion.d/task_completions` ```bash #!/usr/bin/env bash _task_completions() { local cur prev commands cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" commands=$(./task help | grep -A999 -e '---' | awk '{print $2}' | sed 's/^[[:space:]]*//' | grep -v '^$' | tr '\n' ' ') if [[ ${COMP_CWORD} == 1 ]]; then COMPREPLY=( $(compgen -W "${commands}" -- "${cur}") ) elif [[ ${COMP_CWORD} == 2 ]]; then COMPREPLY=( $(compgen -f -- "${cur}") ) else COMPREPLY=() fi } complete -F _task_completions task complete -F _task_completions t ``` Completion for zsh: `~/.oh-my-zsh/completions/_task` ```bash #compdef task t _arguments '1: :->tasks' '*: :_files' case "$state" in tasks) args=$(./task help | grep -A999 -e '---' | awk '{print $2}' | sed 's/^[[:space:]]*//' | grep -v '^$' | tr '\n' ' ') args="$args help" _arguments "1:profiles:($args)" ;; esac ``` The completion also works for task alias `alias t='./task'`. ### GitHub Actions Running task file commands in GitHub Actions is highly recommended. This way you can run the same CI/CD procedures in the GitHub runner as you do on your localhost. The GitHub Actions config is simple: `.github/workflows/test.yml ` ```yml on: pull_request: branches: - 'main' push: branches: - 'main' jobs: task-all: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.12' - name: Install uv uses: astral-sh/setup-uv@v5 - name: Install run: ./task install - name: Lint run: ./task lint ``` ### Jenkins Run task file commands in Jenkins: `Jenkinsfile` ```groovy pipeline { agent any stages { stage('version') { steps { script { currentBuild.description = sh (script: 'git log -1 --pretty=%B', returnStdout: true).trim() } sh './task version' } } stage('install') { steps { sh './task install' } } stage('lint') { steps { sh './task lint' } } } }% ```