#!/bin/sh set -eu usage() { cat <<-'EOF' Usage: gistatic -i -o DIRECTORY REPOSITORY... gistatic -o DIRECTORY -u CLONE_URL [-b MAIN_BRANCH] REPOSITORY' EOF } LANGNAME="$(printf '%s' "${LANG:-en}" | cut -d_ -f1)" LANGNAME='en' MSG_MISSING_CLIARG='Missing %s' MSG_INCOMPATIBLE_OPTIONS='Incompatible options: ' MSG_MISSING_ARGS='REPOSITORY' MSG_INDEX_DESCRIPTION='Index of repositories' MSG_TITLE_INDEX='Repositories' MSG_LOGO_ALT_INDEX='Logo image of the repository list' MSG_LOGO_ALT_REPOSITORY='Logo image of the repository' MSG_NAME='Name' MSG_DESCRIPTION='Description' MSG_LAST_COMMIT='Last commit' MSG_COMMIT_FEED='commit feed' MSG_TAGS_FEED='tags feed' MSG_NAV_FILES='files' MSG_NAV_LOG='log' MSG_NAV_REFS='refs' MSG_THEAD_BRANCH='Branch' MSG_THEAD_COMMITMSG='Commit message' MSG_THEAD_AUTHOR='Author' MSG_THEAD_DATE='Date' MSG_THEAD_TAG='Tag' MSG_TITLE_REFS='refs' MSG_GENERATING_LOG='Generating %s...' # HTML escaped messages MSG_ESCAPED_FOOTER_TEMPLATE='Generated with gistatic.' t() { gettext 'gistatic' "$1" } # # Template strings # print_logo() { color="$1" cat < EOF } print_style() { cat <<'EOF' :root { --color: black; --background-color: white; --background-contrast-color: hsl(0, 0%, 98%); --hover-color: hsl(0, 0%, 93%); --nav-color: hsl(0, 0%, 87%); --selected-color: hsl(0, 0%, 80%); --diff-added-color: hsl(120, 100%, 23%); --diff-removed-color: hsl(0, 100%, 47%); } @media(prefers-color-scheme: dark) { :root { --color: white; --background-color: black; --background-contrast-color: hsl(0, 0%, 2%); --hover-color: hsl(0, 0%, 7%); --nav-color: hsl(0, 0%, 13%); --selected-color: hsl(0, 0%, 20%); } body { color: var(--color); background-color: var(--background-color); } a { color: hsl(211, 100%, 60%); } a:visited { color: hsl(242, 100%, 80%); } } body { font-family: monospace; max-width: 1100px; margin: 0 auto 0 auto; } .logo { height: 6em; width: 6em; } .header-horizontal-grouping { display: flex; align-items: center; margin-top: 1em; margin-bottom: 1em; } .header-description { margin-left: 2em; } nav { margin-top: 2em; } nav ul { display: flex; list-style-type: none; margin-bottom: 0; } nav li { margin-left: 10px; } nav a, nav a:visited { padding: 2px 8px 0px 8px; color: var(--color); } .selected-nav-item { background-color: var(--nav-color); } hr { margin-top: 0; border: 0; border-top: 3px solid var(--nav-color); } table { margin: 2em auto; } th { padding-bottom: 1em; } tbody tr:hover { background-color: var(--hover-color); } td { padding-left: 1em; padding-right: 1em; } /* commit page */ .diff-added, .diff-removed { text-decoration: none; } .diff-added:target, .diff-removed:target { background-color: var(--selected-color); } .diff-added, .diff-added:visited { color: var(--diff-added-color); } .diff-removed, .diff-removed:visited { color: var(--diff-removed-color); } /* log page */ .log-commit-box { padding: 1em; margin: 1em; background-color: var(--background-contrast-color); } .log-commit-tag { padding: 2px; border: 1px solid; color: var(--color); } .log-head-highlight { background-color: #ff8888; /* FIXME: hsl + dark-mode */ } .log-branch-highlight { background-color: #88ff88; /* FIXME: hsl + dark-mode */ } .log-tag-highlight { background-color: #ffff88; /* FIXME: hsl + dark-mode */ } .pre-wrapping { overflow: auto; margin: 1em; } .log-notes { /* FIXME: yellow box goes until the end of the screen */ padding: 1em; background-color: #ffd; /* FIXME: hsl + dark-mode */ } .log-pagination { text-align: center; margin: 2em; } footer { text-align: center; } EOF } print_index_header() { cat < $(t "$MSG_TITLE_INDEX")
$(t

$(t "$MSG_TITLE_INDEX")


EOF } print_index_row() { path="$1" repo_vars "$path" last_commit_date="$(git log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' 2>/dev/null ||:)" cat < EOF } print_index_footer() { cat <
$(t "$MSG_NAME") $(t "$MSG_DESCRIPTION") $(t "$MSG_LAST_COMMIT")
$(printf '%s' "$FULLNAME" | htmlesc) $(printf '%s' "$DESCRIPTION" | htmlesc) $(printf '%s' "$last_commit_date" | htmlesc)

$(t "$MSG_ESCAPED_FOOTER_TEMPLATE")

EOF } imgs() { DIR="$1" mkdir -p "$DIR"/img/logo print_logo black > "$DIR"/img/favicon.svg print_logo black > "$DIR"/img/logo/light.svg print_logo white > "$DIR"/img/logo/dark.svg print_style > "$DIR"/style.css } index_write() { mkdir -p "$OUTDIR" { print_index_header for r in "$@"; do print_index_row "$r" done print_index_footer } > "$OUTDIR/index.html" imgs "$OUTDIR" } genlog() { if [ "$VERBOSE" = true ]; then : # printf "$MSXG_GENERATING_LOG\n" "$1" >&2 # FIXME fi } # FIXME: get from config? # exts() { } # FIXME: emit signatures code repo_tarballs_mk() { git branch --format '%(refname:lstrip=2)' | while read -r branch; do cat < \$@ EOF done } repo_refs_mk() { cat < \$@ EOF } # FIXME: translate commits.xml and tags.xml print_clone_url() { if [ -z "$CLONE_URL" ]; then return fi cat < git clone $(printf '%s/%s' "$CLONE_URL" "$FULLNAME"/ | htmlesc) EOF } exec_refs() { repo_vars "$1" cat < $(printf '%s' "$NAME" | htmlesc) - $(t "$MSG_TITLE_REFS")
$(t

$(printf '%s' "$NAME" | htmlesc)

$DESCRIPTION

$(print_clone_url)

EOF for branch in $(git branch --format '%(refname:lstrip=2)'); do commit="$(git rev-parse "$branch")" cat < EOF done cat <
$(t "$MSG_THEAD_BRANCH") $(t "$MSG_THEAD_COMMITMSG") $(t "$MSG_THEAD_AUTHOR") $(t "$MSG_THEAD_DATE")
$(printf '%s' "$branch" | htmlesc) $(printf '%s\n' "$(git log -1 --format=%s "$commit")" | htmlesc) $(printf '%s\n' "$(git log -1 --format=%an "$commit")" | htmlesc) $(printf '%s\n' "$(git log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' "$commit")" | htmlesc)
EOF PRINTED_TABLE_OPEN=false for tag in $(git tag | tac); do commit="$(git rev-parse "$tag")" if [ "$PRINTED_TABLE_OPEN" = false ]; then PRINTED_TABLE_OPEN=true cat < $(t "$MSG_THEAD_TAG") $(t "$MSG_THEAD_COMMITMSG") $(t "$MSG_THEAD_AUTHOR") $(t "$MSG_THEAD_DATE") EOF fi if false && [ -e "$CACHE_DIR/tarball/$repo-$tag.$EXT.asc" ]; then tarball_link="(tarball, sig)" else tarball_link="(tarball)" fi # FIXME: translate tag/ cat < $(printf '%s' "$tag" | htmlesc) $tarball_link $(printf '%s\n' "$(git log -1 --format=%s "$commit")" | htmlesc) $(printf '%s\n' "$(git log -1 --format=%an "$tag")" | htmlesc) $(printf '%s\n' "$(git log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' "$tag")" | htmlesc) EOF done if [ "$PRINTED_TABLE_OPEN" = true ]; then cat < EOF fi cat <

$(t "$MSG_ESCAPED_FOOTER_TEMPLATE")

EOF } exec_commit() { repo_vars "$1" commit="$2" previous_commit="$(git rev-parse "$commit^" 2>/dev/null ||:)" subject="$(git log -1 --format='%s')" cat < $(printf '%s@%s - %s' "$NAME" "$commit" "$subject" | htmlesc)
$(t

$(printf '%s' "$NAME" | htmlesc)

$(printf '%s' "$DESCRIPTION" | htmlesc)

$(print_clone_url)

EOF print_formatted_diff "$commit" "$previous_commit" cat <

$(t "$MSG_ESCAPED_FOOTER_TEMPLATE")

EOF } print_formatted_diff() { commit="$1" previous_commit="$2" printf '
'
	# git show -p --stat "$sha"
	# FIXME: translate tree/
	git show -p --full-index "$commit" | htmlesc | awk \
			-v COMMIT="$commit" -v PREVIOUS_COMMIT="$previous_commit" '
		BEGIN {
			diff_init = 0
			diffl = 0
		}

		/^(diff|index) / {
			diff_init = 1
			print $0
			next
		}

		diff_init == 1 && /^--- a\// {
			fname = substr($0, 7)
			printf "--- a/%s\n",
				PREVIOUS_COMMIT, fname, fname
			next
		}

		diff_init == 1 && /^\+\+\+ b\// {
			fname = substr($0, 7)
			printf "--- b/%s\n",
				COMMIT, fname, fname
			next
		}

		{ diff_init = 0 }

		/^-/ {
			diffl++
			printf "%s\n",
				diffl, diffl, $0
			next
		}

		/^\+/ {
			diffl++
			printf "%s\n",
			diffl, diffl, $0
			next
		}

		{ print $0 }
	'
	printf '
\n' } preamble_mk() { cat </dev/null ||:)" } repo_write() { path="$1" repo_vars "$path" mkdir -p \ "$OUTDIR"/"$FULLNAME"/commit \ "$OUTDIR"/"$FULLNAME"/tarball \ { preamble_mk repo_tarballs_mk repo_commits_mk repo_refs_mk } | tee f | (unset GIT_DIR; make -f- -j`nproc`) imgs "$OUTDIR"/"$FULLNAME" } VERBOSE=true INDEX=false OUTDIR=. CLONE_URL= MAX_JOBS=1 MAIN_BRANCH='main' MAIN_BRANCH_SET=false REPO= DESCRIPTION= CMD='ln' EXEC= while getopts 'b:o:u:j:isqx:' flag; do case "$flag" in (b) MAIN_BRANCH="$OPTARG" MAIN_BRANCH_SET=true ;; (o) OUTDIR="$OPTARG" ;; (u) CLONE_URL="$OPTARG" ;; (j) MAX_JOBS="$OPTARG" ;; (i) INDEX=true ;; (q) VERBOSE=false ;; (s) CMD='ln -s' ;; (x) EXEC="$OPTARG" ;; (*) usage >&2 exit 2 ;; esac done shift $((OPTIND - 1)) if [ -n "$EXEC" ]; then "exec_$EXEC" "$@" exit fi if false; then if [ "$INDEX" = false ]; then assert_arg "$CLONE_URL" '-u CLONE_URL' elif [ -n "$CLONE_URL" ] || [ "$MAIN_BRANCH_SET" = true ]; then { printf '%s' "$MSG_INCOMPATIBLE_OPTIONS" # FIXME printf -- '-i' if [ -n "$CLONE_URL" ]; then printf -- ' -u' fi if [ "$MAIN_BRANCH_SET" = true ]; then printf -- ' -b' fi printf '\n\n' usage } >&2 exit 2 fi fi eval "$(assert-arg "${1:-}" "$MSG_MISSING_ARGS")" # FIXME if false; then if [ "$INDEX" = true ]; then index_write "$@" else repo_write "$1" fi fi index_write "$@" for path in "$@"; do repo_write "$path" done