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."