splux-site · log · files · refs · tags · releases · people · languages · GitHub · file
tools/build-git.sh
53586 bytes at 4c4f12c · GitHub
#!/bin/sh
# Build a static git browser under $OUT/git from local clones.
# Languages follow GitHub Linguist. Authors are GitHub accounts when known.
# Env: OUT CORE EXTRA COMMUNITY SPS SITE GIT_HOST SITE_URL
set -eu
cd "$(CDPATH= cd -P "$(dirname "$0")/.." && pwd)" || exit 1
OUT=${OUT:-_site}
CORE=${CORE:-vendor/core}
EXTRA=${EXTRA:-vendor/extra}
COMMUNITY=${COMMUNITY:-vendor/community}
SPS=${SPS:-vendor/sps}
SITE=${SITE:-.}
SITE_URL=${SITE_URL:-https://splux.robertflexx.dev}
GIT_HOST=${GIT_HOST:-$SITE_URL/git}
GIT_HOST=${GIT_HOST%/}
AWK=tools/git.awk
RENDER=tools/render.awk
LANGAWK=tools/git-lang.awk
MAPFILE=${MAPFILE:-tools/linguist.map}
MAXBLOB=262144
OWNER=RobertFlexx
tab=$(printf '\t')
GIT_KIND=
GIT_REPO=
GIT_USER=
GIT_SHA=
GIT_PATH=
esc() {
awk -f "$AWK" -v mode=one -v text="$1"
}
# cut -f keeps empty TSV columns. IFS=tab read does not.
tsv_cut() {
printf '%s\n' "$1" | cut -f "$2"
}
is_git() {
git -C "$1" rev-parse --is-inside-work-tree >/dev/null 2>&1
}
is_binary_name() {
case $1 in
*.png|*.jpg|*.jpeg|*.gif|*.webp|*.ico|*.woff|*.woff2|*.ttf| \
*.gz|*.xz|*.zst|*.bz2|*.zip|*.tar|*.bin)
return 0 ;;
esac
return 1
}
have_gh() {
command -v gh >/dev/null 2>&1
}
github_of() {
case $1 in
SPS) printf '%s\n' https://github.com/RobertFlexx/SPS ;;
sps-core) printf '%s\n' https://github.com/RobertFlexx/sps-core ;;
sps-extra) printf '%s\n' https://github.com/RobertFlexx/sps-extra ;;
sps-community) printf '%s\n' https://github.com/RobertFlexx/sps-community ;;
splux-site) printf '%s\n' https://github.com/RobertFlexx/splux-site ;;
*) printf '%s\n' "https://github.com/RobertFlexx/$1" ;;
esac
}
desc_of() {
case $1 in
SPS) printf '%s\n' 'Source Package System. Installer and live ISO builder.' ;;
sps-core) printf '%s\n' 'Official base recipe collection.' ;;
sps-extra) printf '%s\n' 'Official extra recipe collection.' ;;
sps-community) printf '%s\n' 'Opt-in community recipe collection. Not enabled by setup.' ;;
splux-site) printf '%s\n' 'This website. Static HTML from the recipe trees.' ;;
*) printf '%s\n' "$1" ;;
esac
}
valid_login() {
case $1 in
""|-*|*-|*[^A-Za-z0-9-]*) return 1 ;;
esac
[ "${#1}" -ge 1 ] && [ "${#1}" -le 39 ]
}
page_begin() {
_dest=$1
_title=$2
_desc=$3
mkdir -p "$(dirname "$_dest")"
{
printf '%s\n' '<!DOCTYPE html>'
printf '%s\n' '<html lang="en">'
printf '%s\n' '<head>'
printf '%s\n' '<meta charset="utf-8">'
printf '%s\n' '<meta name="viewport" content="width=device-width, initial-scale=1">'
printf '<title>%s</title>\n' "$(esc "$_title")"
printf '<meta name="description" content="%s">\n' "$(esc "$_desc")"
printf '%s\n' '<link rel="icon" href="../../../../../assets/favicon.png" type="image/png">'
printf '%s\n' '<link rel="stylesheet" href="../../../../../assets/style.css">'
printf '%s\n' '</head>'
printf '%s\n' '<body>'
printf '%s\n' '<a class="skip" href="#main">Skip to content</a>'
printf '%s\n' '<div class="wrap">'
printf '%s\n' '@@HEADER@@'
printf '<main id="main" class="git-page"'
[ -n "${GIT_KIND-}" ] && printf ' data-kind="%s"' "$(esc "$GIT_KIND")"
[ -n "${GIT_REPO-}" ] && printf ' data-repo="%s"' "$(esc "$GIT_REPO")"
[ -n "${GIT_USER-}" ] && printf ' data-user="%s"' "$(esc "$GIT_USER")"
[ -n "${GIT_SHA-}" ] && printf ' data-sha="%s"' "$(esc "$GIT_SHA")"
[ -n "${GIT_PATH-}" ] && printf ' data-path="%s"' "$(esc "$GIT_PATH")"
printf '>\n'
} >"$_dest"
}
page_end() {
_dest=$1
{
printf '%s\n' '</main>'
printf '%s\n' '@@FOOTER@@'
printf '%s\n' '</div>'
printf '%s\n' '<script src="../../../../../assets/render.js?v=4c4f12c-20261019130111"></script>'
printf '%s\n' '<script src="../../../../../assets/git.js?v=4c4f12c-20261019130111" data-root="../../../../../" data-git="https://splux.robertflexx.dev/git"></script>'
printf '%s\n' '</body></html>'
} >>"$_dest"
}
write_redirect() {
dest=$1
target=$2
mkdir -p "$(dirname "$dest")"
{
printf '%s\n' '<!DOCTYPE html>'
printf '%s\n' '<html lang="en"><head>'
printf '%s\n' '<meta charset="utf-8">'
printf '<meta http-equiv="refresh" content="0;url=%s">\n' "$(esc "$target")"
printf '<link rel="canonical" href="%s">\n' "$(esc "$target")"
printf '<title>Redirect</title></head><body>\n'
printf '<p><a href="%s">Continue</a></p>\n' "$(esc "$target")"
printf '%s\n' '</body></html>'
} >"$dest"
}
lookup_lang() {
_path=$1
_file=$2
[ -s "$_file" ] || return 0
awk -F '\t' -v p="$_path" '$1 == p { print $2 "\t" $4; exit }' "$_file"
}
# Highlight a blob, or render Markdown. Baked HTML matches site/assets/render.js.
render_blob() {
_lang=${1-}
_path=${2-}
case $_path in
*.md|*.mdx|*.markdown|*.mdown|*.MD)
awk -f "$RENDER" -v mode=markdown -v mapfile="$MAPFILE"
return
;;
esac
case $_lang in
Markdown|MDX)
awk -f "$RENDER" -v mode=markdown -v mapfile="$MAPFILE"
;;
*)
awk -f "$RENDER" -v mode=highlight -v lang="$_lang" -v mapfile="$MAPFILE"
;;
esac
}
source_archives() {
_repo=$1
_tag=$2
_gh=$(github_of "$_repo")
_enc=$(pct_seg "$_tag")
printf '%s\n' '<h3>Source</h3>'
printf '%s\n' '<ul class="plain">'
printf '<li><a href="%s/archive/refs/tags/%s.zip">%s.zip</a></li>\n' \
"$(esc "$_gh")" "$(esc "$_enc")" "$(esc "$_tag")"
printf '<li><a href="%s/archive/refs/tags/%s.tar.gz">%s.tar.gz</a></li>\n' \
"$(esc "$_gh")" "$(esc "$_enc")" "$(esc "$_tag")"
printf '%s\n' '</ul>'
}
# Local tags plus GitHub tags/releases. Writes .tags.tsv .releases.tsv .assets.tsv
# and decoded note bodies under .bodies/.
collect_repo_meta() {
_name=$1
_dir=$2
_base=$3
_tagstsv=$_base/.tags.tsv
_reltsv=$_base/.releases.tsv
_assetstsv=$_base/.assets.tsv
_bodies=$_base/.bodies
_ghtags=$_base/.tags-gh.tsv
_local=$_base/.tags-local.tsv
_rawrel=$_base/.releases-raw.tsv
_names=$_base/.tag-names
mkdir -p "$_bodies"
: >"$_tagstsv"
: >"$_reltsv"
: >"$_assetstsv"
: >"$_names"
if ! fetch_github_tags "$_name" "$_ghtags"; then
: >"$_ghtags"
fi
git -C "$_dir" for-each-ref --sort=-creatordate \
--format='%(objectname)%09%(objecttype)%09%(refname:short)%09%(creatordate:iso-strict)%09%(taggername)%09%(*objectname)%09%(contents:subject)' \
refs/tags 2>/dev/null |
awk -F '\t' -v OFS='\t' '{
subj = $7
for (i = 8; i <= NF; i++)
subj = subj " " $i
gsub(/\t/, " ", subj)
print $1, $2, $3, $4, $5, $6, subj
}' >"$_local" || : >"$_local"
if [ -s "$_local" ]; then
while IFS= read -r _line || [ -n "${_line-}" ]
do
[ -n "${_line-}" ] || continue
_obj=$(tsv_cut "$_line" 1)
_otype=$(tsv_cut "$_line" 2)
_tname=$(tsv_cut "$_line" 3)
_iso=$(tsv_cut "$_line" 4)
_peeled=$(tsv_cut "$_line" 6)
_subj=$(tsv_cut "$_line" 7)
[ -n "$_tname" ] || continue
if [ "$_otype" = tag ]; then
_kind=annotated
_sha=$_peeled
[ -n "$_sha" ] || _sha=$_obj
else
_kind=lightweight
_sha=$_obj
fi
if [ -z "$_sha" ] && [ -s "$_ghtags" ]; then
_sha=$(awk -F '\t' -v n="$_tname" '$1 == n { print $2; exit }' "$_ghtags")
fi
_short=$(printf '%s' "$_sha" | cut -c1-7)
_seg=$(pct_seg "$_tname")
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$_tname" "$_sha" "$_short" "$_iso" "$_kind" "$_subj" "" "$_seg" \
>>"$_tagstsv"
printf '%s\n' "$_tname" >>"$_names"
done <"$_local"
fi
if [ -s "$_ghtags" ]; then
while IFS= read -r _line || [ -n "${_line-}" ]
do
[ -n "${_line-}" ] || continue
_tname=$(tsv_cut "$_line" 1)
_sha=$(tsv_cut "$_line" 2)
[ -n "$_tname" ] || continue
if [ -s "$_names" ] && grep -Fxq "$_tname" "$_names"; then
continue
fi
_short=$(printf '%s' "$_sha" | cut -c1-7)
_seg=$(pct_seg "$_tname")
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$_tname" "$_sha" "$_short" "" "lightweight" "" "" "$_seg" \
>>"$_tagstsv"
printf '%s\n' "$_tname" >>"$_names"
done <"$_ghtags"
fi
_latest=
if fetch_github_releases "$_name" "$_rawrel"; then
fetch_github_release_assets "$_name" "$_assetstsv" || : >"$_assetstsv"
_latest=$(fetch_github_latest_tag "$_name" 2>/dev/null || true)
while IFS= read -r _line || [ -n "${_line-}" ]
do
[ -n "${_line-}" ] || continue
_rtag=$(tsv_cut "$_line" 1)
_title=$(tsv_cut "$_line" 2)
_riso=$(tsv_cut "$_line" 3)
_rlogin=$(tsv_cut "$_line" 4)
_ravatar=$(tsv_cut "$_line" 5)
_pre=$(tsv_cut "$_line" 6)
_react=$(tsv_cut "$_line" 9)
_b64=$(tsv_cut "$_line" 10)
[ -n "$_rtag" ] || continue
[ -n "$_title" ] || _title=$_rtag
_seg=$(pct_seg "$_rtag")
_body=$_bodies/$_seg
if [ -n "$_b64" ]; then
printf '%s' "$_b64" | base64 -d >"$_body" 2>/dev/null || : >"$_body"
else
: >"$_body"
fi
_excerpt=$(excerpt_text "$_body")
_islatest=no
[ "$_rtag" = "$_latest" ] && _islatest=yes
_nassets=0
if [ -s "$_assetstsv" ]; then
_nassets=$(awk -F '\t' -v t="$_rtag" '$1 == t { c++ } END { print c + 0 }' \
"$_assetstsv")
fi
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$_rtag" "$_title" "$_riso" "$_rlogin" "$_ravatar" "$_pre" \
"$_islatest" "$_nassets" "$_excerpt" "$_react" "$_seg" \
>>"$_reltsv"
if [ -s "$_names" ] && grep -Fxq "$_rtag" "$_names"; then
:
else
_rsha=$(awk -F '\t' -v n="$_rtag" '$1 == n { print $2; exit }' "$_ghtags")
_rshort=$(printf '%s' "$_rsha" | cut -c1-7)
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$_rtag" "$_rsha" "$_rshort" "$_riso" "lightweight" "" \
"$_rtag" "$_seg" >>"$_tagstsv"
printf '%s\n' "$_rtag" >>"$_names"
fi
done <"$_rawrel"
fi
if [ -s "$_reltsv" ]; then
if ! awk -F '\t' '$7 == "yes" { found = 1; exit } END { exit found ? 0 : 1 }' "$_reltsv"
then
awk -F '\t' -v OFS='\t' '
{
n++
line[n] = $0
pre[n] = $6
}
END {
pick = 0
for (i = 1; i <= n; i++) {
if (pre[i] != "yes") {
pick = i
break
}
}
if (!pick)
pick = 1
for (i = 1; i <= n; i++) {
split(line[i], f, "\t")
f[7] = (i == pick) ? "yes" : "no"
out = f[1]
for (j = 2; j <= 11; j++)
out = out "\t" f[j]
print out
}
}
' "$_reltsv" >"$_base/.releases-mark.tsv"
mv "$_base/.releases-mark.tsv" "$_reltsv"
fi
awk -F '\t' -v OFS='\t' '
FNR == NR { has[$1] = 1; next }
{
$7 = ($1 in has) ? $1 : ""
print
}
' "$_reltsv" "$_tagstsv" >"$_base/.tags-joined.tsv"
mv "$_base/.tags-joined.tsv" "$_tagstsv"
fi
rm -f "$_ghtags" "$_local" "$_rawrel" "$_names"
}
colorize_langs() {
awk -F '\t' -v OFS='\t' '
FNR == NR {
if ($1 == "lang" && $3 != "")
c[$3] = $5
next
}
{
col = ($3 != "") ? $3 : c[$1]
print $1, $2, col
}
' "$MAPFILE" "$1"
}
local_lang_scan() {
_dir=$1
_dest=$2
git -C "$_dir" ls-tree -r -l HEAD | while IFS= read -r line
do
set -- $line
[ "${2-}" = blob ] || continue
_sz=$4
_obj=$3
shift 4
_path=$*
[ -n "$_path" ] || continue
_base=${_path##*/}
_shebang=
case $_base in
*.*|recipe) ;;
*)
if ! is_binary_name "$_base"; then
_shebang=$(git -C "$_dir" cat-file blob "$_obj" | awk 'NR==1 { print; exit }' | tr '\t\r' ' ')
case $_shebang in
'#!'*) ;;
*) _shebang= ;;
esac
fi
;;
esac
printf '%s\t%s\t%s\n' "$_sz" "$_path" "$_shebang"
done >"$_dest"
}
fetch_github_langs() {
_repo=$1
_dest=$2
have_gh || return 1
gh api "repos/$OWNER/$_repo/languages" --jq \
'to_entries | sort_by(-.value) | .[] | [.key, (.value|tostring)] | @tsv' \
>"$_dest" 2>/dev/null && [ -s "$_dest" ]
}
fetch_github_commits() {
_repo=$1
_dest=$2
have_gh || return 1
gh api --paginate "repos/$OWNER/$_repo/commits?per_page=100" --jq \
'.[] | [
.sha,
(.author.login // .committer.login // ""),
((.author.avatar_url // .committer.avatar_url // "") | gsub("[\r\t]"; " ")),
(if .commit.verification.verified == true then "yes" else "no" end)
] | @tsv' >"$_dest" 2>/dev/null && [ -s "$_dest" ]
}
fetch_github_user() {
_login=$1
_dest=$2
have_gh || return 1
valid_login "$_login" || return 1
gh api "users/$_login" --jq \
'[
.login,
((.name // "") | gsub("[\r\t\n]"; " ")),
((.bio // "") | gsub("[\r\t\n]"; " ")),
(.avatar_url // ""),
(.html_url // ""),
(.public_repos | tostring),
(.followers | tostring),
((.company // "") | gsub("[\r\t\n]"; " ")),
((.location // "") | gsub("[\r\t\n]"; " ")),
((.blog // "") | gsub("[\r\t\n]"; " "))
] | @tsv' >"$_dest" 2>/dev/null && [ -s "$_dest" ]
}
# One URL path segment. Keep unreserved characters, percent-encode the rest.
pct_seg() {
printf '%s' "$1" | LC_ALL=C awk '
BEGIN {
hex = "0123456789ABCDEF"
for (i = 0; i < 256; i++)
ord[sprintf("%c", i)] = i
}
{
for (i = 1; i <= length($0); i++) {
c = substr($0, i, 1)
if (c ~ /[A-Za-z0-9._~-]/)
printf "%s", c
else {
o = ord[c] + 0
printf "%%%s%s", substr(hex, int(o / 16) + 1, 1), \
substr(hex, (o % 16) + 1, 1)
}
}
}'
}
fetch_github_tags() {
_repo=$1
_dest=$2
have_gh || return 1
gh api --paginate "repos/$OWNER/$_repo/tags?per_page=100" --jq \
'.[] | [.name, .commit.sha] | @tsv' \
>"$_dest" 2>/dev/null && [ -s "$_dest" ]
}
fetch_github_releases() {
_repo=$1
_dest=$2
have_gh || return 1
gh api --paginate "repos/$OWNER/$_repo/releases?per_page=100" --jq \
'.[] | select(.draft == false) | [
.tag_name,
((.name // .tag_name) | gsub("[\r\t\n]"; " ")),
(.published_at // .created_at // ""),
(.author.login // ""),
((.author.avatar_url // "") | gsub("[\r\t]"; " ")),
(if .prerelease then "yes" else "no" end),
(.html_url // ""),
((.target_commitish // "") | gsub("[\r\t]"; " ")),
((.reactions.total_count // 0) | tostring),
((.body // "") | @base64)
] | @tsv' >"$_dest" 2>/dev/null && [ -s "$_dest" ]
}
fetch_github_release_assets() {
_repo=$1
_dest=$2
have_gh || return 1
gh api --paginate "repos/$OWNER/$_repo/releases?per_page=100" --jq \
'.[] | select(.draft == false) | .tag_name as $t |
(.assets[]? | [
$t,
((.name // "") | gsub("[\r\t\n]"; " ")),
(.size | tostring),
(.download_count | tostring),
(.browser_download_url // ""),
((.content_type // "") | gsub("[\r\t]"; " "))
] | @tsv)' >"$_dest" 2>/dev/null
return 0
}
fetch_github_latest_tag() {
_repo=$1
have_gh || return 1
gh api "repos/$OWNER/$_repo/releases/latest" --jq .tag_name 2>/dev/null
}
excerpt_text() {
awk 'BEGIN { n = 0 }
{
line = $0
gsub(/[ \t]+/, " ", line)
if (n > 0)
buf = buf " "
buf = buf line
n++
}
END {
if (length(buf) > 400)
buf = substr(buf, 1, 400) "..."
gsub(/\t/, " ", buf)
print buf
}' "$1"
}
who_cell() {
awk -f "$AWK" -v mode=who \
-v userpfx="$1" -v login="$2" -v name="$3" \
-v avatar="$4" -v verified="$5"
}
lang_slug() {
printf '%s\n' "$1" | tr ' /' '--'
}
# In-repo nav. Links go through ../../../../../git/<repo>/ so they work
# without a trailing slash on the current URL.
git_nav() {
_repo=$1
_here=$2
_r="../../../../../git/${_repo}/"
_gh=$(github_of "$_repo")
printf '<p class="git-nav">'
if [ "$_here" = home ]; then
printf '%s' "$(esc "$_repo")"
else
printf '<a href="%s">%s</a>' "$(esc "$_r")" "$(esc "$_repo")"
fi
printf ' · '
if [ "$_here" = log ]; then
printf 'log'
else
printf '<a href="%slog/">log</a>' "$(esc "$_r")"
fi
printf ' · '
if [ "$_here" = files ]; then
printf 'files'
else
printf '<a href="%stree/">files</a>' "$(esc "$_r")"
fi
printf ' · '
if [ "$_here" = refs ]; then
printf 'refs'
else
printf '<a href="%srefs/">refs</a>' "$(esc "$_r")"
fi
printf ' · '
if [ "$_here" = tags ] || [ "$_here" = tag ]; then
printf 'tags'
else
printf '<a href="%stags/">tags</a>' "$(esc "$_r")"
fi
printf ' · '
if [ "$_here" = releases ] || [ "$_here" = release ] || [ "$_here" = latest ]; then
printf 'releases'
else
printf '<a href="%sreleases/">releases</a>' "$(esc "$_r")"
fi
printf ' · '
if [ "$_here" = people ]; then
printf 'people'
else
printf '<a href="%speople/">people</a>' "$(esc "$_r")"
fi
printf ' · '
if [ "$_here" = langs ] || [ "$_here" = lang ]; then
printf 'languages'
else
printf '<a href="%slang/">languages</a>' "$(esc "$_r")"
fi
printf ' · <a href="%s">GitHub</a>' "$(esc "$_gh")"
if [ "$_here" = commit ]; then
printf ' · commit'
fi
if [ "$_here" = blob ]; then
printf ' · file'
fi
if [ "$_here" = tag ]; then
printf ' · tag'
fi
if [ "$_here" = release ]; then
printf ' · release'
fi
if [ "$_here" = latest ]; then
printf ' · latest'
fi
printf '</p>\n'
}
mkdir -p "$OUT/git" "$OUT/data" "$OUT/git/users"
# Refresh Linguist map when the network allows; keep the committed map otherwise.
if command -v curl >/dev/null 2>&1; then
yml=$OUT/data/.languages.yml
if curl -fsSL --max-time 20 \
-o "$yml" \
https://raw.githubusercontent.com/github-linguist/linguist/master/lib/linguist/languages.yml
then
if awk -f tools/linguist-extract.awk "$yml" >"$OUT/data/.linguist.map" &&
[ -s "$OUT/data/.linguist.map" ]
then
MAPFILE=$OUT/data/.linguist.map
fi
fi
rm -f "$yml"
fi
if [ ! -s "$MAPFILE" ]; then
printf '%s\n' "git: missing $MAPFILE" >&2
exit 1
fi
awk -F '\t' '
function jesc(s) {
gsub(/\\/, "\\\\", s)
gsub(/"/, "\\\"", s)
return s
}
BEGIN { printf "{" }
$1 == "lang" && $3 != "" && $5 != "" {
if (n++) printf ","
printf "\"%s\":\"%s\"", jesc($3), jesc($5)
}
END { print "}" }
' "$MAPFILE" >"$OUT/data/linguist-colors.json"
awk -f "$RENDER" -v mode=famjson -v mapfile="$MAPFILE" \
</dev/null >"$OUT/data/linguist-fams.json"
index_rows=$OUT/data/git-index.tsv
all_commits=$OUT/data/git-commits.tsv
all_people=$OUT/data/git-people.tsv
: >"$index_rows"
: >"$all_commits"
: >"$all_people"
build_repo() {
name=$1
dir=$2
is_git "$dir" || return 0
gh=$(github_of "$name")
repo_desc=$(desc_of "$name")
head=$(git -C "$dir" rev-parse HEAD)
short=$(git -C "$dir" rev-parse --short HEAD)
branch=$(git -C "$dir" rev-parse --abbrev-ref HEAD 2>/dev/null || printf '%s\n' HEAD)
lastiso=$(git -C "$dir" log -1 --format='%cI')
ncommits=$(git -C "$dir" rev-list --count HEAD)
nfiles=$(git -C "$dir" ls-tree -r --name-only HEAD | wc -l)
nfiles=$(printf '%s' "$nfiles" | tr -d ' ')
base=$OUT/git/$name
mkdir -p "$base/log" "$base/refs" "$base/tree" "$base/commit" "$base/blob" \
"$base/people" "$base/lang" "$base/tags" "$base/releases/tag" \
"$base/releases/latest" "$base/.bodies"
userpfx="../../../../../git/users/"
reporoot="../../../../../git/$name/"
scan=$base/.scan.tsv
filelang=$base/.filelang.tsv
langtmp=$base/.langs.tsv
local_lang_scan "$dir" "$scan"
awk -f "$LANGAWK" -v mode=count -v mapfile="$MAPFILE" -v filelang="$filelang" \
"$scan" >"$base/.langs-local.tsv"
rm -f "$scan"
ghl=$base/.langs-gh.tsv
if fetch_github_langs "$name" "$ghl"; then
colorize_langs "$ghl" | LC_ALL=C sort -t "$(printf '\t')" -k2,2nr >"$langtmp"
else
LC_ALL=C sort -t "$(printf '\t')" -k2,2nr "$base/.langs-local.tsv" >"$langtmp"
fi
rm -f "$ghl" "$base/.langs-local.tsv"
cp "$langtmp" "$OUT/data/git-langs-$name.tsv"
clog=$base/.commits.tsv
git -C "$dir" log --format='%H%x09%h%x09%ct%x09%cI%x09%an%x09%s' |
awk -F '\t' 'BEGIN { OFS="\t" } {
subj=$6
for (i = 7; i <= NF; i++) subj = subj " " $i
gsub(/\t/, " ", subj)
print $1, $2, $3, $4, subj, $5
}' >"$base/.commits-local.tsv"
ghc=$base/.commits-gh.tsv
if fetch_github_commits "$name" "$ghc"; then
awk -F '\t' -v OFS='\t' '
FNR == NR { login[$1]=$2; av[$1]=$3; ver[$1]=$4; next }
{ print $1, $2, $3, $4, $5, $6, login[$1], av[$1], ver[$1] }
' "$ghc" "$base/.commits-local.tsv" >"$clog"
else
awk -F '\t' -v OFS='\t' '{ print $0, "", "", "" }' \
"$base/.commits-local.tsv" >"$clog"
fi
rm -f "$ghc" "$base/.commits-local.tsv"
awk -F '\t' -v OFS='\t' -v repo="$name" '{
print repo, $1, $2, $3, $4, $5, $6, $7, $8, $9
}' "$clog" >>"$all_commits"
awk -F '\t' -v OFS='\t' '{
login=$7
name=$6
key = (login != "") ? login : name
if (key == "") next
c[key]++
if (login != "")
haslogin[key]=login
if ($8 != "" && !(key in avatar))
avatar[key]=$8
if (name != "" && !(key in author))
author[key]=name
epoch=$3+0
if (!(key in last) || epoch >= last[key]) {
last[key]=epoch
iso[key]=$4
subj[key]=$5
sha[key]=$1
}
}
END {
for (k in c)
print (k in haslogin) ? haslogin[k] : "", avatar[k], c[k], \
(k in author) ? author[k] : k, iso[k], subj[k], sha[k]
}' "$clog" | LC_ALL=C sort -t "$(printf '\t')" -k3,3nr >"$base/.people.tsv"
if [ -s "$base/.people.tsv" ]; then
awk -F '\t' -v OFS='\t' '$1 != "" { print $1, $2, $3 }' \
"$base/.people.tsv" >>"$all_people"
fi
# Languages with file counts for the bar and the language index.
awk -F '\t' -v OFS='\t' -v fl="$filelang" '
BEGIN {
while ((getline < fl) > 0) {
path=$1
name=$2
col=$4
sz=$5+0
group=($6 != "") ? $6 : $2
if (name != "") {
key = name SUBSEP path
if (!(key in seen)) {
seen[key]=1
nf[name]++
fsz[name]+=sz
}
if (col != "" && !(name in color))
color[name]=col
}
if (group != "" && group != name) {
key = group SUBSEP path
if (!(key in seen)) {
seen[key]=1
nf[group]++
fsz[group]+=sz
}
}
}
close(fl)
}
{
gh[$1]=$2+0
if ($3 != "")
color[$1]=$3
bar[$1]=1
}
END {
for (k in nf)
all[k]=1
for (k in bar)
all[k]=1
n=0
for (k in all) {
n++
names[n]=k
}
for (i = 1; i <= n; i++) {
for (j = i + 1; j <= n; j++) {
bi = (names[i] in gh) ? gh[names[i]] : fsz[names[i]]+0
bj = (names[j] in gh) ? gh[names[j]] : fsz[names[j]]+0
if (bj > bi) {
t=names[i]; names[i]=names[j]; names[j]=t
}
}
}
for (i = 1; i <= n; i++) {
k=names[i]
print k, gh[k]+0, color[k], nf[k]+0, fsz[k]+0
}
}
' "$langtmp" >"$base/.lang-index.tsv"
awk -F '\t' -v OFS='\t' '$2 + 0 > 0 { print $1, $2, $3, $4 }' \
"$base/.lang-index.tsv" >"$base/.langs-bar.tsv"
if [ -s "$base/.langs-bar.tsv" ]; then
mv "$base/.langs-bar.tsv" "$langtmp"
else
rm -f "$base/.langs-bar.tsv"
fi
collect_repo_meta "$name" "$dir" "$base"
tagstsv=$base/.tags.tsv
reltsv=$base/.releases.tsv
assetstsv=$base/.assets.tsv
n_tags=0
n_releases=0
latest_tag=
if [ -s "$tagstsv" ]; then
n_tags=$(wc -l <"$tagstsv" | tr -d ' ')
fi
if [ -s "$reltsv" ]; then
n_releases=$(wc -l <"$reltsv" | tr -d ' ')
latest_tag=$(awk -F '\t' '$7 == "yes" { print $1; exit }' "$reltsv")
fi
GIT_KIND=repo
GIT_REPO=$name
GIT_USER=
GIT_SHA=
GIT_PATH=
page_begin "$base/index.html" "$name - Splux Git" "$repo_desc"
{
printf '<h2>%s</h2>\n' "$(esc "$name")"
printf '%s\n' '<hr class="rule">'
printf '<p>%s</p>\n' "$(esc "$repo_desc")"
git_nav "$name" home
printf '%s\n' '<p class="live-note" id="git-live" hidden>Showing newer commits from GitHub.</p>'
printf '%s\n' '<div class="info" id="git-info">'
printf '<div class="dl-row"><span class="muted">HEAD</span><span id="git-head"><a href="commit/%s/"><code>%s</code></a> (%s)</span></div>\n' \
"$(esc "$head")" "$(esc "$short")" "$(esc "$branch")"
printf '<div class="dl-row"><span class="muted">Commits</span><span id="git-ncommits">%s</span></div>\n' \
"$(esc "$ncommits")"
printf '<div class="dl-row"><span class="muted">Files</span><span>%s</span></div>\n' \
"$(esc "$nfiles")"
printf '<div class="dl-row"><span class="muted">Tags</span><span id="git-ntags"><a href="%stags/">%s</a></span></div>\n' \
"$(esc "$reporoot")" "$(esc "$n_tags")"
printf '<div class="dl-row"><span class="muted">Releases</span><span id="git-nreleases"><a href="%sreleases/">%s</a></span></div>\n' \
"$(esc "$reporoot")" "$(esc "$n_releases")"
if [ -n "$latest_tag" ]; then
printf '<div class="dl-row"><span class="muted">Latest</span><span id="git-latest"><a href="%sreleases/latest/"><code>%s</code></a></span></div>\n' \
"$(esc "$reporoot")" "$(esc "$latest_tag")"
else
printf '%s\n' '<div class="dl-row"><span class="muted">Latest</span><span id="git-latest" class="muted">none</span></div>'
fi
printf '<div class="dl-row"><span class="muted">Clone</span><span><code>git clone %s.git</code></span></div>\n' \
"$(esc "$gh")"
printf '%s\n' '</div>'
if [ -s "$langtmp" ]; then
printf '%s\n' '<h3>Languages</h3>'
printf '%s\n' '<div id="git-langs">'
awk -f "$AWK" -v mode=langs -v langpfx="${reporoot}lang/" "$langtmp"
printf '%s\n' '</div>'
else
printf '%s\n' '<div id="git-langs"></div>'
fi
if [ -s "$base/.people.tsv" ]; then
printf '%s\n' '<h3>People</h3>'
awk -f "$AWK" -v mode=people -v userpfx="$userpfx" "$base/.people.tsv"
printf '<p><a href="%speople/">All contributors</a></p>\n' "$(esc "$reporoot")"
fi
printf '%s\n' '<h3>Recent commits</h3>'
printf '%s\n' '<div id="git-recent">'
awk -f "$AWK" -v mode=commits -v commitpfx="${reporoot}commit/" \
-v userpfx="$userpfx" -v limit=20 "$clog"
printf '%s\n' '</div>'
printf '%s\n' '<p><a href="log/">Full log</a></p>'
if git -C "$dir" cat-file -e HEAD:README.md 2>/dev/null; then
printf '%s\n' '<h3>README.md</h3>'
git -C "$dir" show HEAD:README.md | render_blob Markdown README.md
elif git -C "$dir" cat-file -e HEAD:README 2>/dev/null; then
printf '%s\n' '<h3>README</h3>'
git -C "$dir" show HEAD:README | render_blob Markdown README
fi
} >>"$base/index.html"
page_end "$base/index.html"
GIT_KIND=log
page_begin "$base/log/index.html" "Log - $name - Splux Git" "Commit log for $name"
{
git_nav "$name" log
printf '<h2>Log</h2>\n'
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<p class="live-note" id="git-live" hidden>Showing newer commits from GitHub.</p>'
printf '%s\n' '<div id="git-log">'
awk -f "$AWK" -v mode=commits -v commitpfx="${reporoot}commit/" \
-v userpfx="$userpfx" "$clog"
printf '%s\n' '</div>'
} >>"$base/log/index.html"
page_end "$base/log/index.html"
GIT_KIND=refs
page_begin "$base/refs/index.html" "Refs - $name - Splux Git" "Branches and tags for $name"
{
git_nav "$name" refs
printf '<h2>Refs</h2>\n'
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<h3>Branches</h3>'
printf '%s\n' '<ul class="plain">'
git -C "$dir" for-each-ref --format='%(objectname) %(refname:short)' refs/heads |
while IFS= read -r line
do
[ -n "$line" ] || continue
h=${line%% *}
r=${line#* }
printf '<li><a href="../commit/%s/">%s</a> <code>%s</code></li>\n' \
"$(esc "$h")" "$(esc "$r")" "$(esc "${h%"${h#???????}"}")"
done
printf '%s\n' '</ul>'
printf '%s\n' '<h3>Tags</h3>'
printf '<p><a href="%stags/">All tags</a>' "$(esc "$reporoot")"
if [ "$n_tags" -gt 0 ]; then
printf ' · %s' "$(esc "$n_tags")"
fi
printf '</p>\n'
if [ -s "$tagstsv" ]; then
head -n 15 "$tagstsv" |
awk -f "$AWK" -v mode=tags \
-v tagpfx="${reporoot}tags/" \
-v commitpfx="${reporoot}commit/" \
-v relpfx="${reporoot}releases/tag/"
if [ "$n_tags" -gt 15 ]; then
printf '<p class="muted">%s more on the <a href="%stags/">tags</a> page.</p>\n' \
"$(esc $((n_tags - 15)))" "$(esc "$reporoot")"
fi
else
printf '%s\n' '<p class="muted">No tags in this tree.</p>'
fi
} >>"$base/refs/index.html"
page_end "$base/refs/index.html"
while IFS= read -r _line || [ -n "${_line-}" ]
do
[ -n "${_line-}" ] || continue
hash=$(tsv_cut "$_line" 1)
shortc=$(tsv_cut "$_line" 2)
iso=$(tsv_cut "$_line" 4)
subject=$(tsv_cut "$_line" 5)
author=$(tsv_cut "$_line" 6)
login=$(tsv_cut "$_line" 7)
avatar=$(tsv_cut "$_line" 8)
verified=$(tsv_cut "$_line" 9)
[ -n "$hash" ] || continue
cdir=$base/commit/$hash
mkdir -p "$cdir"
GIT_KIND=commit
GIT_SHA=$hash
GIT_PATH=
page_begin "$cdir/index.html" "$shortc - $name - Splux Git" "$subject"
{
git_nav "$name" commit
printf '<h2><code>%s</code></h2>\n' "$(esc "$shortc")"
printf '%s\n' '<hr class="rule">'
printf '<p>%s</p>\n' "$(esc "$subject")"
printf '%s\n' '<div class="info">'
printf '<div class="dl-row"><span class="muted">Author</span><span>%s</span></div>\n' \
"$(who_cell "$userpfx" "$login" "$author" "$avatar" "$verified")"
printf '<div class="dl-row"><span class="muted">Date</span><span><time datetime="%s">%s</time></span></div>\n' \
"$(esc "$iso")" "$(esc "$iso")"
printf '<div class="dl-row"><span class="muted">Commit</span><span><code>%s</code></span></div>\n' \
"$(esc "$hash")"
printf '<div class="dl-row"><span class="muted">GitHub</span><span><a href="%s/commit/%s">mirror</a></span></div>\n' \
"$(esc "$gh")" "$(esc "$hash")"
printf '%s\n' '</div>'
printf '%s\n' '<h3>Parents</h3>'
printf '%s\n' '<ul class="plain">'
git -C "$dir" rev-list --parents -n 1 "$hash" | {
read -r self rest || rest=
for p in $rest
do
ps=$(git -C "$dir" rev-parse --short "$p")
printf '<li><a href="../%s/"><code>%s</code></a></li>\n' \
"$(esc "$p")" "$(esc "$ps")"
done
}
printf '%s\n' '</ul>'
printf '%s\n' '<h3>Stat</h3>'
git -C "$dir" show --stat --format= "$hash" | awk -f "$AWK" -v mode=pre
printf '%s\n' '<h3>Diff</h3>'
git -C "$dir" show --format= --color=never "$hash" |
dd bs=1024 count=300 2>/dev/null |
render_blob Diff "$hash.diff"
} >>"$cdir/index.html"
page_end "$cdir/index.html"
if [ -n "$shortc" ] && [ "$shortc" != "$hash" ]; then
write_redirect "$base/commit/$shortc/index.html" "../$hash/"
fi
done <"$clog"
write_tree() {
treepath=${1-}
GIT_KIND=tree
GIT_SHA=
GIT_PATH=$treepath
if [ -n "$treepath" ]; then
tdest=$base/tree/$treepath/index.html
title="$treepath - $name - Splux Git"
else
tdest=$base/tree/index.html
title="Files - $name - Splux Git"
fi
if [ -z "$treepath" ]; then
up_repo="../"
blobpfx="../blob/"
treepfx="./"
else
depth=1
rest=$treepath
while [ "$rest" != "${rest#*/}" ]
do
depth=$((depth + 1))
rest=${rest#*/}
done
up=""
j=0
while [ "$j" -le "$depth" ]
do
up="../$up"
j=$((j + 1))
done
up_repo=$up
blobpfx=$up"blob/"
treepfx="./"
fi
page_begin "$tdest" "$title" "HEAD tree for $name"
{
git_nav "$name" files
if [ -n "$treepath" ]; then
printf '<h2>%s</h2>\n' "$(esc "$treepath")"
else
printf '%s\n' '<h2>Files</h2>'
fi
printf '%s\n' '<hr class="rule">'
if [ -n "$treepath" ]; then
printf '<p><a href="../">../</a></p>\n'
fi
printf '%s\n' '<p><label for="git-filter">Filter</label> <input id="git-filter" type="search" placeholder="file"></p>'
ent=$base/.entries.tsv
: >"$ent"
if [ -n "$treepath" ]; then
git -C "$dir" ls-tree -l "HEAD:$treepath"
else
git -C "$dir" ls-tree -l HEAD
fi | while IFS= read -r line
do
set -- $line
[ $# -ge 5 ] || continue
kind=$2
sz=$4
shift 4
path=$*
basepath=${path##*/}
[ -n "$basepath" ] || continue
if [ "$kind" = tree ]; then
printf 'dir\t\t%s\t%s\t\n' "$basepath/" "$treepfx$basepath/"
elif [ "$kind" = blob ]; then
if [ -n "$treepath" ]; then
fullpath=$treepath/$basepath
else
fullpath=$basepath
fi
fl=$(lookup_lang "$fullpath" "$filelang")
flang=${fl%%$tab*}
printf 'file\t%s\t%s\t%s\t%s\n' "$sz" "$basepath" "$blobpfx$fullpath/" "$flang"
fi
done >"$ent"
if [ -s "$ent" ]; then
awk -f "$AWK" -v mode=entries -v langpfx="${reporoot}lang/" "$ent"
else
printf '%s\n' '<p class="muted">empty directory</p>'
fi
} >>"$tdest"
page_end "$tdest"
}
write_tree ""
git -C "$dir" ls-tree -r -d --name-only HEAD | while IFS= read -r d
do
[ -n "$d" ] || continue
write_tree "$d"
done
git -C "$dir" ls-tree -r -l HEAD | while IFS= read -r line
do
set -- $line
[ "$2" = blob ] || continue
sz=$4
obj=$3
shift 4
path=$*
[ -n "$path" ] || continue
bdest=$base/blob/$path/index.html
GIT_KIND=blob
GIT_SHA=
GIT_PATH=$path
page_begin "$bdest" "$path - $name - Splux Git" "$path at HEAD"
fl=$(lookup_lang "$path" "$filelang")
flang=${fl%%$tab*}
fcolor=${fl#*$tab}
[ "$fcolor" = "$fl" ] && fcolor=
{
git_nav "$name" blob
printf '<h2>%s</h2>\n' "$(esc "$path")"
printf '%s\n' '<hr class="rule">'
if [ -n "$flang" ]; then
printf '<p class="lang-badge"><a href="%slang/%s/"><span class="lang-dot" style="background:%s"></span> %s</a></p>\n' \
"$(esc "$reporoot")" "$(esc "$(lang_slug "$flang")")" \
"$(esc "${fcolor:-#888888}")" "$(esc "$flang")"
fi
printf '<p class="muted">%s bytes at <a href="%scommit/%s/">%s</a> · <a href="%s/blob/main/%s">GitHub</a></p>\n' \
"$(esc "$sz")" "$(esc "$reporoot")" "$(esc "$head")" "$(esc "$short")" \
"$(esc "$gh")" "$(esc "$path")"
szn=$(printf '%s' "$sz" | tr -d ' ')
case $szn in
''|*[!0-9]*) szn=0 ;;
esac
if is_binary_name "$path"; then
printf '%s\n' '<p>Binary file. Clone the repository or open the GitHub mirror.</p>'
elif [ "$szn" -gt "$MAXBLOB" ]; then
printf '%s\n' '<p>File is too large to embed here. Clone the repository or open the GitHub mirror.</p>'
else
git -C "$dir" cat-file blob "$obj" | render_blob "$flang" "$path"
fi
} >>"$bdest"
page_end "$bdest"
done
GIT_KIND=people
GIT_SHA=
GIT_PATH=
page_begin "$base/people/index.html" "People - $name - Splux Git" \
"Commit authors in $name"
{
git_nav "$name" people
printf '%s\n' '<h2>People</h2>'
printf '%s\n' '<hr class="rule">'
printf '<p>Everyone who authored a commit in %s. GitHub accounts link to their profile. Authors without a GitHub login are listed by git name. <a href="../../../../../git/users/">All people</a></p>\n' \
"$(esc "$name")"
printf '%s\n' '<p class="live-note" id="git-live" hidden>Updated from GitHub.</p>'
printf '%s\n' '<p><label for="git-filter">Filter</label> <input id="git-filter" type="search" placeholder="person"></p>'
if [ -s "$base/.people.tsv" ]; then
awk -f "$AWK" -v mode=peoplelist -v userpfx="$userpfx" \
-v commitpfx="${reporoot}commit/" "$base/.people.tsv"
else
printf '%s\n' '<table class="pkgs git-people" id="git-people-table"><thead><tr><th>Person</th><th>Commits</th><th>Last commit</th><th>When</th></tr></thead><tbody></tbody></table>'
fi
} >>"$base/people/index.html"
page_end "$base/people/index.html"
GIT_KIND=langs
GIT_PATH=
page_begin "$base/lang/index.html" "Languages - $name - Splux Git" \
"Languages in $name"
{
git_nav "$name" langs
printf '%s\n' '<h2>Languages</h2>'
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<p>Click a language to see how many files use it.</p>'
printf '%s\n' '<p><label for="git-filter">Filter</label> <input id="git-filter" type="search" placeholder="language"></p>'
if [ -s "$base/.lang-index.tsv" ]; then
awk -f "$AWK" -v mode=langindex -v langpfx="${reporoot}lang/" \
"$base/.lang-index.tsv"
else
printf '%s\n' '<p class="muted">No languages recorded.</p>'
fi
} >>"$base/lang/index.html"
page_end "$base/lang/index.html"
if [ -s "$base/.lang-index.tsv" ]; then
while IFS= read -r _lline || [ -n "${_lline-}" ]
do
[ -n "${_lline-}" ] || continue
langname=$(tsv_cut "$_lline" 1)
nlangfiles=$(tsv_cut "$_lline" 4)
langbytes=$(tsv_cut "$_lline" 5)
[ -n "$langname" ] || continue
lslug=$(lang_slug "$langname")
ldest=$base/lang/$lslug/index.html
GIT_KIND=lang
GIT_PATH=$langname
page_begin "$ldest" "$langname - $name - Splux Git" \
"$langname files in $name"
{
git_nav "$name" lang
printf '<h2>%s</h2>\n' "$(esc "$langname")"
printf '%s\n' '<hr class="rule">'
printf '<p>%s files' "$(esc "${nlangfiles:-0}")"
if [ -n "$langbytes" ] && [ "$langbytes" != 0 ]; then
printf ', %s bytes' "$(esc "$langbytes")"
fi
printf ' · <a href="%slang/">all languages</a></p>\n' \
"$(esc "$reporoot")"
printf '%s\n' '<p><label for="git-filter">Filter</label> <input id="git-filter" type="search" placeholder="file"></p>'
lf=$base/.lang-files.tsv
awk -F '\t' -v OFS='\t' -v lang="$langname" \
-v blobpfx="${reporoot}blob/" '
$2 == lang || $6 == lang {
print "file", $5, $1, blobpfx $1 "/", $2
}
' "$filelang" | LC_ALL=C sort -t "$(printf '\t')" -k3,3 >"$lf"
if [ -s "$lf" ]; then
awk -f "$AWK" -v mode=entries -v showlang=no "$lf"
else
printf '%s\n' '<p class="muted">No files listed for this language.</p>'
fi
} >>"$ldest"
page_end "$ldest"
rm -f "$lf"
done <"$base/.lang-index.tsv"
fi
GIT_KIND=tags
GIT_SHA=
GIT_PATH=
page_begin "$base/tags/index.html" "Tags - $name - Splux Git" "Tags in $name"
{
git_nav "$name" tags
printf '%s\n' '<h2>Tags</h2>'
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<p class="live-note" id="git-live" hidden>Updated from GitHub.</p>'
printf '%s\n' '<p>Every tag in this tree. Annotated tags keep their message. A tag with a GitHub release links through to that page. Source archives are zip and tar.gz from GitHub.</p>'
printf '%s\n' '<p><label for="git-filter">Filter</label> <input id="git-filter" type="search" placeholder="tag"></p>'
if [ -s "$tagstsv" ]; then
awk -f "$AWK" -v mode=tags \
-v tagpfx="${reporoot}tags/" \
-v commitpfx="${reporoot}commit/" \
-v relpfx="${reporoot}releases/tag/" \
"$tagstsv"
else
printf '%s\n' '<p class="muted">No tags in this tree.</p>'
fi
} >>"$base/tags/index.html"
page_end "$base/tags/index.html"
if [ -s "$tagstsv" ]; then
while IFS= read -r _tline || [ -n "${_tline-}" ]
do
[ -n "${_tline-}" ] || continue
tname=$(tsv_cut "$_tline" 1)
tsha=$(tsv_cut "$_tline" 2)
tshort=$(tsv_cut "$_tline" 3)
tiso=$(tsv_cut "$_tline" 4)
tkind=$(tsv_cut "$_tline" 5)
tsubj=$(tsv_cut "$_tline" 6)
trel=$(tsv_cut "$_tline" 7)
tseg=$(tsv_cut "$_tline" 8)
[ -n "$tname" ] || continue
[ -n "$tseg" ] || tseg=$(pct_seg "$tname")
tdest=$base/tags/$tseg/index.html
GIT_KIND=tag
GIT_PATH=$tname
GIT_SHA=$tsha
page_begin "$tdest" "$tname - $name - Splux Git" "Tag $tname in $name"
{
git_nav "$name" tag
printf '<h2><code>%s</code></h2>\n' "$(esc "$tname")"
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<p class="live-note" id="git-live" hidden>Updated from GitHub.</p>'
printf '%s\n' '<div class="info">'
if [ "$tkind" = annotated ]; then
printf '%s\n' '<div class="dl-row"><span class="muted">Kind</span><span><span class="badge">annotated</span></span></div>'
else
printf '%s\n' '<div class="dl-row"><span class="muted">Kind</span><span><span class="badge">lightweight</span></span></div>'
fi
if [ -n "$tsha" ]; then
printf '<div class="dl-row"><span class="muted">Commit</span><span><a href="%scommit/%s/"><code>%s</code></a></span></div>\n' \
"$(esc "$reporoot")" "$(esc "$tsha")" "$(esc "${tshort:-$tsha}")"
fi
if [ -n "$tiso" ]; then
printf '<div class="dl-row"><span class="muted">Date</span><span><time datetime="%s">%s</time></span></div>\n' \
"$(esc "$tiso")" "$(esc "$tiso")"
fi
if [ -n "$trel" ]; then
printf '<div class="dl-row"><span class="muted">Release</span><span><a href="%sreleases/tag/%s/">%s</a></span></div>\n' \
"$(esc "$reporoot")" "$(esc "$tseg")" "$(esc "$tname")"
fi
printf '<div class="dl-row"><span class="muted">GitHub</span><span><a href="%s/releases/tag/%s">mirror</a></span></div>\n' \
"$(esc "$gh")" "$(esc "$tseg")"
printf '%s\n' '</div>'
if [ -n "$tsubj" ]; then
printf '<p>%s</p>\n' "$(esc "$tsubj")"
fi
source_archives "$name" "$tname"
} >>"$tdest"
page_end "$tdest"
done <"$tagstsv"
fi
GIT_KIND=releases
GIT_SHA=
GIT_PATH=
page_begin "$base/releases/index.html" "Releases - $name - Splux Git" \
"GitHub releases for $name"
{
git_nav "$name" releases
printf '%s\n' '<h2>Releases</h2>'
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<p class="live-note" id="git-live" hidden>Updated from GitHub.</p>'
printf '%s\n' '<p>GitHub releases for this tree. ISO files and other assets stay on GitHub. Notes are Markdown. <a href="latest/">Latest</a></p>'
printf '%s\n' '<p><label for="git-filter">Filter</label> <input id="git-filter" type="search" placeholder="release"></p>'
if [ -s "$reltsv" ]; then
awk -f "$AWK" -v mode=releases \
-v relpfx="${reporoot}releases/tag/" \
-v tagpfx="${reporoot}tags/" \
-v userpfx="$userpfx" \
"$reltsv"
else
printf '%s\n' '<p class="muted">No GitHub releases yet.</p>'
fi
} >>"$base/releases/index.html"
page_end "$base/releases/index.html"
write_release_page() {
_rdest=$1
_rhere=$2
_rline=$3
_rtag=$(tsv_cut "$_rline" 1)
_rtitle=$(tsv_cut "$_rline" 2)
_riso=$(tsv_cut "$_rline" 3)
_rlogin=$(tsv_cut "$_rline" 4)
_ravatar=$(tsv_cut "$_rline" 5)
_rpre=$(tsv_cut "$_rline" 6)
_rlatest=$(tsv_cut "$_rline" 7)
_rnassets=$(tsv_cut "$_rline" 8)
_rreact=$(tsv_cut "$_rline" 10)
_rseg=$(tsv_cut "$_rline" 11)
[ -n "$_rtag" ] || return 0
[ -n "$_rtitle" ] || _rtitle=$_rtag
[ -n "$_rseg" ] || _rseg=$(pct_seg "$_rtag")
GIT_KIND=$_rhere
GIT_PATH=$_rtag
GIT_SHA=
page_begin "$_rdest" "$_rtitle - $name - Splux Git" "Release $_rtag of $name"
{
git_nav "$name" "$_rhere"
printf '<h2>%s</h2>\n' "$(esc "$_rtitle")"
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<p class="live-note" id="git-live" hidden>Updated from GitHub.</p>'
printf '%s\n' '<p class="meta">'
if [ "$_rlatest" = yes ]; then
printf '%s' '<span class="badge latest">Latest</span> '
fi
if [ "$_rpre" = yes ]; then
printf '%s' '<span class="badge">Pre-release</span> '
fi
printf '</p>\n'
printf '%s\n' '<div class="info">'
printf '<div class="dl-row"><span class="muted">Tag</span><span><a href="%stags/%s/"><code>%s</code></a></span></div>\n' \
"$(esc "$reporoot")" "$(esc "$_rseg")" "$(esc "$_rtag")"
if [ -n "$_riso" ]; then
printf '<div class="dl-row"><span class="muted">Date</span><span><time datetime="%s">%s</time></span></div>\n' \
"$(esc "$_riso")" "$(esc "$_riso")"
fi
if [ -n "$_rlogin" ]; then
printf '<div class="dl-row"><span class="muted">Author</span><span>%s</span></div>\n' \
"$(who_cell "$userpfx" "$_rlogin" "$_rlogin" "$_ravatar" "")"
fi
if [ -n "$_rnassets" ] && [ "$_rnassets" != 0 ]; then
printf '<div class="dl-row"><span class="muted">Files</span><span>%s</span></div>\n' \
"$(esc "$_rnassets")"
fi
if [ -n "$_rreact" ] && [ "$_rreact" != 0 ]; then
printf '<div class="dl-row"><span class="muted">Reactions</span><span>%s</span></div>\n' \
"$(esc "$_rreact")"
fi
printf '<div class="dl-row"><span class="muted">GitHub</span><span><a href="%s/releases/tag/%s">mirror</a></span></div>\n' \
"$(esc "$gh")" "$(esc "$_rseg")"
printf '%s\n' '</div>'
_body=$base/.bodies/$_rseg
if [ -s "$_body" ]; then
printf '%s\n' '<h3>Notes</h3>'
awk -f "$RENDER" -v mode=markdown -v mapfile="$MAPFILE" "$_body"
fi
_atmp=$base/.assets-one.tsv
: >"$_atmp"
if [ -s "$assetstsv" ]; then
awk -F '\t' -v OFS='\t' -v t="$_rtag" \
'$1 == t { print $2, $3, $4, $5, $6 }' \
"$assetstsv" >"$_atmp"
fi
if [ -s "$_atmp" ]; then
printf '%s\n' '<h3>Assets</h3>'
awk -f "$AWK" -v mode=assets "$_atmp"
fi
rm -f "$_atmp"
source_archives "$name" "$_rtag"
} >>"$_rdest"
page_end "$_rdest"
}
if [ -s "$reltsv" ]; then
while IFS= read -r _rline || [ -n "${_rline-}" ]
do
[ -n "${_rline-}" ] || continue
_rseg=$(tsv_cut "$_rline" 11)
_rtag=$(tsv_cut "$_rline" 1)
[ -n "$_rtag" ] || continue
[ -n "$_rseg" ] || _rseg=$(pct_seg "$_rtag")
write_release_page "$base/releases/tag/$_rseg/index.html" release "$_rline"
done <"$reltsv"
fi
GIT_KIND=latest
GIT_PATH=$latest_tag
latest_line=
if [ -s "$reltsv" ]; then
latest_line=$(awk -F '\t' '$7 == "yes" { print; exit }' "$reltsv")
if [ -z "$latest_line" ]; then
latest_line=$(head -n 1 "$reltsv")
fi
fi
if [ -n "$latest_line" ]; then
write_release_page "$base/releases/latest/index.html" latest "$latest_line"
else
page_begin "$base/releases/latest/index.html" "Latest - $name - Splux Git" \
"Latest GitHub release of $name"
{
git_nav "$name" latest
printf '%s\n' '<h2>Latest</h2>'
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<p class="muted">No GitHub releases yet.</p>'
} >>"$base/releases/latest/index.html"
page_end "$base/releases/latest/index.html"
fi
day=${lastiso%%T*}
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$name" "$repo_desc" "$short" "$head" "$ncommits" "$nfiles" "$day" "$gh" \
>>"$index_rows"
rm -f "$langtmp" "$clog" "$base/.entries.tsv" "$base/.people.tsv" \
"$filelang" "$base/.lang-index.tsv" "$base/.tags.tsv" \
"$base/.releases.tsv" "$base/.assets.tsv"
rm -rf "$base/.bodies"
printf '%s\n' "git: $name ($ncommits commits, $nfiles files, $n_tags tags, $n_releases releases)" >&2
}
build_repo SPS "$SPS"
build_repo sps-core "$CORE"
build_repo sps-extra "$EXTRA"
build_repo sps-community "$COMMUNITY"
build_repo splux-site "$SITE"
# People across trees
users_raw=$OUT/data/.users-raw.tsv
awk -F '\t' -v OFS='\t' '
$1 == "" { next }
{
login=$1
av=$2
c[login] += $3 + 0
if (av != "" && !(login in avatar))
avatar[login]=av
}
END {
for (l in c)
print l, avatar[l], c[l]
}
' "$all_people" | LC_ALL=C sort -t "$(printf '\t')" -k3,3nr >"$users_raw"
users_full=$OUT/data/git-users.tsv
: >"$users_full"
if [ -s "$users_raw" ]; then
while IFS="$(printf '\t')" read -r login avatar n || [ -n "${login-}" ]
do
[ -n "${login-}" ] || continue
valid_login "$login" || continue
uf=$OUT/data/.user-$login.tsv
if fetch_github_user "$login" "$uf"; then
awk -F '\t' -v OFS='\t' -v n="$n" -v av="$avatar" '{
if ($4 == "" && av != "") $4 = av
print $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, n
}' "$uf" >>"$users_full"
else
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$login" "" "" "$avatar" "https://github.com/$login" \
"" "" "" "" "" "$n" >>"$users_full"
fi
rm -f "$uf"
done <"$users_raw"
fi
rm -f "$users_raw"
GIT_KIND=users
GIT_REPO=
GIT_USER=
GIT_SHA=
GIT_PATH=
page_begin "$OUT/git/users/index.html" "People - Splux Git" "GitHub accounts that pushed to the Splux trees."
{
printf '%s\n' '<p class="git-nav"><a href="../">git</a> · people</p>'
printf '%s\n' '<h2>People</h2>'
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<p>Accounts that authored commits in SPS, sps-core, sps-extra, sps-community, and this site. Avatars and names come from GitHub. Open someone to see their profile and their commits here.</p>'
printf '%s\n' '<p class="live-note" id="git-live" hidden>Updated from GitHub.</p>'
if [ -s "$users_full" ]; then
awk -F '\t' -v OFS='\t' '{ print $1, $4, $11 }' "$users_full" |
awk -f "$AWK" -v mode=people -v userpfx="../../../../../git/users/"
else
printf '%s\n' '<p class="muted">No GitHub accounts recorded for these commits.</p>'
fi
} >>"$OUT/git/users/index.html"
page_end "$OUT/git/users/index.html"
tab=$(printf '\t')
if [ -s "$users_full" ]; then
while IFS= read -r _line || [ -n "${_line-}" ]
do
[ -n "${_line-}" ] || continue
login=$(tsv_cut "$_line" 1)
name=$(tsv_cut "$_line" 2)
bio=$(tsv_cut "$_line" 3)
avatar=$(tsv_cut "$_line" 4)
htmlurl=$(tsv_cut "$_line" 5)
prepos=$(tsv_cut "$_line" 6)
followers=$(tsv_cut "$_line" 7)
company=$(tsv_cut "$_line" 8)
location=$(tsv_cut "$_line" 9)
blog=$(tsv_cut "$_line" 10)
n=$(tsv_cut "$_line" 11)
[ -n "$login" ] || continue
udest=$OUT/git/users/$login/index.html
GIT_KIND=user
GIT_USER=$login
page_begin "$udest" "$login - Splux Git" "GitHub account $login"
{
printf '%s\n' '<p class="git-nav"><a href="../../">git</a> · <a href="../">people</a></p>'
printf '%s\n' '<div class="profile" id="git-profile">'
if [ -n "$avatar" ]; then
printf '<img class="avatar profile-avatar" src="%s" width="80" height="80" alt="">\n' \
"$(esc "$avatar")"
fi
printf '%s\n' '<div class="profile-body">'
printf '<h2>%s</h2>\n' "$(esc "$login")"
if [ -n "$name" ] && [ "$name" != "$login" ]; then
printf '<p class="profile-name">%s</p>\n' "$(esc "$name")"
fi
if [ -n "$bio" ]; then
printf '<p>%s</p>\n' "$(esc "$bio")"
fi
printf '%s\n' '<div class="info">'
if [ -n "$htmlurl" ]; then
printf '<div class="dl-row"><span class="muted">GitHub</span><span><a href="%s">%s</a></span></div>\n' \
"$(esc "$htmlurl")" "$(esc "$login")"
fi
if [ -n "$company" ]; then
printf '<div class="dl-row"><span class="muted">Company</span><span>%s</span></div>\n' \
"$(esc "$company")"
fi
if [ -n "$location" ]; then
printf '<div class="dl-row"><span class="muted">Location</span><span>%s</span></div>\n' \
"$(esc "$location")"
fi
if [ -n "$blog" ]; then
printf '<div class="dl-row"><span class="muted">URL</span><span><a href="%s">%s</a></span></div>\n' \
"$(esc "$blog")" "$(esc "$blog")"
fi
if [ -n "$prepos" ]; then
printf '<div class="dl-row"><span class="muted">Public repos</span><span>%s</span></div>\n' \
"$(esc "$prepos")"
fi
if [ -n "$followers" ]; then
printf '<div class="dl-row"><span class="muted">Followers</span><span>%s</span></div>\n' \
"$(esc "$followers")"
fi
if [ -n "$n" ]; then
printf '<div class="dl-row"><span class="muted">Commits here</span><span>%s</span></div>\n' \
"$(esc "$n")"
fi
printf '%s\n' '</div>'
printf '%s\n' '</div></div>'
printf '%s\n' '<h3>Commits in Splux trees</h3>'
printf '%s\n' '<div id="git-user-log">'
awk -F '\t' -v OFS='\t' -v login="$login" '
$8 == login {
print $2, $3, $4, $5, $6, $7, $8, $9, $10, $1
}
' "$all_commits" |
awk -f "$AWK" -v mode=commits -v repopfx="../../../../../git/" \
-v userpfx="../../../../../git/users/"
printf '%s\n' '</div>'
} >>"$udest"
page_end "$udest"
done <"$users_full"
fi
GIT_KIND=index
GIT_REPO=
GIT_USER=
GIT_SHA=
GIT_PATH=
page_begin "$OUT/git/index.html" "Git - Splux Linux" "Always-updating browse of the Splux git trees."
{
printf '%s\n' '<h2>Git</h2>'
printf '%s\n' '<hr class="rule">'
printf '%s\n' '<p>This is a live HTML mirror of the trees. Languages match GitHub Linguist. Each commit shows the GitHub account that uploaded it, with their avatar. Open <a href="users/">people</a> for profiles. Pages rebuild with the handbook; the browser also asks GitHub for anything newer. There is no git server here. Clone the GitHub URLs. <a href="../../../../../docs/community/">sps-community</a> is listed here; it is not enabled by <code>setup</code>.</p>'
printf '%s\n' '<p class="git-nav"><a href="users/">people</a></p>'
printf '%s\n' '<p class="live-note" id="git-live" hidden>Updated from GitHub.</p>'
printf '%s\n' '<p><label for="git-filter">Filter</label> <input id="git-filter" type="search" placeholder="repository"></p>'
printf '%s\n' '<table class="pkgs" id="git-repos">'
printf '%s\n' '<thead><tr><th>Repository</th><th>Description</th><th>HEAD</th><th>Commits</th><th>Updated</th></tr></thead>'
printf '%s\n' '<tbody>'
if [ -s "$index_rows" ]; then
while IFS="$tab" read -r name desc short head ncommits nfiles day gh || [ -n "${name-}" ]
do
[ -n "${name-}" ] || continue
printf '<tr data-repo="%s" data-search="%s %s">' \
"$(esc "$name")" "$(esc "$name")" "$(esc "$desc")"
printf '<td><a class="name" href="%s/">%s</a></td>' "$(esc "$name")" "$(esc "$name")"
printf '<td>%s</td>' "$(esc "$desc")"
printf '<td><a href="%s/commit/%s/"><code>%s</code></a></td>' \
"$(esc "$name")" "$(esc "$head")" "$(esc "$short")"
printf '<td>%s</td>' "$(esc "$ncommits")"
printf '<td>%s</td>' "$(esc "$day")"
printf '</tr>\n'
done <"$index_rows"
fi
printf '%s\n' '</tbody></table>'
if [ -s "$users_full" ]; then
printf '%s\n' '<h3>People</h3>'
awk -F '\t' -v OFS='\t' '{ print $1, $4, $11 }' "$users_full" |
awk -f "$AWK" -v mode=people -v userpfx="../../../../../git/users/"
fi
printf '%s\n' '<h3>Clone</h3>'
printf '%s\n' '<pre class="block">git clone https://github.com/RobertFlexx/SPS.git'
printf '%s\n' 'git clone https://github.com/RobertFlexx/sps-core.git'
printf '%s\n' 'git clone https://github.com/RobertFlexx/sps-extra.git'
printf '%s\n' 'git clone https://github.com/RobertFlexx/sps-community.git'
printf '%s\n' 'git clone https://github.com/RobertFlexx/splux-site.git</pre>'
printf '%s\n' '<p class="note">Live ISO files stay on GitHub Releases. Recipe browse also lives under <a href="../../../../../packages/">packages</a>.</p>'
} >>"$OUT/git/index.html"
page_end "$OUT/git/index.html"
# JSON summaries
awk -F '\t' '
function jesc(s) {
gsub(/\\/, "\\\\", s)
gsub(/"/, "\\\"", s)
return s
}
BEGIN { print "[" }
{
if (NR > 1) print ","
printf "{\"name\":\"%s\",\"description\":\"%s\",\"head\":\"%s\",\"full\":\"%s\",\"commits\":%s,\"files\":%s,\"updated\":\"%s\",\"github\":\"%s\"}", \
jesc($1), jesc($2), jesc($3), jesc($4), $5 + 0, $6 + 0, jesc($7), jesc($8)
}
END { print "\n]" }
' "$index_rows" >"$OUT/data/git.json"
awk -F '\t' '
function jesc(s) {
gsub(/\\/, "\\\\", s)
gsub(/"/, "\\\"", s)
return s
}
BEGIN { print "[" }
{
if (NR > 1) print ","
printf "{\"login\":\"%s\",\"name\":\"%s\",\"bio\":\"%s\",\"avatar\":\"%s\",\"github\":\"%s\",\"public_repos\":%s,\"followers\":%s,\"company\":\"%s\",\"location\":\"%s\",\"blog\":\"%s\",\"commits\":%s}", \
jesc($1), jesc($2), jesc($3), jesc($4), jesc($5), $6 + 0, $7 + 0, jesc($8), jesc($9), jesc($10), $11 + 0
}
END { print "\n]" }
' "$users_full" >"$OUT/data/git-users.json"
rm -f "$OUT/data/.linguist.map" "$all_people"
# Keep git-commits.tsv and git-langs-*.tsv for the live script if needed;
# commits TSV can be large. Drop it from the Pages artifact.
rm -f "$all_commits"
printf '%s\n' "git: wrote $OUT/git" >&2