#!/usr/bin/env bash #------------------------------------------------------------------------------- ## @file bashdoc.sh ## @brief Erzeugt Markdown-Doku aus den '##'-Kommentaren von Bash-Scripts ## (Präambel + Funktionsköpfe, angelehnt an javadoc) ## @author ## @owner ## @assist KI-unterstützt (Claude), geprüft von ## @version 1.0.0 (07.10.2026) ## ## @usage bashdoc.sh [Optionen] [...] ## Ausgabe auf stdout oder mit -o in eine Datei ## @options ## @OPTIONS@ ## @exit 0 = ok · 1 = Fehler · 2 = Aufruffehler #------------------------------------------------------------------------------- set -Eeuo pipefail SCRIPT_PATH="$(readlink -f -- "${BASH_SOURCE[0]}")" SCRIPT_DIR="${SCRIPT_PATH%/*}" SCRIPT_NAME="${SCRIPT_PATH##*/}" # shellcheck source=_functions.sh source "${SCRIPT_DIR}/_functions.sh" || { echo "FEHLER: ${SCRIPT_DIR}/_functions.sh fehlt" >&2; exit 1; } B_OPTS+=( "o|output|value:DATEI||Markdown in DATEI schreiben (relativ = zum Aufrufverzeichnis)" "p|private|flag|0|auch interne Funktionen (_name) dokumentieren" ) b_init "$@" set -- "${ARGS[@]}" ## @brief Wandelt ein Script in Markdown um ## @param $1 Script-Datei ## @return 0 ok, 1 Datei nicht lesbar; Markdown auf stdout doc_file() { local file="$1" [[ -r "$file" ]] || { berror "Nicht lesbar: $file"; return 1; } awk -v fname="${file##*/}" -v private="$OPTION_PRIVATE" ' function flush_block(name, i, l, tag) { if (!private && name ~ /^_/) { n = 0; return } printf "\n### `%s()`\n\n", name for (i = 1; i <= n; i++) { l = blk[i] if (l ~ /^@brief/) { sub(/^@brief[ \t]*/, "", l); print l; last = "brief" } else if (l ~ /^@param/) { sub(/^@param[ \t]*/, "", l); print "- **Param** " l; last = "param" } else if (l ~ /^@return/) { sub(/^@return[ \t]*/, "", l); print "- **Return** " l; last = "return" } else if (l ~ /^@/) { tag = l; sub(/[ \t].*/, "", tag); sub(/^@[a-z]+[ \t]*/, "", l); print "- **" substr(tag, 2) "** " l } else if (l != "") { print (last == "brief" ? l : " " l) } } n = 0 } function print_opts( i, f, left) { print " -h -d -q -n -l Standardoptionen (Hilfe, Debug, still, Trockenlauf, Logdatei)" for (i = 1; i <= nopt; i++) { split(opt[i], f, "|"); sub(/:.*/, "", f[3]) left = (f[1] != "" ? "-" f[1] " | " : "") "--" f[2] if (opt_meta[i] != "") left = left " " opt_meta[i] printf " %-26s %s%s\n", left, f[5], (f[3] == "value" && f[4] != "" ? " (Default: " f[4] ")" : "") } } # 1. Durchlauf: Script-Optionen aus B_OPTS+=( "…" ) sammeln FNR == NR { if ($0 ~ /^[ \t]*B_OPTS\+=\(/) inopts = 1 if (inopts) { l = $0 while (match(l, /"[^"]*"/)) { o = substr(l, RSTART + 1, RLENGTH - 2); l = substr(l, RSTART + RLENGTH) opt[++nopt] = o; split(o, f, "|"); opt_meta[nopt] = (f[3] ~ /:/ ? substr(f[3], index(f[3], ":") + 1) : (f[3] == "value" ? "WERT" : "")) } if ($0 ~ /\)[ \t]*$/) inopts = 0 } next } # 2. Durchlauf: Präambel + Funktionsköpfe /^#-{10,}/ { part++; if (part == 1) printf "## %s\n\n```text\n", fname; if (part == 2) print "```"; next } part == 1 && /^##/ { l = $0; sub(/^## ?/, "", l); if (l !~ /@OPTIONS@/) print l; else print_opts(); next } part >= 2 && /^[ \t]*##/ { l = $0; sub(/^[ \t]*## ?/, "", l); blk[++n] = l; next } part >= 2 && n > 0 && /^[ \t]*(function[ \t]+)?[A-Za-z_][A-Za-z0-9_]*[ \t]*\(\)/ { name = $0; sub(/^[ \t]*(function[ \t]+)?/, "", name); sub(/[ \t]*\(.*/, "", name) flush_block(name); next } part >= 2 && n > 0 && !/^[ \t]*#/ { n = 0 } ' "$file" "$file" } ## @brief Hauptablauf ## @param $@ Script-Dateien ## @return 0 ok, 1 Fehler main() { (( $# )) || bdie "Mindestens ein Script angeben" 2 local f out="" rc=0 [[ -n "$OPTION_OUTPUT" ]] && out="$(b_abspath "$OPTION_OUTPUT" "$B_CALL_DIR")" { printf '# Script-Doku\n\n> Erzeugt mit `bin/bashdoc.sh` am %(%d.%m.%Y)T – Quelle sind die `##`-Kommentare.\n\n' -1 for f in "$@"; do doc_file "$(b_abspath "$f" "$B_CALL_DIR")" || rc=1 printf '\n' done } > "${out:-/dev/stdout}" [[ -n "$out" ]] && becho "Doku geschrieben: $out" return "$rc" } main "$@"