From a4b7c38d10b6901b11cdbb83df0dd9a1b2dc00cb Mon Sep 17 00:00:00 2001 From: Janik von Rotz Date: Wed, 29 Oct 2025 18:27:56 +0100 Subject: [PATCH] docs(website): restructure documentation and move content to dedicated pages - 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 --- AGENTS.md | 4 + README.md | 757 +------------------------------------ _includes/layouts/base.njk | 12 +- image/README.md | 2 +- library.md | 219 +++++++++++ usage.md | 420 ++++++++++++++++++++ 6 files changed, 653 insertions(+), 761 deletions(-) create mode 100644 library.md create mode 100644 usage.md diff --git a/AGENTS.md b/AGENTS.md index 2dce927..968c33c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,7 @@ +--- +layout: layouts/base.njk +--- + # AGENTS.md ## General agent guidance diff --git a/README.md b/README.md index e13f59f..7946395 100644 --- a/README.md +++ b/README.md @@ -127,546 +127,9 @@ fi These are the main parts of every task file script. -## Usage +## Examples -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.md) 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' - } - } - } -}% -``` - -## Example - -This website is built with a `task` file. Here is the source: - -```bash -#!/usr/bin/env bash -set -e - -help-table() { - local cmd_width=20 - local opt_width=6 - local desc_width=42 - local column="| %-${cmd_width}s | %-${opt_width}s | %-${desc_width}s |\n" - - printf "$column" 'Command' 'Option' 'Description' - echo "|$(printf '%*s' $((cmd_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((opt_width + 2)) '' | tr ' ' '-')|$(printf '%*s' $((desc_width + 2)) '' | tr ' ' '-')|" - printf "$column" 'all' '' 'Run all tasks.' - printf "$column" 'build-11ty' '' 'Build the 11ty website.' - printf "$column" 'build-image' '' 'Build Docker image.' - printf "$column" 'commit-with-llm' '' 'Commit with llm generated commit message.' - printf "$column" 'dev-11ty' '' 'Run 11ty server.' - printf "$column" 'help' '[grep]' 'Show help for commands.' - printf "$column" 'install' '' 'Install node packages.' - printf "$column" 'lint' '' 'Lint code with prettier.' - printf "$column" 'run' '' 'Run Docker image.' - printf "$column" 'run-tests' '' 'Test with shellcheck.' - printf "$column" 'serve-11tyy' '' 'Serve the 11ty output folder.' - printf "$column" 'version' '' 'Show version of required tools.' -} - -help() { - echo - if [[ -n "$1" ]]; then - help-table | grep -i "$1" | column -t -s'|' - else - echo "task [options]" - echo - echo "commands:" - echo - help-table - fi - echo -} - -# Static vars - -BUILDX_PLATFORM="linux/amd64" -CONTAINER_REGISTRY="janikvonrotz" -CONTAINER_TAG="taskfile.build:latest" -DOCKERFILE="image/Dockerfile" - -# Import commands - -if [[ -d "$HOME/taskfile.build/bin" ]]; then - for file in "$HOME/taskfile.build/bin/"*; do - if [[ -f "$file" ]]; then - source "$file" - fi - done -fi - -# Project commands - -run() { - echo "Open http://localhost:8080 in your browser" - docker run -p 8080:80 \ - -e GIT_URL="https://git.taskfile.build" \ - -e TASK_COMMANDS="install,build-11ty" \ - -e CADDY_ROOT="_site" \ - janikvonrotz/taskfile.build:latest -} - -install() { - pnpm install -} - -dev-11ty() { - pnpm dlx @11ty/eleventy --serve -} - -lint() { - pnpm exec prettier . --write -} - -run-tests() { - echo "Test with shellcheck." - shellcheck task -} - -build-11ty() { - pnpm dlx @11ty/eleventy -} - -serve-11ty() { - pnpx serve ./_site -} - -version() { - echo "eleventy: $(pnpm dlx @11ty/eleventy --version)" -} - -if declare -f "$1" > /dev/null; then - "$1" "${@:2}" -else - case "$1" in - all) - install - build-11ty - ;; - *) - echo "Unknown command: $1" - help - exit 1 - ;; - esac -fi -``` - -Source: [janikvonrotz/taskfile.build](https://codeberg.org/janikvonrotz/taskfile.build/src/branch/main/task) - -### More examples - -More links to the implementations of the task file standard: +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) @@ -677,218 +140,4 @@ More links to the implementations of the task file standard: - [Mint-System/Website](https://github.com/Mint-System/Website/blob/main/task) - [Mint-System/Wiki](https://github.com/Mint-System/Wiki/blob/main/task) -## Library - -The repository of this website provides a library of reusable functions. - -Clone the repository into your home folder and ensure it is updated regurarly. - -```bash -clone-taskfile(){ - if [[ ! -d "$HOME/taskfile.build" ]]; then - echo -e "\033[38;5;214mGit\033[0m: Clone taskfile repo" - git clone https://git.taskfile.build "$HOME/taskfile.build" - else - echo -e "\033[38;5;214mGit\033[0m: Pull taskfile repo" - git -C "$HOME/taskfile.build" pull - fi -} -``` - -In your task file you can import these functions from the taskfile library. - -```bash -if [[ -d "$HOME/taskfile.build/bin" ]; then - for file in "$HOME/taskfile.build/bin/"*; do - if [[ -f "$file" ]]; then - source "$file" - fi - done -fi -``` - -### Task - -For autocomplete add the command to the help table. - -#### init-config-dir - -```bash -printf "$column" "init-config-dir" "" "Setup task file config dir." -``` - -#### template-dotenv - -```bash -printf "$column" "template-dotenv" "" "Generate .env from .env.template." -``` - -### Python - -Set git env vars. - -```bash -GIT_BRANCH=$(git symbolic-ref --short -q HEAD) -``` - -#### init-venv - -```bash -printf "$column" "init-venv" "" "Initialize and activate Python virtual env." -``` - -### Environment - -#### create-odoo-env - -```bash -printf "$column" "create-odoo-env" "[env]" "Create env file for Odoo instance." -``` - -#### create-nextcloud-env - -```bash -printf "$column" "create-nextcloud-env" "[env]" "Create env file for Nextcloud Instance." -``` - -#### list-env - -```bash -printf "$column" "list-env" "" "List env files." -``` - -#### show-env - -```bash -printf "$column" "show-env" "[env]" "Output content of the env file." -``` - -#### copy-env - -```bash -printf "$column" "copy-env" "[env][env]" "Copy env file." -``` - -#### edit-env - -```bash -printf "$column" "edit-env" "[env]" "Open env file in default editor." -``` - -#### load-env - -```bash -printf "$column" "load-env" "[env]" "Load and export env file." -``` - -#### rename-env - -```bash -printf "$column" "rename-env" "[env][env]" "Rename env file." -``` - -#### remove-env - -```bash -printf "$column" "remove-env" "[env]" "Remove environment config." -``` - -#### backup-env-files - -```bash -printf "$column" "backup-env-files" "[path]" "Archive and copy env files to target location." -``` - -#### restore-env-files - -```bash -printf "$column" "restore-env-files" "[path]" "Extract and copy env files from backup file." -``` - -### LLM - -#### commit-with-llm - -```bash -printf "$column" "commit-with-llm" "" "Commit with llm generated commit message." -``` - -#### update-with-llm - -```bash -printf "$column" "update-with-llm" "[glob][prompt]" "Feed files matching glob to LLM and apply changes." -``` - -### Git - -#### update-submodule - -```bash -printf "$column" "update-submodule" "" "Update git submodules." -``` - -### Pass - -Set path to pass entry. - -```bash -PASS_ENTRY=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]') -``` - -#### save-dotenv-to-pass - -```bash -printf "$column" "save-dotenv-to-pass" "" "Store content of .env in pass entry." -``` - -#### load-dotenv=from-pass - -```bash -printf "$column" "load-dotenv=from-pass" "" "Restore content to .env from pass entry." -``` - -### KeePass - -Set path to KeePass database and entry. - -```bash -KEEPASS_DATABASE_FILE="/path/to/database.kdbx" -KEEPASS_DATABASE_KEY_FILE="/path/to/keepass.key" -KEEPASS_ENTRY=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]') -``` - -#### save-dotenv-to-keepass - -```bash -printf "$column" "save-dotenv-to-keepass" "" "Store content of .env in KeePass entry." -``` - -#### load-dotenv-from-keepass - -```bash -printf "$column" "load-dotenv-from-keepass" "" "Restore content to .env from KeePass entry." -``` - -### Docker - -Set Docker env vars. - -```bash -BUILDX_PLATFORM="linux/amd64" -CONTAINER_REGISTRY="janikvonrotz" -CONTAINER_TAG="python.casa:latest" -DOCKERFILE="image/Dockerfile" -DOCKER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY/" -``` - -#### login-docker - -```bash -printf "$column" "login-docker" "[user][token]" "Run Docker login and create docker config." -``` - -#### build-image - -```bash -printf "$column" "build-image" "" "Build Docker image with buildx." -``` +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/_includes/layouts/base.njk b/_includes/layouts/base.njk index dc4dd92..46142d0 100644 --- a/_includes/layouts/base.njk +++ b/_includes/layouts/base.njk @@ -29,18 +29,18 @@
{{ content | safe }}
diff --git a/image/README.md b/image/README.md index efd115e..1d4e32b 100644 --- a/image/README.md +++ b/image/README.md @@ -24,7 +24,7 @@ export CADDY_ROOT="_site" # default is path to the repo Run the tasfile.buld image: ```bash -function run() { +run() { echo "Open http://localhost:8080 in your browser" docker run -p 8080:80 \ -e GIT_URL="$GIT_URL" \ diff --git a/library.md b/library.md new file mode 100644 index 0000000..9b4da9b --- /dev/null +++ b/library.md @@ -0,0 +1,219 @@ +--- +layout: layouts/base.njk +--- + +# Library + +The repository of this website provides a library of reusable functions. + +Clone the repository into your home folder and ensure it is updated regurarly. + +```bash +clone-taskfile(){ + if [[ ! -d "$HOME/taskfile.build" ]]; then + echo -e "\033[38;5;214mGit\033[0m: Clone taskfile repo" + git clone https://git.taskfile.build "$HOME/taskfile.build" + else + echo -e "\033[38;5;214mGit\033[0m: Pull taskfile repo" + git -C "$HOME/taskfile.build" pull + fi +} +``` + +In your task file you can import these functions from the taskfile library. + +```bash +if [[ -d "$HOME/taskfile.build/bin" ]; then + for file in "$HOME/taskfile.build/bin/"*; do + if [[ -f "$file" ]]; then + source "$file" + fi + done +fi +``` + +## Task + +For autocomplete add the command to the help table. + +### init-config-dir + +```bash +printf "$column" "init-config-dir" "" "Setup task file config dir." +``` + +### template-dotenv + +```bash +printf "$column" "template-dotenv" "" "Generate .env from .env.template." +``` + +## Python + +Set git env vars. + +```bash +GIT_BRANCH=$(git symbolic-ref --short -q HEAD) +``` + +### init-venv + +```bash +printf "$column" "init-venv" "" "Initialize and activate Python virtual env." +``` + +## Environment + +### create-odoo-env + +```bash +printf "$column" "create-odoo-env" "[env]" "Create env file for Odoo instance." +``` + +### create-nextcloud-env + +```bash +printf "$column" "create-nextcloud-env" "[env]" "Create env file for Nextcloud Instance." +``` + +### list-env + +```bash +printf "$column" "list-env" "" "List env files." +``` + +### show-env + +```bash +printf "$column" "show-env" "[env]" "Output content of the env file." +``` + +### copy-env + +```bash +printf "$column" "copy-env" "[env][env]" "Copy env file." +``` + +### edit-env + +```bash +printf "$column" "edit-env" "[env]" "Open env file in default editor." +``` + +### load-env + +```bash +printf "$column" "load-env" "[env]" "Load and export env file." +``` + +### rename-env + +```bash +printf "$column" "rename-env" "[env][env]" "Rename env file." +``` + +### remove-env + +```bash +printf "$column" "remove-env" "[env]" "Remove environment config." +``` + +### backup-env-files + +```bash +printf "$column" "backup-env-files" "[path]" "Archive and copy env files to target location." +``` + +### restore-env-files + +```bash +printf "$column" "restore-env-files" "[path]" "Extract and copy env files from backup file." +``` + +## LLM + +### commit-with-llm + +```bash +printf "$column" "commit-with-llm" "" "Commit with llm generated commit message." +``` + +### update-with-llm + +```bash +printf "$column" "update-with-llm" "[glob][prompt]" "Feed files matching glob to LLM and apply changes." +``` + +## Git + +### update-submodule + +```bash +printf "$column" "update-submodule" "" "Update git submodules." +``` + +## Pass + +Set path to pass entry. + +```bash +PASS_ENTRY=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]') +``` + +### save-dotenv-to-pass + +```bash +printf "$column" "save-dotenv-to-pass" "" "Store content of .env in pass entry." +``` + +### load-dotenv=from-pass + +```bash +printf "$column" "load-dotenv=from-pass" "" "Restore content to .env from pass entry." +``` + +## KeePass + +Set path to KeePass database and entry. + +```bash +KEEPASS_DATABASE_FILE="/path/to/database.kdbx" +KEEPASS_DATABASE_KEY_FILE="/path/to/keepass.key" +KEEPASS_ENTRY=$(echo "env/${PWD##*/}" | tr '[:upper:]' '[:lower:]') +``` + +### save-dotenv-to-keepass + +```bash +printf "$column" "save-dotenv-to-keepass" "" "Store content of .env in KeePass entry." +``` + +### load-dotenv-from-keepass + +```bash +printf "$column" "load-dotenv-from-keepass" "" "Restore content to .env from KeePass entry." +``` + +## Docker + +Set Docker env vars. + +```bash +BUILDX_PLATFORM="linux/amd64" +CONTAINER_REGISTRY="janikvonrotz" +CONTAINER_TAG="python.casa:latest" +DOCKERFILE="image/Dockerfile" +DOCKER_CONFIG="$HOME/.docker/$CONTAINER_REGISTRY/" +``` + +### login-docker + +```bash +printf "$column" "login-docker" "[user][token]" "Run Docker login and create docker config." +``` + +### build-image + +```bash +printf "$column" "build-image" "" "Build Docker image with buildx." +``` diff --git a/usage.md b/usage.md new file mode 100644 index 0000000..67d1ecf --- /dev/null +++ b/usage.md @@ -0,0 +1,420 @@ +--- +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' + } + } + } +}% +``` \ No newline at end of file