#!/usr/bin/env bash #shellcheck disable=SC2089 # # Make website from .md files # # Author: zenobit # Date: February 14, 2026 # License: MIT # count=$(find ./ -name '*.md' | wc -l) # Functions for colored bordered messages (gum) or ANSI colored messages if command -v gum -v >/dev/null; then _r() { gum style --border rounded --border-foreground "#e50203" --padding "0 1" "${@}" } _re() { gum style --border rounded --border-foreground "#e50203" --padding "0 1" "${@}" exit 1 } _g() { gum style --border rounded --border-foreground "#14a113" --padding "0 1" "$@" } _b() { gum style --border rounded --border-foreground "#004EFB" --padding "0 1" "$@" } _y() { gum style --border rounded --border-foreground "#fdde13" --padding "0 1" "$@" } ask() { gum confirm "$@" } _header() { local text green red yellow text="${1}" red=$(gum style --border rounded --border-foreground "#e50203" --padding "0 1" "$text") yellow=$(gum style --border rounded --border-foreground "#fdde13" --padding "0 1" "$red") green=$(gum style --border rounded --border-foreground "#14a113" --padding "0 1" "$yellow") echo "$green" } _footer() { local text green red yellow text="${1}" green=$(gum style --border rounded --border-foreground "#14a113" --padding "0 1" "$text") yellow=$(gum style --border rounded --border-foreground "#fdde13" --padding "0 1" "$green") red=$(gum style --border rounded --border-foreground "#e50203" --padding "0 1" "$yellow") echo "$red" } else RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' #CYAN='\033[0;36m' NC='\033[0m' _g() { echo -e "${GREEN}${1}${NC}" } _r() { echo -e "${RED}${1}${NC}" } _re() { echo -e "${RED}${1}${NC}" >&2 exit 1 } _b() { echo -e "${BLUE}${1}${NC}" } _y() { echo -e "${YELLOW}${1}${NC}" } ask() { _y "Press Enter to continue anything else for Quit..." echo "$1" read -n 1 -s key if [[ ! $key == '' ]]; then exit 1 fi } _header() { echo -e "\n${RED}${1}${NC}\n" } _footer() { echo -e "\n${RED}${1}${NC}\n" } fi export -f _r _re _g _b _y # --------------------------------------------------------------------------- # Optional override: set FORGE_URL to your Gitea/Forgejo base URL to skip # auto-detection, e.g. in .env or before calling the script: # export FORGE_URL="https://git.example.com" # --------------------------------------------------------------------------- [ -f .env ] && source .env # Try to detect a Gitea/Forgejo instance by probing /api/v1/meta # Returns 0 (true) if confirmed, 1 if not _is_gitea() { local base_url="$1" local response response=$(curl -sf --max-time 5 "${base_url}/api/v1/meta" 2>/dev/null) # Gitea/Forgejo meta endpoint returns JSON with a "version" field [[ "$response" =~ \"version\" ]] && return 0 return 1 } # Detect repository URL and platform from git remote origin _detect_repo() { if ! git rev-parse --git-dir >/dev/null 2>&1; then _r "Not a git repository — repo buttons will be disabled" REPO_URL="" REPO_PLATFORM="" REPO_ZIP_URL="" REPO_RELEASE_URL="" return fi # Always use the main remote repository local raw raw=$(git remote get-url "$(git remote | head -n 1)" 2>/dev/null) if [ -z "$raw" ]; then _r "No git remote found — repo buttons will be disabled" REPO_URL="" REPO_PLATFORM="" REPO_ZIP_URL="" REPO_RELEASE_URL="" return fi # Normalize SSH → HTTPS local https_url if [[ "$raw" =~ ^git@ ]]; then https_url=$(echo "$raw" | sed 's|git@\(.*\):\(.*\)\.git|https://\1/\2|; s|git@\(.*\):\(.*\)|https://\1/\2|') else https_url="${raw%.git}" fi # Extract base URL (scheme + host) and slug (user/repo) local base_url slug base_url=$(echo "$https_url" | grep -oP 'https?://[^/]+') slug=$(echo "$https_url" | sed "s|${base_url}/||") # Detect platform — known hosts first, then Gitea/Forgejo probe if [[ "$https_url" =~ github\.com ]]; then REPO_PLATFORM="github" REPO_URL="$https_url" REPO_ZIP_URL="${https_url}/archive/refs/heads/main.zip" REPO_RELEASE_URL="${https_url}/releases/latest" elif [[ "$https_url" =~ gitlab\.com ]]; then REPO_PLATFORM="gitlab" REPO_URL="$https_url" # GitLab: project path needed for archive URL local proj_path proj_path=$(echo "$https_url" | sed 's|https://gitlab\.com/||') REPO_ZIP_URL="https://gitlab.com/${proj_path}/-/archive/main/${proj_path##*/}-main.zip" REPO_RELEASE_URL="${https_url}/-/releases" else # Gitea/Forgejo: use FORGE_URL override or auto-detect via API probe local forge_base="${FORGE_URL:-}" if [ -n "$forge_base" ]; then _b "FORGE_URL override: $forge_base" REPO_PLATFORM="gitea" elif _is_gitea "$base_url"; then _g "Gitea/Forgejo detected at: $base_url" forge_base="$base_url" REPO_PLATFORM="gitea" else _y "Unknown platform — only source link will be shown" REPO_PLATFORM="other" REPO_URL="$https_url" REPO_ZIP_URL="" REPO_RELEASE_URL="" _check_release return fi REPO_URL="$https_url" REPO_ZIP_URL="${forge_base}/${slug}/archive/main.zip" REPO_RELEASE_URL="${https_url}/releases/latest" fi _g "Repo detected [$REPO_PLATFORM]: $REPO_URL" # Check if any release exists via API _check_release } # Query platform API to detect if at least one release exists _check_release() { REPO_HAS_RELEASE=0 [ -z "$REPO_URL" ] && return local base_url slug http_code body base_url=$(echo "$REPO_URL" | grep -oP 'https?://[^/]+') slug=$(echo "$REPO_URL" | sed "s|${base_url}/||") case "$REPO_PLATFORM" in github) http_code=$(curl -sf -o /dev/null -w "%{http_code}" \ -H "Accept: application/vnd.github+json" \ "https://api.github.com/repos/${slug}/releases/latest" 2>/dev/null) # 200 = release exists, 404 = no releases [ "$http_code" = "200" ] && REPO_HAS_RELEASE=1 ;; gitlab) local encoded_slug encoded_slug=$(echo "$slug" | sed 's|/|%2F|g') body=$(curl -sf \ "https://gitlab.com/api/v4/projects/${encoded_slug}/releases?per_page=1" 2>/dev/null) [ -n "$body" ] && [ "$body" != "[]" ] && REPO_HAS_RELEASE=1 ;; gitea) # Gitea/Forgejo: GET /api/v1/repos/{owner}/{repo}/releases?limit=1 body=$(curl -sf --max-time 5 \ "${base_url}/api/v1/repos/${slug}/releases?limit=1" 2>/dev/null) [ -n "$body" ] && [ "$body" != "[]" ] && [ "$body" != "null" ] && REPO_HAS_RELEASE=1 ;; *) REPO_HAS_RELEASE=0 ;; esac if [ "$REPO_HAS_RELEASE" = "1" ]; then _g "Release found" else _y "No release found — release button will be shown as strikethrough" fi } add_validation() { echo '

Ověřit CSS!

' >> "$output" } # Build nav snippet for a given page with active class # Injects: oSoWoSo | Home | [Download Release] [Download ZIP] [Source] | other pages... _build_nav() { local current="$1" local nav_file="$2" echo '' >> "$nav_file" } # Target: build target_build() { ## Builds the website, optional: title argument _detect_repo if [ -z "$1" ]; then _r "No website title provided using generated one!" title=$(basename "$(pwd)" | sed 's/_/ /g') else title="$1" fi _g "Creating: $title" if [ -f docs/CNAME ]; then cp docs/CNAME src/ fi rm -rf docs && mkdir -p docs cp -r src/* docs/ rm -f docs/toggle.html docs/toc.html docs/svg-color.html if [ "$count" = 1 ]; then for file in *.md; do [ -f "$file" ] || continue local nav_file="docs/_nav.html" _build_nav "index" "$nav_file" echo "Processing: $file → index.html" # single file pandoc "$file" -f gfm -s \ --css=style.css \ --toc \ --toc-depth=3 \ --include-before-body=src/toggle.html \ --include-before-body=src/toc.html \ --include-before-body="$nav_file" \ --include-after-body=src/svg-color.html \ --metadata title="$title" \ -o docs/index.html rm -f "$nav_file" done elif [ "$count" -gt 1 ]; then for file in *.md; do [ -f "$file" ] || continue if [ "$file" = "README.md" ]; then output="docs/index.html" meta_title="$title" current="index" else output="docs/${file%.md}.html" meta_title=$(echo "${file%.md}" | sed 's/_/ /g') current="${file%.md}" fi local nav_file="docs/_nav_${current}.html" _build_nav "$current" "$nav_file" echo "Processing: $file → $output" # multi file pandoc "$file" -f gfm -s \ --css=style.css \ --toc \ --toc-depth=3 \ --include-before-body=src/toggle.html \ --include-before-body=src/toc.html \ --include-before-body="$nav_file" \ --include-after-body=src/svg-color.html \ --metadata title="$meta_title" \ -o "$output" rm -f "$nav_file" done else _re "ERROR: No .md files found!" fi _g "Finished" } # Target: help target_help() { ## Show this help message _b 'OSOWOSO Website Build System' echo "" echo "Usage: $0 [target]" echo "" echo "Available targets:" echo "" awk 'BEGIN {FS = "## "} /^target_[a-zA-Z_-]+\(\).*## / { sub(/target_/, "", $1) sub(/\(\) \{/, "", $1) gsub(/^[ \t]+/, "", $1) printf " \033[36m%-15s\033[0m %s\n", $1, $2 }' "$0" | sort || _re 'ERROR: No functions found! Create function you want use for arguments and help as: target_"Name of argument aka function"() { ## Your usage message here.' echo "" } # Target: serve target_serve() { ## Serve site locally out of docs folder if [ ! -f docs/index.html ]; then _r "Site not built!" ask 'Build first?' && target_build "$@" || _re "Exiting" fi _g 'Press Ctrl+C to stop' python3 -m http.server -d docs 8000 } # Main script logic main() { local target="${1:-help}" target="${target#target_}" local func_name="target_${target//-/_}" if declare -f "$func_name" > /dev/null; then shift "$func_name" "$@" else #shellcheck disable=SC2016,2086 _re 'Unknown target: $target. Run '$0 help' for available targets.' fi } # Run main with all arguments main "$@"