- Split README.md into usage.md and library.md for better organization - Move library section to its own page and update references - Rename "Usage" section to "Examples" in README.md - Update navigation links in base layout to reflect new structure - Fix typo in function definition in image/README.md - Add frontmatter to AGENTS.md for proper layout rendering - Update footer link from GitHub to Codeberg
420 lines
12 KiB
Markdown
420 lines
12 KiB
Markdown
---
|
|
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**: <span id="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</span>
|
|
|
|
**Examples for objects**: <span id="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</span>
|
|
|
|
**Objects can be tools**: <span id="object-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</span>
|
|
|
|
<button id="generate-function-names">Generate Function Names</button> <ul id="function-names"> <!-- Random function names will be inserted here by JS --> </ul>
|
|
|
|
## 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'
|
|
}
|
|
}
|
|
}
|
|
}%
|
|
``` |