diff options
author | EuAndreh <eu@euandre.org> | 2022-01-18 11:40:47 -0300 |
---|---|---|
committer | EuAndreh <eu@euandre.org> | 2022-01-18 14:02:59 -0300 |
commit | 47bfd2ed8c3219e79f8974a8fc2ac9265ed91bd2 (patch) | |
tree | 3d162596d874b4f775da12c843ad3918b593f713 /aux | |
parent | Initial empty commit (diff) | |
download | td-47bfd2ed8c3219e79f8974a8fc2ac9265ed91bd2.tar.gz td-47bfd2ed8c3219e79f8974a8fc2ac9265ed91bd2.tar.xz |
First commit, now with a clean history
Diffstat (limited to 'aux')
-rwxr-xr-x | aux/80-columns.sh | 12 | ||||
-rwxr-xr-x | aux/assert-shellcheck.sh | 6 | ||||
-rwxr-xr-x | aux/ci/ci-build.sh | 60 | ||||
-rwxr-xr-x | aux/ci/git-post-receive.sh | 22 | ||||
-rwxr-xr-x | aux/ci/git-pre-push.sh | 22 | ||||
-rwxr-xr-x | aux/ci/report.sh | 109 | ||||
-rw-r--r-- | aux/containers/guix/manifest.scm | 23 | ||||
-rwxr-xr-x | aux/containers/guix/with-container.sh | 19 | ||||
-rwxr-xr-x | aux/lib.sh | 30 | ||||
-rw-r--r-- | aux/tld.txt | 1 | ||||
l--------- | aux/with-container | 1 | ||||
-rwxr-xr-x | aux/workflow/TODOs.sh | 64 | ||||
-rwxr-xr-x | aux/workflow/assert-changelog.sh | 66 | ||||
-rwxr-xr-x | aux/workflow/assert-readme.sh | 108 | ||||
-rwxr-xr-x | aux/workflow/assert-todos.sh | 58 | ||||
-rwxr-xr-x | aux/workflow/commonmark.sh | 48 | ||||
-rwxr-xr-x | aux/workflow/dist.sh | 112 | ||||
-rw-r--r-- | aux/workflow/favicon.html | 1 | ||||
-rw-r--r-- | aux/workflow/favicon.svg | 62 | ||||
-rwxr-xr-x | aux/workflow/l10n.sh | 109 | ||||
-rw-r--r-- | aux/workflow/preamble.md | 16 | ||||
-rwxr-xr-x | aux/workflow/public.sh | 83 | ||||
-rwxr-xr-x | aux/workflow/sign-tarballs.sh | 38 | ||||
-rw-r--r-- | aux/workflow/style.css | 62 |
24 files changed, 1132 insertions, 0 deletions
diff --git a/aux/80-columns.sh b/aux/80-columns.sh new file mode 100755 index 0000000..ae4660e --- /dev/null +++ b/aux/80-columns.sh @@ -0,0 +1,12 @@ +#!/bin/sh +set -eu + +# shellcheck disable=2068 +for f in ${@:-$(cat -)}; do + if [ "$(file -i "$f" | cut -d' ' -f2 | cut -d/ -f1)" = 'text' ]; then + sed 's/\t/ /g' "$f" | + awk -v FNAME="$f" 'length > 80 { + printf "%s:%s:%s\n", FNAME, NR, $0 + }' + fi +done diff --git a/aux/assert-shellcheck.sh b/aux/assert-shellcheck.sh new file mode 100755 index 0000000..40fd364 --- /dev/null +++ b/aux/assert-shellcheck.sh @@ -0,0 +1,6 @@ +#!/bin/sh +set -eu + +find . -name '*.sh' -print0 | + xargs -0 awk 'FNR==1 && /^#!\/bin\/sh$/ { print FILENAME }' | + xargs shellcheck diff --git a/aux/ci/ci-build.sh b/aux/ci/ci-build.sh new file mode 100755 index 0000000..34233b7 --- /dev/null +++ b/aux/ci/ci-build.sh @@ -0,0 +1,60 @@ +#!/bin/sh +set -eux + +PROJECT="$1" +LOGS_DIR="$2" +SHA="$3" +FILENAME="$(date -Is)-$SHA.log" +LOGFILE="$LOGS_DIR/$FILENAME" + +mkdtemp() { + name="$(echo 'mkstemp(template)' | + m4 -D template="${TMPDIR:-/tmp}/m4-tmpname.")" + rm -f "$name" + mkdir "$name" + echo "$name" +} + +{ + echo "Starting CI job at: $(date -Is)" + + finish() { + STATUS="$?" + printf "\n\n>>> exit status was %s\n" "$STATUS" + echo "Finishing CI job at: $(date -Is)" + cd - + NOTE=$(cat <<EOF +See CI logs with: + git notes --ref=refs/notes/ci-logs show $SHA + git notes --ref=refs/notes/ci-data show $SHA +EOF +) + git notes --ref=refs/notes/ci-data add -f -m "$STATUS $FILENAME" + git notes --ref=refs/notes/ci-logs add -f -F "$LOGFILE" + git notes append -m "$NOTE" + + cd - + git fetch origin refs/notes/*:refs/notes/* + sh aux/ci/report.sh -n "$PROJECT" -o public + rsync -av public/ "/srv/http/$PROJECT/" --delete + + printf '\n>>>\n>>> CI logs added as Git note.\n>>>\n>>> Run status was %s\n>>>\n\n' \ + "$STATUS" + } + trap finish EXIT + + unset GIT_DIR + REMOTE="$PWD" + cd "$(mkdtemp)" + git clone "$REMOTE" . + git config --global user.email git@euandre.org + git config --global user.name 'EuAndreh CI' + + if [ -e aux/with-container ]; then + RUNNER='sh aux/with-container' + else + RUNNER='sh -c' + fi + + $RUNNER 'make clean public dev-check' +} 2>&1 | tee "$LOGFILE" diff --git a/aux/ci/git-post-receive.sh b/aux/ci/git-post-receive.sh new file mode 100755 index 0000000..92bba73 --- /dev/null +++ b/aux/ci/git-post-receive.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -eu + +for n in $(seq 0 $((GIT_PUSH_OPTION_COUNT - 1))); do + opt="$(eval "echo \$GIT_PUSH_OPTION_$n")" + if [ "$opt" = skip-ci ] || [ "$opt" = ci-skip ]; then + printf "\n'%s' option detected, not running ci-build.sh\n\n" \ + "$opt" + exit 0 + fi +done + +# shellcheck disable=2034 +read -r _oldrev SHA _refname + +PROJECT="$(basename "$PWD" | cut -d. -f1)" # remove .git suffix +LOGS_DIR="/opt/ci/$PROJECT/logs" +sh "/opt/ci/$PROJECT/ci-build.sh" "$PROJECT" "$LOGS_DIR" "$SHA" ||: + +echo 'To retrigger the build, run:' +echo "cd /srv/http/$PROJECT.git/" +echo "sh /opt/ci/$PROJECT/ci-build.sh" "$PROJECT" "$LOGS_DIR" "$SHA" diff --git a/aux/ci/git-pre-push.sh b/aux/ci/git-pre-push.sh new file mode 100755 index 0000000..eaaa7bd --- /dev/null +++ b/aux/ci/git-pre-push.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -eux + +TLD="$(cat aux/tld.txt)" +. aux/lib.sh + +PROJECT="$(basename "$PWD")" +LOGS_DIR="/opt/ci/$PROJECT/logs" +REMOTE_GIT_DIR="/srv/http/$PROJECT.git" + +DESCRIPTION="$(mkstemp)" +if [ -f description ] +then + cp description "$DESCRIPTION" +else + git config euandreh.description > "$DESCRIPTION" +fi + +scp "$DESCRIPTION" "$TLD:$REMOTE_GIT_DIR/description" +ssh "$TLD" mkdir -p "$LOGS_DIR" +scp aux/ci/ci-build.sh "$TLD:$(dirname "$LOGS_DIR")/ci-build.sh" +scp aux/ci/git-post-receive.sh "$TLD:$REMOTE_GIT_DIR/hooks/post-receive" diff --git a/aux/ci/report.sh b/aux/ci/report.sh new file mode 100755 index 0000000..e900e26 --- /dev/null +++ b/aux/ci/report.sh @@ -0,0 +1,109 @@ +#!/bin/sh +set -eu + +TLD="$(cat aux/tld.txt)" +. aux/lib.sh + +while getopts 'n:o:' flag; do + case "$flag" in + n) + PROJECT="$OPTARG" + ;; + o) + OUTDIR="$OPTARG" + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +assert_arg() { + if [ -z "$1" ]; then + echo "Missing $2" >&2 + exit 2 + fi +} + +assert_arg "${PROJECT:-}" '-n PROJECT' +assert_arg "${OUTDIR:-}" '-o OUTDIR' + +PASS='✅' +FAIL='❌' + +mkdir -p "$OUTDIR/ci-logs" "$OUTDIR/ci-data" + +OUT="$(mkstemp)" +chmod 644 "$OUT" + +for c in $(git notes list | cut -d\ -f2); do + DATA="$(git notes --ref=refs/notes/ci-data show "$c")" + FILENAME="$(echo "$DATA" | cut -d\ -f2)" + echo "$DATA" > "$OUTDIR/ci-data/$FILENAME" + git notes --ref=refs/notes/ci-logs show "$c" \ + > "$OUTDIR/ci-logs/$FILENAME" +done + +{ + cat <<EOF +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1" /> + <meta name="description" content="CI logs for $PROJECT" /> + <link rel="icon" type="image/svg+xml" href="favicon.svg" /> + <title>$PROJECT - CI logs</title> + +EOF + + cat aux/workflow/style.css + + cat <<EOF + + <style> + pre { + display: inline; + } + ol { + list-style-type: disc; + } + </style> + </head> + <body> + <main> + <h1> + CI logs for + <a href="https://$TLD/$PROJECT/en/">$PROJECT</a> + </h1> + <ol> +EOF +} > "$OUT" + +for f in $(find "$OUTDIR/ci-data/" -type f | LANG=C.UTF-8 sort -r); do + DATA="$(cat "$f")" + STATUS="$(echo "$DATA" | cut -d\ -f1)" + FILENAME="$(echo "$DATA" | cut -d\ -f2)" + + if [ "$STATUS" = 0 ]; then + STATUS_MARKER="$PASS" + else + STATUS_MARKER="$FAIL" + fi + + cat <<EOF >> "$OUT" + <li> + <a href="ci-logs/$FILENAME">$STATUS_MARKER <pre>$FILENAME</pre></a> + </li> +EOF +done + +cat <<EOF >> "$OUT" + </ol> + </main> + </body> +</html> +EOF + +mv "$OUT" "$OUTDIR/ci.html" diff --git a/aux/containers/guix/manifest.scm b/aux/containers/guix/manifest.scm new file mode 100644 index 0000000..7d07345 --- /dev/null +++ b/aux/containers/guix/manifest.scm @@ -0,0 +1,23 @@ +(specifications->manifest + (map symbol->string + '(bash + coreutils + findutils + diffutils + grep + sed + m4 + git + gawk + make + graphviz + shellcheck + pandoc + gettext + po4a-text + mdpo-patched + hunspell + hunspell-dict-en-utf8 + hunspell-dict-pt-utf8 + hunspell-dict-fr-utf8 + hunspell-dict-eo-utf8))) diff --git a/aux/containers/guix/with-container.sh b/aux/containers/guix/with-container.sh new file mode 100755 index 0000000..fcbb8b5 --- /dev/null +++ b/aux/containers/guix/with-container.sh @@ -0,0 +1,19 @@ +#!/bin/sh +set -eu + +CHANNEL_REMOTE='https://euandreh.xyz/package-repository.git' +CHANNEL_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/euandreh-guix-channel" + +if [ ! -d "$CHANNEL_DIR" ]; then + git clone "$CHANNEL_REMOTE" "$CHANNEL_DIR" +fi + +git -C "$CHANNEL_DIR" pull + +ENV_CMD="guix environment -L $CHANNEL_DIR/src/ -m aux/containers/guix/manifest.scm" + +if [ -z "${1:-}" ]; then + $ENV_CMD +else + $ENV_CMD --pure -C -- sh -c "$@" +fi diff --git a/aux/lib.sh b/aux/lib.sh new file mode 100755 index 0000000..b47812d --- /dev/null +++ b/aux/lib.sh @@ -0,0 +1,30 @@ +#!/bin/sh + +# +# Generally, utilities that I expected to exist in POSIX, but don't. +# + +uuid() { + # Taken from: + # https://serverfault.com/a/799198 + od -xN20 /dev/urandom | + head -n1 | + awk '{OFS="-"; print $2$3,$4,$5,$6,$7$8$9}' +} + +tmpname() { + echo 'mkstemp(template)' | m4 -D template="${TMPDIR:-/tmp}/m4-tmpname." +} + +mkstemp() { + name="$(tmpname)" + touch "$name" + echo "$name" +} + +mkdtemp() { + name="$(tmpname)" + rm -f "$name" + mkdir "$name" + echo "$name" +} diff --git a/aux/tld.txt b/aux/tld.txt new file mode 100644 index 0000000..0cb8b8b --- /dev/null +++ b/aux/tld.txt @@ -0,0 +1 @@ +euandreh.xyz diff --git a/aux/with-container b/aux/with-container new file mode 120000 index 0000000..b96cf67 --- /dev/null +++ b/aux/with-container @@ -0,0 +1 @@ +containers/guix/with-container.sh
\ No newline at end of file diff --git a/aux/workflow/TODOs.sh b/aux/workflow/TODOs.sh new file mode 100755 index 0000000..b7cbae1 --- /dev/null +++ b/aux/workflow/TODOs.sh @@ -0,0 +1,64 @@ +#!/bin/sh +set -eu + +TLD="$(cat aux/tld.txt)" +PROJECT_UC= +while getopts 'n:N:m:o:' flag; do + case "$flag" in + n) + PROJECT="$OPTARG" + ;; + N) + PROJECT_UC="$OPTARG" + ;; + m) + MAILING_LIST="$OPTARG" + ;; + o) + OUTDIR="$OPTARG" + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +assert_arg() { + if [ -z "$1" ]; then + echo "Missing $2" >&2 + exit 2 + fi +} + +assert_arg "${PROJECT:-}" '-n PROJECT' +assert_arg "${MAILING_LIST:-}" '-m MAILING_LIST' +assert_arg "${OUTDIR:-}" '-o OUTDIR' + +if [ -z "${PROJECT_UC:-}" ]; then + PROJECT_UC="$PROJECT" +fi + + +cat aux/workflow/preamble.md TODOs.md | + td -H | + sed \ + -e "s:@PROJECT_UC@:$PROJECT_UC:g" \ + -e "s:@PROJECT@:$PROJECT:g" \ + -e "s:@MAILING_LIST@:$MAILING_LIST:g" \ + -e "s:@TLD@:$TLD:g" | + pandoc \ + --toc \ + --highlight-style pygments \ + --toc-depth=2 \ + -s \ + --metadata title="$PROJECT_UC - TODOs" \ + --metadata lang=en \ + -r commonmark \ + -w html \ + -H aux/workflow/favicon.html \ + -H aux/workflow/style.css | + sed \ + -e 's:<a><a:<a:g' \ + -e 's:</a></a>:</a>:g' \ + > "$OUTDIR/TODOs.html" diff --git a/aux/workflow/assert-changelog.sh b/aux/workflow/assert-changelog.sh new file mode 100755 index 0000000..c58a600 --- /dev/null +++ b/aux/workflow/assert-changelog.sh @@ -0,0 +1,66 @@ +#!/bin/sh +set -eu + +TLD="$(cat aux/tld.txt)" +PROJECT_UC= +while getopts 'n:N:' flag; do + case "$flag" in + n) + PROJECT="$OPTARG" + ;; + N) + PROJECT_UC="$OPTARG" + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +assert_arg() { + if [ -z "$1" ]; then + echo "Missing $2" >&2 + exit 2 + fi +} + +assert_arg "${PROJECT:-}" '-n PROJECT' + +if [ -z "${PROJECT_UC:-}" ]; then + PROJECT_UC="$PROJECT" +fi + +HOMEPAGE_LINK="Changelog for [$PROJECT_UC](https://$TLD/$PROJECT/en/)." + +if ! grep -qF "$HOMEPAGE_LINK" CHANGELOG.md; then + echo "Missing link to homepage in CHANGELOG.md:" >&2 + echo "$HOMEPAGE_LINK" + exit 1 +fi + +assert() { + DATE="$1" + VVERSION="$2" + VERSION="${2#v}" + CHANGELOG_ENTRY="$(printf \ + '# [%s](https://euandreh.xyz/%s.git/commit/?id=%s) - %s' \ + "$VERSION" "$PROJECT" "$VVERSION" "$DATE")" + if ! grep -qF "$CHANGELOG_ENTRY" CHANGELOG.md; then + echo "Missing '$CHANGELOG_ENTRY' entry from CHANGELOG.md" >&2 + exit 1 + fi +} + +if [ -e .git ]; then + for VVERSION in $(git tag); do + DATE="$(git log -1 --format=%cd --date=short "$VVERSION")" + assert "$DATE" "$VVERSION" + done +fi + +# "$@" represents a list of tags to be also included in the verification. +for VVERSION in "$@"; do + DATE="$(date '+%Y-%m-%d')" + assert "$DATE" "$VVERSION" +done diff --git a/aux/workflow/assert-readme.sh b/aux/workflow/assert-readme.sh new file mode 100755 index 0000000..0a85221 --- /dev/null +++ b/aux/workflow/assert-readme.sh @@ -0,0 +1,108 @@ +#!/bin/sh +set -eu + +if [ ! -e .git ]; then + exit +fi + +TLD="$(cat aux/tld.txt)" +. aux/lib.sh + +while getopts 'n:m:' flag; do + case "$flag" in + n) + PROJECT="$OPTARG" + ;; + m) + MAILING_LIST="$OPTARG" + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +assert_arg() { + if [ -z "$1" ]; then + echo "Missing $2" >&2 + exit 2 + fi +} + +assert_arg "${PROJECT:-}" '-n PROJECT' +assert_arg "${MAILING_LIST:-}" '-m MAILING_LIST' + +EXPECTED="$(mkstemp)" +cat <<EOF >> "$EXPECTED" + +For running the extra development-only checks, run: + +\`\`\`shell +$ make dev-check +\`\`\` + +and for generating the documentation HTML and website, run: + +\`\`\`shell +$ make public +\`\`\` + +Send contributions to the [mailing list] via +[\`git send-email\`](https://git-send-email.io/). + + +## Links + +- [homepage](https://$TLD/$PROJECT/en/) +- [source code](https://euandreh.xyz/$PROJECT.git/) +- [bug tracking](https://$TLD/$PROJECT/TODOs.html) +- [mailing list] +- [CI logs](https://$TLD/$PROJECT/ci.html) +- [CHANGELOG](https://$TLD/$PROJECT/en/CHANGELOG.html) + +[mailing list]: https://lists.sr.ht/~euandreh/$MAILING_LIST?search=%5B$PROJECT%5D +EOF + +RELEASES_LIST="$(mkstemp)" +add_release() { + DATE="$1" + VVERSION="$2" + echo "- [$VVERSION](https://euandreh.xyz/$PROJECT.git/commit/?id=$VVERSION) [$PROJECT-$VVERSION.tar.gz](https://euandreh.xyz/$PROJECT.git/snapshot/$PROJECT-$VVERSION.tar.gz) ([sig](https://euandreh.xyz/$PROJECT.git/snapshot/$PROJECT-$VVERSION.tar.gz.asc)) - $DATE" >> "$RELEASES_LIST" +} + +for VVERSION in $(git tag); do + DATE="$(git log -1 --format=%cd --date=short "$VVERSION")" + add_release "$DATE" "$VVERSION" +done + +# "$@" represents a list of tags to be also included in the verification. +for VVERSION in "$@"; do + if ! git tag | grep -qF "$VVERSION"; then + DATE="$(date '+%Y-%m-%d')" + add_release "$DATE" "$VVERSION" + fi +done + +if [ -s "$RELEASES_LIST" ]; then + printf '\n\n## Releases\n\n' >> "$EXPECTED" + sort -r "$RELEASES_LIST" >> "$EXPECTED" +fi + +cat <<EOF >> "$EXPECTED" + + +## License + +The code is licensed under +[GNU Affero General Public License v3.0 or later][AGPL-3.0-or-later] +(AGPL-3.0-or-later). + +[AGPL-3.0-or-later]: https://euandreh.xyz/$PROJECT.git/tree/COPYING +EOF + +if ! tail -n "$(wc -l < "$EXPECTED")" README.md | diff - "$EXPECTED"; then + echo 'Wrong metadata at the end of README.md file' + echo "See expected content at: $EXPECTED" + exit 1 +fi diff --git a/aux/workflow/assert-todos.sh b/aux/workflow/assert-todos.sh new file mode 100755 index 0000000..bc4907d --- /dev/null +++ b/aux/workflow/assert-todos.sh @@ -0,0 +1,58 @@ +#!/bin/sh +set -eu + +if [ -e .git ] && git grep FIXME | grep -v '^TODOs.md' | + grep -v '^aux/workflow/assert-todos.sh'; then + echo "Found dangling FIXME markers on the project." + echo "You should write them down properly on TODOs.md." + exit 1 +fi + +awk -F'{#' ' +BEGIN { + exitstatus = 0 + h2flag = 0 + h2status = "" + prevline = "" + idx = 0 + delete ids[0] +} +h2flag == 1 { + split($0, l, " ") + timelinestatus = l[2] + if (h2status != timelinestatus) { + print "h2/timeline status mismatch for line " NR-1 + print prevline + print $0 + exitstatus = 1 + } + h2status = "" + h2flag = 0 +} + +/^## (TODO|DOING|WAITING|MEETING|INACTIVE|NEXT|CANCELLED|DONE|WONTFIX)/ { + if (match($0, / \{#.*?\}.*$/) == 0) { + print "Missing ID for line " NR ":\n" $0 + exitstatus = 1 + } + id_with_prefix = substr($2, 0, length($2) - 1) + match(id_with_prefix, /^\w+-/) + id = substr(id_with_prefix, RLENGTH + 1) + if (id in arr) { + print "Duplicate ID: " id + exitstatus = 1 + } else { + arr[id] = 1 + } + + split($0, l, " ") + h2status = l[2] + h2flag = 1 + prevline = $0 +} + + +/^# Scratch$/ { + exit exitstatus +} +' TODOs.md diff --git a/aux/workflow/commonmark.sh b/aux/workflow/commonmark.sh new file mode 100755 index 0000000..088d447 --- /dev/null +++ b/aux/workflow/commonmark.sh @@ -0,0 +1,48 @@ +#!/bin/sh +set -eu + +while getopts 'N:t:l:H:' flag; do + case "$flag" in + N) + PROJECT_UC="$OPTARG" + ;; + t) + TITLE="$OPTARG" + ;; + l) + THE_LANG="$OPTARG" + ;; + H) + ALTERNATES="$OPTARG" + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +assert_arg() { + if [ -z "$1" ]; then + echo "Missing $2" >&2 + exit 2 + fi +} + +assert_arg "${PROJECT_UC:-}" '-N PROJECT_UC' +assert_arg "${TITLE:-}" '-t TITLE' +assert_arg "${THE_LANG:-}" '-l THE_LANG' +assert_arg "${ALTERNATES:-}" '-H ALTERNATES' + +pandoc \ + --toc \ + --highlight-style pygments \ + --toc-depth=2 \ + -s \ + --metadata title="$PROJECT_UC - $TITLE" \ + --metadata "lang=$THE_LANG" \ + -r commonmark \ + -w html \ + -H aux/workflow/favicon.html \ + -H aux/workflow/style.css \ + -H "$ALTERNATES" diff --git a/aux/workflow/dist.sh b/aux/workflow/dist.sh new file mode 100755 index 0000000..48a9d57 --- /dev/null +++ b/aux/workflow/dist.sh @@ -0,0 +1,112 @@ +#!/bin/sh +set -eu + +PROJECT_UC= +while getopts 'd:V:n:N:m:' flag; do + case "$flag" in + d) + DATE="$OPTARG" + ;; + V) + VVERSION="v$OPTARG" + ;; + n) + PROJECT="$OPTARG" + ;; + N) + PROJECT_UC="$OPTARG" + ;; + m) + MAILING_LIST="$OPTARG" + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +assert_arg() { + if [ -z "$1" ]; then + echo "Missing $2" >&2 + exit 2 + fi +} + +assert_arg "${DATE:-}" '-d DATE' +assert_arg "${VVERSION:-}" '-V VERSION' +assert_arg "${PROJECT:-}" '-n PROJECT' +assert_arg "${MAILING_LIST:-}" '-m MAILING_LIST' + +if [ -z "${PROJECT_UC:-}" ]; then + PROJECT_UC="$PROJECT" +fi + + +if [ "$(git rev-parse --abbrev-ref HEAD)" != 'main' ]; then + echo 'Not on branch "main".' >&2 + exit 1 +fi + +if git show "$VVERSION" 1>/dev/null 2>/dev/null; then + echo "Version '$VVERSION' already exists." >&2 + exit 1 +fi + +if [ "v$(awk '/^VERSION *=/{print $3; exit}' Makefile)" != "$VVERSION" ]; then + echo "Version '$VVERSION' mismatch with \$(VERSION) in Makefile." >&2 + echo 'Make sure to invoke this script with "make dist".' >&2 + exit 1 +fi + +if ! printf '%s\n%s\n' "$(git tag)" "$VVERSION" | sort -nct. -k1 -k2 -k3; then + echo 'New tag is not bigger than existing ones.' >&2 + exit 1 +fi + +if [ "$DATE" != "$(git log -1 --format=%cd --date=short HEAD)" ]; then + echo "Date '$DATE' is not up-to-date." >&2 + exit 1 +fi + +if [ "$(awk '/^DATE *=/{print $3; exit}' Makefile)" != "$DATE" ]; then + echo "Date '$DATE' mismatch with \$(DATE) in Makefile." >&2 + echo 'Make sure to invoke this script with "make dist".' >&2 + exit 1 +fi + +if [ "Release $VVERSION" != "$(git log --format=%B -1 HEAD | head -n1)" ]; then + echo "Commit message isn't 'Release $VVERSION'." >&2 + exit 1 +fi + +make clean +make dev-check EXTRA_VERSION="$VVERSION" + +if ! (git diff --quiet && git diff --quiet --staged); then + echo 'Dirty repository.' + exit 1 +fi + + +git tag "$VVERSION" +sh aux/workflow/sign-tarballs.sh -n "$PROJECT" + + +printf 'Publish version? [Y/n]: ' >&2 +read -r publish + +if [ "$publish" = 'n' ]; then + cat <<EOF >&2 +Now push the tag and the signature before pushing the commit: + +git push origin refs/notes/signatures/tar.gz -o skip-ci --no-verify +git push --tags -o skip-ci --no-verify +git push + +EOF +else + git push origin refs/notes/signatures/tar.gz -o skip-ci --no-verify + git push --tags -o skip-ci --no-verify + git push +fi diff --git a/aux/workflow/favicon.html b/aux/workflow/favicon.html new file mode 100644 index 0000000..8f9327c --- /dev/null +++ b/aux/workflow/favicon.html @@ -0,0 +1 @@ +<link rel="icon" type="image/svg+xml" href="favicon.svg" /> diff --git a/aux/workflow/favicon.svg b/aux/workflow/favicon.svg new file mode 100644 index 0000000..ce566b2 --- /dev/null +++ b/aux/workflow/favicon.svg @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"> + <path d="M 0 8 L 1 8 L 1 9 L 0 9 L 0 8 Z" /> + <path d="M 0 13 L 1 13 L 1 14 L 0 14 L 0 13 Z" /> + <path d="M 1 8 L 2 8 L 2 9 L 1 9 L 1 8 Z" /> + <path d="M 1 13 L 2 13 L 2 14 L 1 14 L 1 13 Z" /> + <path d="M 2 8 L 3 8 L 3 9 L 2 9 L 2 8 Z" /> + <path d="M 2 13 L 3 13 L 3 14 L 2 14 L 2 13 Z" /> + <path d="M 3 8 L 4 8 L 4 9 L 3 9 L 3 8 Z" /> + <path d="M 3 13 L 4 13 L 4 14 L 3 14 L 3 13 Z" /> + <path d="M 4 7 L 5 7 L 5 8 L 4 8 L 4 7 Z" /> + <path d="M 4 8 L 5 8 L 5 9 L 4 9 L 4 8 Z" /> + <path d="M 4 13 L 5 13 L 5 14 L 4 14 L 4 13 Z" /> + <path d="M 5 6 L 6 6 L 6 7 L 5 7 L 5 6 Z" /> + <path d="M 5 7 L 6 7 L 6 8 L 5 8 L 5 7 Z" /> + <path d="M 5 13 L 6 13 L 6 14 L 5 14 L 5 13 Z" /> + <path d="M 6 5 L 7 5 L 7 6 L 6 6 L 6 5 Z" /> + <path d="M 6 6 L 7 6 L 7 7 L 6 7 L 6 6 Z" /> + <path d="M 6 14 L 7 14 L 7 15 L 6 15 L 6 14 Z" /> + <path d="M 7 1 L 8 1 L 8 2 L 7 2 L 7 1 Z" /> + <path d="M 7 14 L 8 14 L 8 15 L 7 15 L 7 14 Z" /> + <path d="M 7 15 L 8 15 L 8 16 L 7 16 L 7 15 Z" /> + <path d="M 7 2 L 8 2 L 8 3 L 7 3 L 7 2 Z" /> + <path d="M 7 3 L 8 3 L 8 4 L 7 4 L 7 3 Z" /> + <path d="M 7 4 L 8 4 L 8 5 L 7 5 L 7 4 Z" /> + <path d="M 7 5 L 8 5 L 8 6 L 7 6 L 7 5 Z" /> + <path d="M 8 1 L 9 1 L 9 2 L 8 2 L 8 1 Z" /> + <path d="M 8 15 L 9 15 L 9 16 L 8 16 L 8 15 Z" /> + <path d="M 9 1 L 10 1 L 10 2 L 9 2 L 9 1 Z" /> + <path d="M 9 2 L 10 2 L 10 3 L 9 3 L 9 2 Z" /> + <path d="M 9 6 L 10 6 L 10 7 L 9 7 L 9 6 Z" /> + <path d="M 9 15 L 10 15 L 10 16 L 9 16 L 9 15 Z" /> + <path d="M 10 2 L 11 2 L 11 3 L 10 3 L 10 2 Z" /> + <path d="M 10 3 L 11 3 L 11 4 L 10 4 L 10 3 Z" /> + <path d="M 10 4 L 11 4 L 11 5 L 10 5 L 10 4 Z" /> + <path d="M 10 5 L 11 5 L 11 6 L 10 6 L 10 5 Z" /> + <path d="M 10 6 L 11 6 L 11 7 L 10 7 L 10 6 Z" /> + <path d="M 11 6 L 12 6 L 12 7 L 11 7 L 11 6 Z" /> + <path d="M 11 8 L 12 8 L 12 9 L 11 9 L 11 8 Z" /> + <path d="M 10 15 L 11 15 L 11 16 L 10 16 L 10 15 Z" /> + <path d="M 11 10 L 12 10 L 12 11 L 11 11 L 11 10 Z" /> + <path d="M 11 12 L 12 12 L 12 13 L 11 13 L 11 12 Z" /> + <path d="M 11 14 L 12 14 L 12 15 L 11 15 L 11 14 Z" /> + <path d="M 11 15 L 12 15 L 12 16 L 11 16 L 11 15 Z" /> + <path d="M 12 6 L 13 6 L 13 7 L 12 7 L 12 6 Z" /> + <path d="M 12 8 L 13 8 L 13 9 L 12 9 L 12 8 Z" /> + <path d="M 12 10 L 13 10 L 13 11 L 12 11 L 12 10 Z" /> + <path d="M 12 12 L 13 12 L 13 13 L 12 13 L 12 12 Z" /> + <path d="M 12 14 L 13 14 L 13 15 L 12 15 L 12 14 Z" /> + <path d="M 13 6 L 14 6 L 14 7 L 13 7 L 13 6 Z" /> + <path d="M 13 8 L 14 8 L 14 9 L 13 9 L 13 8 Z" /> + <path d="M 13 10 L 14 10 L 14 11 L 13 11 L 13 10 Z" /> + <path d="M 13 12 L 14 12 L 14 13 L 13 13 L 13 12 Z" /> + <path d="M 13 13 L 14 13 L 14 14 L 13 14 L 13 13 Z" /> + <path d="M 13 14 L 14 14 L 14 15 L 13 15 L 13 14 Z" /> + <path d="M 14 7 L 15 7 L 15 8 L 14 8 L 14 7 Z" /> + <path d="M 14 8 L 15 8 L 15 9 L 14 9 L 14 8 Z" /> + <path d="M 14 9 L 15 9 L 15 10 L 14 10 L 14 9 Z" /> + <path d="M 14 10 L 15 10 L 15 11 L 14 11 L 14 10 Z" /> + <path d="M 14 11 L 15 11 L 15 12 L 14 12 L 14 11 Z" /> + <path d="M 14 12 L 15 12 L 15 13 L 14 13 L 14 12 Z" /> +</svg> diff --git a/aux/workflow/l10n.sh b/aux/workflow/l10n.sh new file mode 100755 index 0000000..cb132a3 --- /dev/null +++ b/aux/workflow/l10n.sh @@ -0,0 +1,109 @@ +#!/bin/sh +set -eu + +LANGS= +MAX_JOBS=64 +while getopts 'l:L:j:' flag; do + case "$flag" in + l) + LANGS="$OPTARG" + ;; + L) + CONTRIBLANGS="$OPTARG" + ;; + j) + MAX_JOBS="$OPTARG" + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +assert_arg() { + if [ -z "$1" ]; then + echo "Missing $2" >&2 + exit 2 + fi +} + +assert_arg "${LANGS:-}" '-l LANGS' + +PARALLEL_N=0 +parallel_run() { + { + "$@" + } & + PARALLEL_N=$((PARALLEL_N + 1)) + if [ "$PARALLEL_N" = "$MAX_JOBS" ]; then + wait + PARALLEL_N=0 + fi +} + +po_run() { + from_f="$1" + lang="$2" + to_f="$(echo "$from_f" | sed "s/en\./$lang./")" + printf 'Generating %s...\n' "$to_f" >&2 + pofile="po/LC_MESSAGES/$from_f/$lang.po" + mkdir -p "$(dirname "$pofile")" + + case "$from_f" in + *.en.[1-9].in) + po4a-updatepo -f man -m "$from_f" -p "$pofile" + po4a-translate -f man -m "$from_f" \ + -p "$pofile" -l "$to_f" -k 0 -v >&2 + ;; + *.en.html) + po4a-updatepo -f xhtml -m "$from_f" -p "$pofile" + po4a-translate -f xhtml -m "$from_f" \ + -p "$pofile" -l "$to_f" -k 0 -v >&2 + ;; + *.en.md) + touch "$pofile" + md2po --include-codeblocks --quiet --save \ + --po-filepath "$pofile" < "$from_f" + po2md --pofiles "$pofile" --save "$to_f" \ + --quiet --wrapwidth 999 < "$from_f" + ;; + *.en.msg|*.en.txt) + po4a-updatepo -f text -m "$from_f" -p "$pofile" + po4a-translate -f text -m "$from_f" \ + -p "$pofile" -l "$to_f" -k 0 -v >&2 + ;; + *) + echo "Unsupported file format: $from_f" >&2 + exit 2 + ;; + esac +} + +for from_f in "$@"; do + for lang in $LANGS ${CONTRIBLANGS:-}; do + parallel_run po_run "$from_f" "$lang" + done +done + +EXIT_CODE=0 + +end="\033[0m" +yellowb="\033[1;33m" +for lang in $LANGS; do + # shellcheck disable=2044 + for pofile in $(find po/ -type f -name "$lang.po"); do + if ! LANG=POSIX msgfmt --statistics "$pofile" 2>&1 | + grep untranslated; then + continue + fi + # shellcheck disable=2059 + printf "\n ${yellowb}WARNING${end}!" >&2 + printf "\n Missing translations for %s\n\n" "$pofile" >&2 + EXIT_CODE=1 + done +done + +if [ -n "${ASSERT_NO_MISSING_TRANSLATIONS:-}" ]; then + exit "$EXIT_CODE" +fi diff --git a/aux/workflow/preamble.md b/aux/workflow/preamble.md new file mode 100644 index 0000000..a699d04 --- /dev/null +++ b/aux/workflow/preamble.md @@ -0,0 +1,16 @@ +# About + +TODOs for [@PROJECT_UC@](https://@TLD@/@PROJECT@/en/). + +Register a new one at +<span id="new">[~euandreh/@MAILING_LIST@@lists.sr.ht](mailto:~euandreh/@MAILING_LIST@@lists.sr.ht?subject=%5B@PROJECT@%5D%20BUG%20or%20TASK%3A%20%3Cdescription%3E)</span> +and see [existing discussions](https://lists.sr.ht/~euandreh/@MAILING_LIST@?search=%5B@PROJECT@%5D). + +*Você também pode escrever em português*. + +*Vous pouvez aussi écrire en français*. + +*Vi povas ankaŭ skribi esperante*. + +*Tu también puedes escribir en español*. + diff --git a/aux/workflow/public.sh b/aux/workflow/public.sh new file mode 100755 index 0000000..2c8c36a --- /dev/null +++ b/aux/workflow/public.sh @@ -0,0 +1,83 @@ +#!/bin/sh +set -eu + +TLD="$(cat aux/tld.txt)" +. aux/lib.sh + +PROJECT_UC= +while getopts 'n:N:m:o:l:' flag; do + case "$flag" in + n) + PROJECT="$OPTARG" + ;; + N) + PROJECT_UC="$OPTARG" + ;; + m) + MAILING_LIST="$OPTARG" + ;; + o) + OUTDIR="$OPTARG" + ;; + l) + LANGS="$OPTARG" + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND - 1)) + +assert_arg() { + if [ -z "$1" ]; then + echo "Missing $2" >&2 + exit 2 + fi +} + +assert_arg "${PROJECT:-}" '-n PROJECT' +assert_arg "${MAILING_LIST:-}" '-m MAILING_LIST' +assert_arg "${OUTDIR:-}" '-o OUTDIR' +assert_arg "${LANGS:-}" '-l LANGS' +PROJECT_UC="${PROJECT_UC:-$PROJECT}" + + +alternates_for() { + ALTERNATES="$(mkstemp)" + lang="$1" + OUTNAME="$2" + for l in $LANGS; do + if [ "$l" = "$lang" ]; then + continue + fi + cat <<EOF >> "$ALTERNATES" +<link rel="alternate" href="https://$TLD/$PROJECT/$l/$OUTNAME" hreflang="$l" /> +EOF + done + echo "$ALTERNATES" +} + + +mkdir -p "$OUTDIR" + +sh aux/workflow/TODOs.sh \ + -N "$PROJECT_UC" -n "$PROJECT" -m "$MAILING_LIST" -o "$OUTDIR" + +for lang in $LANGS; do + mkdir -p "$OUTDIR/$lang/" + sh aux/workflow/commonmark.sh \ + -N "$PROJECT" -l "$lang" -t README \ + -H "$(alternates_for "$lang" '')" \ + < "README.$lang.md" > "$OUTDIR/$lang/index.html" + sh aux/workflow/commonmark.sh \ + -N "$PROJECT" -l "$lang" -t CHANGELOG \ + -H "$(alternates_for "$lang" 'CHANGELOG.html')" \ + < "CHANGELOG.$lang.md" > "$OUTDIR/$lang/CHANGELOG.html" + ln -fs ../favicon.svg "$OUTDIR/$lang" +done + +ln -fs en/index.html "$OUTDIR/index.html" +cp aux/workflow/favicon.svg "$OUTDIR" + +sh aux/ci/report.sh -n "$PROJECT" -o "$OUTDIR" diff --git a/aux/workflow/sign-tarballs.sh b/aux/workflow/sign-tarballs.sh new file mode 100755 index 0000000..3ab2bb8 --- /dev/null +++ b/aux/workflow/sign-tarballs.sh @@ -0,0 +1,38 @@ +#!/bin/sh +set -eu + +while getopts 'n:' flag; do + case "$flag" in + n) + PROJECT="$OPTARG" + ;; + *) + exit 2 + ;; + esac +done +shift $((OPTIND -1)) + +assert_arg() { + if [ -z "$1" ]; then + echo "Missing $2" >&2 + exit 2 + fi +} + +assert_arg "${PROJECT:-}" '-n PROJECT' + + +SIGNATURES="$(git notes --ref=refs/notes/signatures/tar.gz list | cut -d\ -f2)" +for tag in $(git tag); do + COMMIT="$(git rev-list -n1 "$tag")" + if echo "$SIGNATURES" | grep -qF "$COMMIT"; then + continue + fi + echo "Adding missing signature to $tag" >&2 + git notes --ref=refs/notes/signatures/tar.gz add -C "$( + git archive --format tar.gz --prefix "$PROJECT-$tag/" "$tag" | + gpg --output - --armor --detach-sign | + git hash-object -w --stdin + )" "$tag" +done diff --git a/aux/workflow/style.css b/aux/workflow/style.css new file mode 100644 index 0000000..56e4712 --- /dev/null +++ b/aux/workflow/style.css @@ -0,0 +1,62 @@ +<style> + @media(prefers-color-scheme: dark) { + :root { + color: white; + background-color: black; + } + + a { + color: hsl(211, 100%, 60%); + } + + a:visited { + color: hsl(242, 100%, 80%); + } + } + + body { + max-width: 800px; + margin: 0 auto 0 auto; + } + + hr { + background-color: #ccc; + } + + .header-anchor { + opacity: 0.5; + } + + .tag { + font-family: monospace; + font-size: 70%; + background-color: lightgray; + color: black; + padding: 3px; + border-radius: 5px; + } + + .TODO { + color: brown; + } + + .DOING { + color: yellowgreen; + } + + .WAITING, .MEETING { + color: orange; + } + + .INACTIVE { + color: gray; + } + + .NEXT { + color: red; + } + + .CANCELLED, .DONE, .WONTFIX { + color: green; + } +</style> |