diff --git a/README.md b/README.md
index 7946395..23944fe 100644
--- a/README.md
+++ b/README.md
@@ -20,31 +20,31 @@ It is to be understood as a software development pattern to standardize the inst
The specification is a short guide to set up a task file for a Python project.
-- Create a file `task` in your project.
+Create a file `task` in your project.
```bash
touch task
```
-- Ensure it is executable.
+Ensure it is executable.
```bash
chmod +x task
```
-- First add the bash shebang.
+First add the bash shebang.
```bash
#!/usr/bin/env bash
```
-- Then append the abort on error setting.
+Then append the abort on error setting.
```bash
set -e
```
-- Load environment variables from the `.env` file.
+Load environment variables from the `.env` file.
```bash
if [[ -a ".env" ]]; then
@@ -52,7 +52,7 @@ if [[ -a ".env" ]]; then
fi
```
-- Add a help function.
+Add a help function.
```bash
help-table() {
@@ -83,7 +83,7 @@ help() {
}
```
-- Setup the command functions.
+Setup the command functions.
```bash
version() {
@@ -105,7 +105,7 @@ lint() {
}
```
-- Finish the task file with command switch cases.
+Finish the task file with command switch cases.
```bash
if declare -f "$1" > /dev/null; then
@@ -127,17 +127,25 @@ fi
These are the main parts of every task file script.
-## Examples
+Now see [usage](/usage) on how to use the task file.
-Checkout these implementations of the task file standard:
+## Naming
-- [janikvonrotz/dotfiles](https://codeberg.org/janikvonrotz/dotfiles/src/main/task)
-- [janikvonrotz/python.casa](https://codeberg.org/janikvonrotz/python.casa/src/main/task)
-- [Mint-System/Ansible-Build](https://github.com/Mint-System/Ansible-Build/blob/main/task)
-- [Mint-System/Kubernetes-Build](https://github.com/Mint-System/Kubernetes-Build/blob/main/task)
-- [Mint-System/Odoo-Build](https://github.com/Mint-System/Odoo-Build/blob/main/task)
-- [Mint-System/Odoo-Wiki](https://github.com/Mint-Wiki/Odoo-Build/blob/main/task)
-- [Mint-System/Website](https://github.com/Mint-System/Website/blob/main/task)
-- [Mint-System/Wiki](https://github.com/Mint-System/Wiki/blob/main/task)
+The naming of functions is important. There are basically two styles:
-This website is built with a `task` file. Here is the source: [janikvonrotz/taskfile.build](https://codeberg.org/janikvonrotz/taskfile.build/src/branch/main/task)
+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.
diff --git a/_includes/layouts/base.njk b/_includes/layouts/base.njk
index 46142d0..143e1bc 100644
--- a/_includes/layouts/base.njk
+++ b/_includes/layouts/base.njk
@@ -28,11 +28,10 @@
diff --git a/examples.md b/examples.md
new file mode 100644
index 0000000..76f213c
--- /dev/null
+++ b/examples.md
@@ -0,0 +1,18 @@
+---
+layout: layouts/base.njk
+---
+
+# Examples
+
+Checkout these implementations of the task file standard:
+
+- [janikvonrotz/dotfiles](https://codeberg.org/janikvonrotz/dotfiles/src/main/task)
+- [janikvonrotz/python.casa](https://codeberg.org/janikvonrotz/python.casa/src/main/task)
+- [Mint-System/Ansible-Build](https://github.com/Mint-System/Ansible-Build/blob/main/task)
+- [Mint-System/Kubernetes-Build](https://github.com/Mint-System/Kubernetes-Build/blob/main/task)
+- [Mint-System/Odoo-Build](https://github.com/Mint-System/Odoo-Build/blob/main/task)
+- [Mint-System/Odoo-Wiki](https://github.com/Mint-Wiki/Odoo-Build/blob/main/task)
+- [Mint-System/Website](https://github.com/Mint-System/Website/blob/main/task)
+- [Mint-System/Wiki](https://github.com/Mint-System/Wiki/blob/main/task)
+
+This website is built with a `task` file. Here is the source: [janikvonrotz/taskfile.build](https://codeberg.org/janikvonrotz/taskfile.build/src/branch/main/task)
diff --git a/patterns.md b/patterns.md
new file mode 100644
index 0000000..57b180e
--- /dev/null
+++ b/patterns.md
@@ -0,0 +1,276 @@
+---
+layout: layouts/base.njk
+---
+
+# Patterns
+
+The task file from the specification is very basic. Commands can also 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
+}
+```
diff --git a/usage.md b/usage.md
index 67d1ecf..ac89f10 100644
--- a/usage.md
+++ b/usage.md
@@ -4,312 +4,15 @@ 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`.\
+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
+## Completion
With the output of `task help` the task commands can be completed.
@@ -356,6 +59,14 @@ esac
The completion also works for task alias `alias t='./task'`.
+## Image
+
+See [taskfile.build/image](/image) for details.
+
+## Integration
+
+The task file is intended to be integrated into the shell setup. Whenever an applicatio uses a domain specific language (DSL) ensure that the DSL calls the task file. Most you'll see DSLs to describe build pipelines.
+
### 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.
@@ -417,4 +128,4 @@ pipeline {
}
}
}%
-```
\ No newline at end of file
+```