mirror of
https://github.com/oSoWoSo/DistroHopper.git
synced 2026-06-14 17:36:40 +00:00
509 lines
14 KiB
Bash
Executable file
509 lines
14 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
#shellcheck disable=SC2089
|
|
#
|
|
# Make website from .md files
|
|
#
|
|
# Author: zenobit <zen@duck.com>
|
|
# Date: February 14, 2026
|
|
# License: MIT
|
|
#
|
|
|
|
BUILD_VERSION='0.0.3'
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
# Source shared functions
|
|
# shellcheck source=lib.sh
|
|
source "${SCRIPT_DIR}/lib.sh"
|
|
|
|
# Load .env — project dir takes priority
|
|
check_variables() {
|
|
if [ -z "$TITLE" ]; then
|
|
return 1
|
|
fi
|
|
}
|
|
if [ -f ".env" ]; then
|
|
source ".env"
|
|
check_variables && echo 'Using .env'
|
|
|
|
elif [ -f "src/.env" ]; then
|
|
source "src/.env"
|
|
check_variables && echo 'Using src/.env'
|
|
|
|
elif [ -f "${SCRIPT_DIR}/.env" ]; then
|
|
source "${SCRIPT_DIR}/.env"
|
|
check_variables && echo 'Using .env'
|
|
fi
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Repo detection
|
|
# ---------------------------------------------------------------------------
|
|
|
|
_is_gitea() {
|
|
local base_url="$1"
|
|
local response
|
|
response=$(curl -sfL --max-time 5 "${base_url}/api/v1/meta" 2>/dev/null)
|
|
[[ "$response" =~ \"version\" ]] && return 0
|
|
return 1
|
|
}
|
|
|
|
_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
|
|
|
|
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
|
|
|
|
local base_url slug
|
|
base_url=$(echo "$https_url" | grep -oP 'https?://[^/]+')
|
|
slug="${https_url#"${base_url}"/}"
|
|
|
|
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"
|
|
local proj_path
|
|
proj_path="${https_url#https://gitlab.com/}"
|
|
REPO_ZIP_URL="https://gitlab.com/${proj_path}/-/archive/main/${proj_path##*/}-main.zip"
|
|
REPO_RELEASE_URL="${https_url}/-/releases"
|
|
|
|
elif [[ "$https_url" =~ codeberg\.org ]]; then
|
|
REPO_PLATFORM="gitea"
|
|
REPO_URL="$https_url"
|
|
REPO_ZIP_URL="${https_url}/archive/main.zip"
|
|
REPO_RELEASE_URL="${https_url}/releases/latest"
|
|
printf "%-12s %s\n" "Forgejo:" "$REPO_URL"
|
|
_check_release
|
|
return
|
|
|
|
else
|
|
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
|
|
printf "%-12s %s\n" "Gitea:" "$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
|
|
|
|
printf "%-12s %s\n" "${REPO_PLATFORM}:" "$slug"
|
|
_check_release
|
|
}
|
|
|
|
_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="${REPO_URL#"${base_url}"/}"
|
|
|
|
case "$REPO_PLATFORM" in
|
|
github)
|
|
local auth_header=""
|
|
[ -n "${GITHUB_TOKEN:-}" ] && auth_header="-H \"Authorization: Bearer ${GITHUB_TOKEN}\""
|
|
http_code=$(curl -sf -o /dev/null -w "%{http_code}" \
|
|
-H "Accept: application/vnd.github+json" \
|
|
${auth_header:+"-H" "Authorization: Bearer ${GITHUB_TOKEN}"} \
|
|
"https://api.github.com/repos/${slug}/releases/latest" 2>/dev/null)
|
|
[ "$http_code" = "200" ] && REPO_HAS_RELEASE=1
|
|
;;
|
|
gitlab)
|
|
local encoded_slug
|
|
encoded_slug="${slug//\//%2F}"
|
|
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)
|
|
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
|
|
printf "%-12s %s\n" "Release:" found
|
|
else
|
|
printf "%-12s ${RED}%s${NC}\n" "Release:" "No"
|
|
fi
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# HTML builders
|
|
# ---------------------------------------------------------------------------
|
|
|
|
add_validation() {
|
|
echo '
|
|
<p>
|
|
<a href="https://jigsaw.w3.org/css-validator/check/referer">
|
|
<img style="border:0;width:88px;height:31px"
|
|
src="https://jigsaw.w3.org/css-validator/images/vcss-blue"
|
|
alt="Valid CSS!" />
|
|
</a>
|
|
</p>
|
|
' >> "$output"
|
|
}
|
|
|
|
_build_nav() {
|
|
local current="$1"
|
|
local nav_file="$2"
|
|
|
|
echo '<nav><ul>' > "$nav_file"
|
|
echo ' <li><a href="https://osowoso.org">oSoWoSo</a></li>' >> "$nav_file"
|
|
echo " <li>⏮️</a></li>" >> "$nav_file"
|
|
|
|
local home_active=""
|
|
[ "$current" = "index" ] && home_active=' class="active"'
|
|
echo " <li><a href=\"index.html\"${home_active}>Home</a></li>" >> "$nav_file"
|
|
|
|
if [ -n "$REPO_RELEASE_URL" ]; then
|
|
if [ "${REPO_HAS_RELEASE:-0}" = "1" ]; then
|
|
echo " <li><a href=\"${REPO_RELEASE_URL}\">⏬ release</a></li>" >> "$nav_file"
|
|
else
|
|
echo " <li><a class=\"no-release\" href=\"${REPO_RELEASE_URL}\">⏬ release</a></li>" >> "$nav_file"
|
|
fi
|
|
fi
|
|
[ -n "$REPO_ZIP_URL" ] && echo " <li><a href=\"${REPO_ZIP_URL}\">📦repo zip</a></li>" >> "$nav_file"
|
|
[ -n "$REPO_URL" ] && echo " <li><a href=\"${REPO_URL}\">🔗git</a></li>" >> "$nav_file"
|
|
|
|
echo " <li>⏭️</a></li>" >> "$nav_file"
|
|
|
|
for md in *.md; do
|
|
[ -f "$md" ] || continue
|
|
[ "$md" = "README.md" ] && continue
|
|
local slug label active
|
|
slug="${md%.md}"
|
|
label="${slug//_/ }"
|
|
active=""
|
|
[ "$slug" = "$current" ] && active=' class="active"'
|
|
echo " <li><a href=\"${slug}.html\"${active}>${label}</a></li>" >> "$nav_file"
|
|
done
|
|
|
|
echo '</ul></nav>' >> "$nav_file"
|
|
}
|
|
|
|
_build_goatcounter_snippet() {
|
|
local url="$1"
|
|
local file="docs/_goatcounter.html"
|
|
cat > "$file" <<EOF
|
|
<script data-goatcounter="https://${url}/count"
|
|
async src="//gc.zgo.at/count.js"></script>
|
|
EOF
|
|
echo "$file"
|
|
}
|
|
|
|
_build_giscus_snippet() {
|
|
local repo="$1" repo_id="$2" category="$3" category_id="$4"
|
|
local file="docs/_giscus.html"
|
|
cat > "$file" <<EOF
|
|
<script src="https://giscus.app/client.js"
|
|
data-repo="${repo}"
|
|
data-repo-id="${repo_id}"
|
|
data-category="${category}"
|
|
data-category-id="${category_id}"
|
|
data-mapping="pathname"
|
|
data-strict="0"
|
|
data-reactions-enabled="1"
|
|
data-emit-metadata="0"
|
|
data-input-position="bottom"
|
|
data-theme="preferred_color_scheme"
|
|
data-lang="cs"
|
|
crossorigin="anonymous"
|
|
async></script>
|
|
EOF
|
|
echo "$file"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Argument definitions
|
|
# format: "short|long|value|description|action"
|
|
# action: variable name to store value in, or "target" to set target
|
|
# ---------------------------------------------------------------------------
|
|
ARGS=(
|
|
"h|help||Show this help message|target"
|
|
"b|build||Build the website|target"
|
|
"s|serve||Serve site locally|target"
|
|
"t|title|<title>|Set website title|title"
|
|
"g|goatcounter|<url>|GoatCounter analytics URL|goatcounter_url"
|
|
"r|giscus-repo|<repo>|Giscus repo (user/repo)|giscus_repo"
|
|
"i|giscus-repo-id|<id>|Giscus repo ID|giscus_repo_id"
|
|
"c|giscus-category|<n>|Giscus category name|giscus_category"
|
|
"C|giscus-cat-id|<id>|Giscus category ID|giscus_category_id"
|
|
)
|
|
|
|
_build_optstring() {
|
|
local optstring=""
|
|
for entry in "${ARGS[@]}"; do
|
|
local short value
|
|
short=$(echo "$entry" | cut -d'|' -f1)
|
|
value=$(echo "$entry" | cut -d'|' -f3)
|
|
optstring+="$short"
|
|
[ -n "$value" ] && optstring+=":"
|
|
done
|
|
echo "$optstring"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Targets
|
|
# ---------------------------------------------------------------------------
|
|
|
|
target_build() { ## Builds the website
|
|
local title="$1"
|
|
local goatcounter_url="$2"
|
|
local giscus_repo="$3"
|
|
local giscus_repo_id="$4"
|
|
local giscus_category="$5"
|
|
local giscus_category_id="$6"
|
|
local count
|
|
|
|
_g "Creating: $title"
|
|
count=$(find ./ -name '*.md' | wc -l)
|
|
_detect_repo
|
|
|
|
if [ -z "$title" ]; then
|
|
_r "No website title provided — using generated one!"
|
|
title=$(basename "$(pwd)" | sed 's/_/ /g')
|
|
fi
|
|
|
|
[ -f docs/CNAME ] && cp docs/CNAME src/
|
|
rm -rf docs && mkdir -p docs
|
|
cp -r src/* docs/
|
|
rm -f docs/toggle.html docs/toc.html docs/svg-color.html
|
|
|
|
local extra_after=()
|
|
if [ -n "$goatcounter_url" ]; then
|
|
echo "GoatCounter: $goatcounter_url"
|
|
extra_after+=("--include-after-body=$(_build_goatcounter_snippet "$goatcounter_url")")
|
|
fi
|
|
if [ -n "$giscus_repo" ]; then
|
|
echo "Giscus: $giscus_repo"
|
|
extra_after+=("--include-after-body=$(_build_giscus_snippet \
|
|
"$giscus_repo" "$giscus_repo_id" "$giscus_category" "$giscus_category_id")")
|
|
fi
|
|
|
|
if [ "$count" = 1 ]; then
|
|
for file in *.md; do
|
|
[ -f "$file" ] || continue
|
|
local nav_file="docs/_nav.html"
|
|
_build_nav "index" "$nav_file"
|
|
printf "${YELLOW}%-12s${NC} %s\n" "processing:" "$file → index.html"
|
|
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 \
|
|
"${extra_after[@]}" \
|
|
--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
|
|
|
|
local output meta_title current
|
|
if [ "$file" = "README.md" ]; then
|
|
output="docs/index.html"
|
|
meta_title="$title"
|
|
current="index"
|
|
else
|
|
output="docs/${file%.md}.html"
|
|
meta_title="${file%.md}"
|
|
meta_title="${meta_title//_/ }"
|
|
current="${file%.md}"
|
|
fi
|
|
|
|
local nav_file="docs/_nav_${current}.html"
|
|
_build_nav "$current" "$nav_file"
|
|
|
|
printf "${YELLOW}%-12s${NC} %s\n" "processing:" "$file → $output"
|
|
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 \
|
|
"${extra_after[@]}" \
|
|
--metadata title="$meta_title" \
|
|
-o "$output"
|
|
rm -f "$nav_file"
|
|
done
|
|
else
|
|
_re "ERROR: No .md files found!"
|
|
fi
|
|
|
|
rm -f docs/_goatcounter.html docs/_giscus.html
|
|
}
|
|
|
|
errorMessage='ERROR: No functions found!
|
|
Create a target function with the format:
|
|
target_name() { ## Description here'
|
|
|
|
# Target: help
|
|
target_help() { ## Show this help message
|
|
_b "Website Build System
|
|
v$BUILD_VERSION
|
|
by oSoWoSo"
|
|
echo -e "\nUsage: $0 [target] [arguments]\n\nAvailable targets:\n"
|
|
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 "$errorMessage"
|
|
echo -e "\nAvailable arguments:\n"
|
|
for entry in "${ARGS[@]}"; do
|
|
local short long value desc
|
|
short=$(echo "$entry" | cut -d'|' -f1)
|
|
long=$(echo "$entry" | cut -d'|' -f2)
|
|
value=$(echo "$entry" | cut -d'|' -f3)
|
|
desc=$(echo "$entry" | cut -d'|' -f4)
|
|
printf " \033[33m%-32s\033[0m %s\n" "-${short}, --${long} ${value}" "$desc"
|
|
done
|
|
echo -e "\nScript will use .env file if found\n"
|
|
}
|
|
|
|
target_serve() { ## Serve site locally out of docs folder
|
|
local title="$1"
|
|
local goatcounter_url="$2"
|
|
local giscus_repo="$3"
|
|
local giscus_repo_id="$4"
|
|
local giscus_category="$5"
|
|
local giscus_category_id="$6"
|
|
|
|
if [ ! -f docs/index.html ]; then
|
|
_r "Site not built!"
|
|
if _confirm 'Build first?'; then
|
|
target_build "$title" "$goatcounter_url" \
|
|
"$giscus_repo" "$giscus_repo_id" \
|
|
"$giscus_category" "$giscus_category_id"
|
|
else
|
|
_re "Exiting"
|
|
fi
|
|
fi
|
|
|
|
_g 'Press Ctrl+C to stop'
|
|
python3 -m http.server -d docs 8000
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Main
|
|
# ---------------------------------------------------------------------------
|
|
|
|
main() {
|
|
# Convert long arguments to short for getopts
|
|
local args=()
|
|
while [[ $# -gt 0 ]]; do
|
|
local matched=0
|
|
for entry in "${ARGS[@]}"; do
|
|
local short long value
|
|
short=$(echo "$entry" | cut -d'|' -f1)
|
|
long=$(echo "$entry" | cut -d'|' -f2)
|
|
value=$(echo "$entry" | cut -d'|' -f3)
|
|
if [[ "$1" == "--${long}" ]]; then
|
|
args+=("-${short}")
|
|
[ -n "$value" ] && { args+=("$2"); shift; }
|
|
matched=1
|
|
break
|
|
fi
|
|
done
|
|
[ "$matched" = 0 ] && args+=("$1")
|
|
shift
|
|
done
|
|
set -- "${args[@]}"
|
|
|
|
# Default values (overridden by .env)
|
|
local target="help"
|
|
local title="${TITLE:-}"
|
|
local goatcounter_url="${GOATCOUNTER_URL:-}"
|
|
local giscus_repo="${GISCUS_REPO:-}"
|
|
local giscus_repo_id="${GISCUS_REPO_ID:-}"
|
|
local giscus_category="${GISCUS_CATEGORY:-}"
|
|
local giscus_category_id="${GISCUS_CATEGORY_ID:-}"
|
|
|
|
while getopts "$(_build_optstring)" opt; do
|
|
for entry in "${ARGS[@]}"; do
|
|
local short long action
|
|
short=$(echo "$entry" | cut -d'|' -f1)
|
|
long=$(echo "$entry" | cut -d'|' -f2)
|
|
action=$(echo "$entry" | cut -d'|' -f5)
|
|
if [[ "$opt" == "$short" ]]; then
|
|
if [[ "$action" == "target" ]]; then
|
|
target="$long"
|
|
else
|
|
printf -v "$action" '%s' "$OPTARG"
|
|
fi
|
|
break
|
|
fi
|
|
done
|
|
done
|
|
shift $((OPTIND - 1))
|
|
|
|
# Positional argument as target (backwards compatibility: ./web-create -b)
|
|
if [[ "$target" == "help" && -n "${1:-}" ]]; then
|
|
target="${1}"
|
|
shift
|
|
fi
|
|
|
|
local func_name="target_${target//-/_}"
|
|
#shellcheck disable=SC2016,2086
|
|
declare -f "$func_name" > /dev/null || _re 'Unknown target: $target. Run '$0 --help' for available targets.'
|
|
|
|
case "$target" in
|
|
build|serve)
|
|
"$func_name" "${title:-}" "$goatcounter_url" \
|
|
"$giscus_repo" "$giscus_repo_id" \
|
|
"$giscus_category" "$giscus_category_id" ;;
|
|
*)
|
|
"$func_name" "$@" ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|
|
|