#!/bin/sh # shellcheck disable=2034 disable=2059 # FIXME: remove these ^^^ set -eu MSG_USAGE_EN='Usage: gistatic -i -o DIRECTORY REPOSITORY... gistatic -o DIRECTORY -u CLONE_URL [-b MAIN_BRANCH] REPOSITORY gistatic [-hV]' MSG_HELP_EN=' Options: -i build the index page of the repositories -u CLONE_URL address to be shown alongside "git clone" -o DIRECTORY output where to place the generated files -b MAIN_BRANCH the default branch of the repository (default: main) -h, --help show this help -V, --version print the version information See "man gistatic" for more information.' MSG_MISSING_CLIARG_EN='Missing %s' MSG_INCOMPATIBLE_OPTIONS_EN='Incompatible options: ' MSG_MISSING_ARGS_EN='[PATH | [PATHS]]' MSG_LANGNAME_EN='en' MSG_INDEX_DESCRIPTION_EN='Index of repositories' MSG_DEFAULT_TITLE_EN='Repositories' MSG_LOGO_ALT_INDEX_EN='Logo image of the repository list' MSG_LOGO_ALT_REPOSITORY_EN='Logo image of the repository' MSG_NAME_EN='Name' MSG_DESCRIPTION_EN='Description' MSG_LAST_COMMIT_EN='Last commit' MSG_COMMIT_FEED_EN='commit feed' MSG_TAGS_FEED_EN='tags feed' MSG_NAV_FILES_EN='files' MSG_NAV_LOG_EN='log' MSG_NAV_REFS_EN='refs' MSG_THEAD_BRANCH_EN='Branch' MSG_THEAD_COMMITMSG_EN='Commit message' MSG_THEAD_AUTHOR_EN='Author' MSG_THEAD_DATE_EN='Date' MSG_THEAD_TAG_EN='Tag' MSG_TITLE_REFS_EN='refs' MSG_GENERATING_LOG_EN='Generating %s...' # HTML escaped messages MSG_ESCAPED_FOOTER_TEMPLATE_EN='Generated with gistatic.' set_lang() { lang="$1" eval " MSG_USAGE=\$MSG_USAGE_$lang MSG_HELP=\$MSG_HELP_$lang MSG_MISSING_CLIARG=\$MSG_MISSING_CLIARG_$lang MSG_INCOMPATIBLE_OPTIONS=\$MSG_INCOMPATIBLE_OPTIONS_$lang MSG_MISSING_ARGS=\$MSG_MISSING_ARGS_$lang MSG_LANGNAME=\$MSG_LANGNAME_$lang MSG_INDEX_DESCRIPTION=\$MSG_INDEX_DESCRIPTION_$lang MSG_DEFAULT_TITLE=\$MSG_DEFAULT_TITLE_$lang MSG_LOGO_ALT_INDEX=\$MSG_LOGO_ALT_INDEX_$lang MSG_LOGO_ALT_REPOSITORY=\$MSG_LOGO_ALT_REPOSITORY_$lang MSG_NAME=\$MSG_NAME_$lang MSG_DESCRIPTION=\$MSG_DESCRIPTION_$lang MSG_LAST_COMMIT=\$MSG_LAST_COMMIT_$lang MSG_COMMIT_FEED=\$MSG_COMMIT_FEED_$lang MSG_TAGS_FEED=\$MSG_TAGS_FEED_$lang MSG_NAV_FILES=\$MSG_NAV_FILES_$lang MSG_NAV_LOG=\$MSG_NAV_LOG_$lang MSG_NAV_REFS=\$MSG_NAV_REFS_$lang MSG_THEAD_BRANCH=\$MSG_THEAD_BRANCH_$lang MSG_THEAD_COMMITMSG=\$MSG_THEAD_COMMITMSG_$lang MSG_THEAD_AUTHOR=\$MSG_THEAD_AUTHOR_$lang MSG_THEAD_DATE=\$MSG_THEAD_DATE_$lang MSG_THEAD_TAG=\$MSG_THEAD_TAG_$lang MSG_TITLE_REFS=\$MSG_TITLE_REFS_$lang MSG_GENERATING_LOG=\$MSG_GENERATING_LOG_$lang MSG_ESCAPED_FOOTER_TEMPLATE=\$MSG_ESCAPED_FOOTER_TEMPLATE_$lang " } get_lang() { # LC_MESSAGES="ll_CC.CODESET@modifier" -> ll_CC, where quotes # are optional locale 2>/dev/null | grep ^LC_MESSAGES | cut -d. -f1 | cut -d\" -f2 | cut -d= -f2 } case "$(get_lang)" in *) set_lang EN ;; esac # # Utilities # tac() { sed '1!G;h;$!d' } escape() { echo "$1" | sed -e 's|&|\&|g;s|<|\<|g;s|>|\>|g;s|"|\"|g' \ -e "s|'|\'|g" } realpath() { mkdir -p "$1" cd "$1" pwd } # # Documentation functions # usage() { printf '%s\n' "$MSG_USAGE" } help() { printf '%s\n' "$MSG_HELP" } version() { printf 'gistatic-0.1.0 1970-01-01\n' } # # Template strings # print_logo() { cat < EOF } print_style() { cat < $(escape "$TITLE")"
$(escape

$(escape "$TITLE")


EOF } print_index_row() { repo="$(basename "$(realpath "${1%.git}")")" description="$(cat "$1/description" 2>/dev/null ||:)" last_commit_date="$(git -C "$1" log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' 2>/dev/null ||:)" cat < EOF } print_index_footer() { cat <
$(escape "$MSG_NAME") $(escape "$MSG_DESCRIPTION") $(escape "$MSG_LAST_COMMIT")
$(escape "$repo") $(escape "$description") $(escape "$last_commit_date")

$MSG_ESCAPED_FOOTER_TEMPLATE

EOF } index_write() { rm -f "$OUTDIR/index.html" print_index_header >> "$OUTDIR/index.html" for r in "$@"; do print_index_row "$r" >> "$OUTDIR/index.html" done print_index_footer >> "$OUTDIR/index.html" print_logo > "$OUTDIR/logo.svg" print_style > "$OUTDIR/style.css" } genlog() { if [ "$VERBOSE" = true ]; then printf "$MSG_GENERATING_LOG\n" "$1" >&2 fi } parallel_n=0 cached_run() { TARGET_PATH="$1" shift # drop $TARGET_PATH shift # drop -- if [ -e "$OUTDIR/$TARGET_PATH" ]; then return fi if [ -e "$CACHE_DIR/$TARGET_PATH" ]; then cp "$CACHE_DIR/$TARGET_PATH" "$OUTDIR/$TARGET_PATH" else { genlog "$OUTDIR/$TARGET_PATH" "$@" cp "$CACHE_DIR/$TARGET_PATH" "$OUTDIR/$TARGET_PATH" } & parallel_n=$((parallel_n + 1)) if [ "$parallel_n" = "$MAX_JOBS" ]; then wait parallel_n=0 fi fi } repo_tarballs_write() { repo="$1" mkdir -p "$OUTDIR/tarballs" "$CACHE_DIR/tarballs" local_parallel_n=0 for branch in $(git branch --format '%(refname:lstrip=2)'); do TARBALL_PATH="$OUTDIR/tarballs/$repo-$branch.tar.gz" genlog "$TARBALL_PATH" git archive --prefix "$repo-$branch/" "$branch" \ -o "$TARBALL_PATH" & local_parallel_n=$((local_parallel_n + 1)) if [ "$local_parallel_n" = "$MAX_JOBS" ]; then wait local_parallel_n=0 fi done wait for tag in $(git tag); do TARBALL_PATH="tarballs/$repo-$tag.tar.gz" cached_run "$TARBALL_PATH" -- \ git archive --prefix "$repo-$tag/" "$tag" \ -o "$CACHE_DIR/$TARBALL_PATH" SIGNATURE_PATH="tarballs/$repo-$tag.tar.gz.asc" if git notes --ref=refs/notes/signatures/tar.gz show "$tag" \ 1>/dev/null 2>&1; then git notes --ref=refs/notes/signatures/tar.gz \ show "$tag" > "$OUTDIR/$SIGNATURE_PATH" fi done wait } print_repo_refs() { repo="$1" description="$2" cat < $(escape "$repo") - $(escape "$MSG_TITLE_REFS")

$(escape "$repo")

$(escape "$description")

git clone $(escape "$CLONE_URL")

EOF for branch in $(git branch --format '%(refname:lstrip=2)'); do sha="$(git rev-parse "$branch")" cat < EOF done cat <
$(escape "$MSG_THEAD_BRANCH") $(escape "$MSG_THEAD_COMMITMSG") $(escape "$MSG_THEAD_AUTHOR") $(escape "$MSG_THEAD_DATE")
$(escape "$branch") $(escape "$(git log -1 --format=%s "$sha")") $(escape "$(git log -1 --format=%an "$sha")") $(escape "$(git log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' "$sha")")
EOF PRINTED_TABLE_OPEN=false for tag in $(git tag | tac); do if [ "$PRINTED_TABLE_OPEN" = false ]; then PRINTED_TABLE_OPEN=true cat < $(escape "$MSG_THEAD_TAG") $(escape "$MSG_THEAD_AUTHOR") $(escape "$MSG_THEAD_DATE") EOF fi if [ -e "$CACHE_DIR/tarballs/$repo-$tag.tar.gz.asc" ]; then tarball_link="(tarball, sig)" else tarball_link="(tarball)" fi cat < $(escape "$tag") $tarball_link $(escape "$(git log -1 --format=%an "$tag")") $(escape "$(git log -1 --format=%cd --date='format:%Y-%m:%d %H:%M' "$tag")") EOF done if [ "$PRINTED_TABLE_OPEN" = true ]; then cat < EOF fi cat <

$MSG_ESCAPED_FOOTER_TEMPLATE

EOF } print_formatted_diff() { sha="$1" previous_sha="$2" printf '
'
	# git show -p --stat "$sha"
	escape "$(git show -p "$sha")" | awk \
			-v SHA="$sha" -v PREVIOUS_SHA="$previous_sha" '
		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_SHA, fname, fname
			next
		}

		diff_init == 1 && /^\+\+\+ b\// {
			fname = substr($0, 7)
			printf "--- b/%s\n",
				SHA, 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' } print_repo_commit_page() { repo="$1" description="$2" sha="$3" previous_sha="$(git rev-parse "$sha^" 2>/dev/null ||:)" summary="$(git log -1 --format=%B "$sha")" cat < $(escape "$repo")@$(escape "$sha") - $(escape "$summary")

$(escape "$repo")

$(escape "$description")

git clone $(escape "$CLONE_URL")

$(print_formatted_diff "$sha" "$previous_sha")

$MSG_ESCAPED_FOOTER_TEMPLATE

EOF } repo_commits_write() { repo="$1" description="$2" mkdir -p "$OUTDIR/commit" "$CACHE_DIR/commit" for branch in $(git branch --format '%(refname:lstrip=2)'); do for commit in $(git rev-list "$branch"); do COMMIT_PATH="commit/$commit.html" if [ -e "$OUTDIR/$COMMIT_PATH" ]; then # Assume all previous history exists, too break fi cached_run "$COMMIT_PATH" -- \ eval "print_repo_commit_page '$repo' '$description' '$commit' \ > '$CACHE_DIR/$COMMIT_PATH'" done done wait } NEWLINE="$(printf '\n')" print_repo_log_page() { repo="$1" description="$2" newer_page_name="$3" older_page_name="$4" shift shift shift shift title='FIXME' if [ -z "$newer_page_name" ]; then escaped_newer_page_link='' else escaped_newer_page_link=" newer FIXME i18n"' ' fi if [ -z "$older_page_name" ]; then escaped_older_page_link='' else escaped_older_page_link=" older FIXME i18n"' ' fi if [ -n "$newer_page_name" ] || [ -n "$older_page_name" ]; then escaped_pagination_open='
' escaped_pagination_close='
' else escaped_pagination_open='' escaped_pagination_close='' fi if [ -n "$newer_page_name" ] && [ -n "$older_page_name" ]; then escaped_pagination_separator=' | ' else escaped_pagination_separator='' fi cat < $(escape "$repo") - $(escape "$title")

$(escape "$repo")

$(escape "$description")

git clone $(escape "$CLONE_URL")

EOF for commit in "$@"; do body_txt="$(git log -1 --format=%b "$commit")" if [ "$body_txt" = "$NEWLINE" ]; then body='' else body=" $body_txt" fi notes_txt="$(git log -1 --format=%N "$commit")" if [ "$notes_txt" = "$NEWLINE" ]; then escaped_notes='' else escaped_notes="
$(escape "$notes_txt")
" fi # FIXME: show if commit is in branch, tag or HEAD # HEAD # main # v0.2.1 cat <

$(escape "$commit") | $(escape "$(git log -1 --format=%an "$commit")")

$(escape "$(git log -1 --format=%s "$commit")")$(escape "$body")
$escaped_notes EOF done cat <

$MSG_ESCAPED_FOOTER_TEMPLATE

EOF } # FIXME: use log/main/1.html over diffs? LOG_PAGE_MAX=50 repo_log_write() { repo="$1" description="$2" mkdir -p "$OUTDIR/log" "$CACHE_DIR/log" for ref in $(git branch --format '%(refname:lstrip=2)') $(git tag); do # FIXME: add bail out of loop when the first page already # exists, so nothing after that needs to be ran mkdir -p "$CACHE_DIR/log_tmp/$ref" PAGE_SIZE=0 PAGE_INDEX=0 for commit in $(git rev-list "$ref" | tac); do if [ "$PAGE_SIZE" = 0 ]; then FIRST="$commit" fi LAST="$commit" PAGE_NAME="$FIRST..$LAST.html" PAGE_SIZE=$((PAGE_SIZE + 1)) if [ "$PAGE_SIZE" != "$LOG_PAGE_MAX" ]; then continue fi echo "$PAGE_NAME" > "$CACHE_DIR/log_tmp/$ref/$PAGE_INDEX" PAGE_INDEX=$((PAGE_INDEX + 1)) PAGE_SIZE=0 done if [ "$PAGE_SIZE" != 0 ]; then echo "$PAGE_NAME" > "$CACHE_DIR/log_tmp/$ref/$PAGE_INDEX" fi PAGE_COMMITS='' PAGE_SIZE=0 PAGE_INDEX=0 for commit in $(git rev-list "$ref" | tac); do PAGE_COMMITS="$commit $PAGE_COMMITS" PAGE_SIZE=$((PAGE_SIZE + 1)) if [ "$PAGE_SIZE" != "$LOG_PAGE_MAX" ]; then continue fi # FIXME: 80 columns NEWER_PAGE_NAME="$(cat "$CACHE_DIR/log_tmp/$ref/$((PAGE_INDEX + 1))" 2>/dev/null ||:)" CURRT_PAGE_NAME="$(cat "$CACHE_DIR/log_tmp/$ref/$((PAGE_INDEX + 0))" 2>/dev/null ||:)" OLDER_PAGE_NAME="$(cat "$CACHE_DIR/log_tmp/$ref/$((PAGE_INDEX - 1))" 2>/dev/null ||:)" PAGE_PATH="log/$CURRT_PAGE_NAME" cached_run "$PAGE_PATH" -- eval "print_repo_log_page \ '$repo' '$description' \ '$NEWER_PAGE_NAME' '$OLDER_PAGE_NAME' \ $PAGE_COMMITS \ > '$CACHE_DIR/$PAGE_PATH'" PAGE_COMMITS='' PAGE_SIZE=0 PAGE_INDEX=$((PAGE_INDEX + 1)) done if [ "$PAGE_SIZE" != 0 ]; then # FIXME: 80 columns NEWER_PAGE_NAME="$(cat "$CACHE_DIR/log_tmp/$ref/$((PAGE_INDEX + 1))" 2>/dev/null ||:)" CURRT_PAGE_NAME="$(cat "$CACHE_DIR/log_tmp/$ref/$((PAGE_INDEX + 0))" 2>/dev/null ||:)" OLDER_PAGE_NAME="$(cat "$CACHE_DIR/log_tmp/$ref/$((PAGE_INDEX - 1))" 2>/dev/null ||:)" PAGE_PATH="log/$CURRT_PAGE_NAME" cached_run "$PAGE_PATH" -- eval "print_repo_log_page \ '$repo' '$description' \ '$NEWER_PAGE_NAME' '$OLDER_PAGE_NAME' \ $PAGE_COMMITS \ > '$CACHE_DIR/$PAGE_PATH'" fi ln -fs "$CURRT_PAGE_NAME" "$OUTDIR/log/$ref.html" done rm -rf "$CACHE_DIR/log_tmp" wait } print_repo_index_page() { repo="$1" description="$2" cat < $(escape "$repo") - FIXME

$(escape "$repo")

$(escape "$description")

git clone $(escape "$CLONE_URL")

Os arquivos vão aqui.
O README vai aqui.
EOF } repo_trees_write() { echo } repo_write() { cd "$1" # FIXME: use $REPO and $DESCRIPTION repo="$(basename "$(realpath "${1%.git}")")" description="$(cat "$1/description" 2>/dev/null ||:)" CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/gistatic/$repo" repo_tarballs_write "$repo" print_repo_refs "$repo" "$description" > "$OUTDIR/refs.html" repo_commits_write "$repo" "$description" repo_log_write "$repo" "$description" print_repo_index_page "$repo" "$description" > "$OUTDIR/index.html" repo_trees_write "$repo" "$description" print_logo > "$OUTDIR/logo.svg" print_style > "$OUTDIR/style.css" cd - > /dev/null } for flag in "$@"; do case "$flag" in --) break ;; --help) usage help exit ;; --version) version exit ;; *) ;; esac done VERBOSE=true INDEX=false TITLE="$MSG_DEFAULT_TITLE" OUTDIR= CLONE_URL= CACHE_DIR= MAX_JOBS=1 MAIN_BRANCH='main' MAIN_BRANCH_SET=false REPO= DESCRIPTION= while getopts 'b:o:t:u:j:iqhV' flag; do case "$flag" in b) MAIN_BRANCH="$OPTARG" MAIN_BRANCH_SET=true ;; o) OUTDIR="$(realpath "$OPTARG")" ;; t) TITLE="$OPTARG" ;; u) CLONE_URL="$OPTARG" ;; j) MAX_JOBS="$OPTARG" ;; i) INDEX=true ;; q) VERBOSE=false ;; h) usage help exit ;; V) version exit ;; *) usage >&2 exit 2 ;; esac done shift $((OPTIND - 1)) assert_arg() { if [ -z "$1" ]; then printf "$MSG_MISSING_CLIARG\n\n" "$2" >&2 usage >&2 exit 2 fi } assert_arg "$OUTDIR" '-o DIRECTORY' 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" 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 assert_arg "${1:-}" "$MSG_MISSING_ARGS" mkdir -p "$OUTDIR" if [ "$INDEX" = true ]; then index_write "$@" else repo_write "$1" fi