#!/usr/bin/env bash set -Eeuo pipefail end="\033[0m" red="\033[0;31m" yellow="\033[0;33m" red() { echo -e "${red}${1}${end}"; } yellow() { echo -e "${yellow}${1}${end}"; } TRACKERS='-a udp://tracker.coppersurfer.tk:6969/announce -a udp://tracker.ccc.de:80/announce -a udp://tracker.publicbt.com:80 -a udp://tracker.istole.it:80 -a http://tracker.openbittorrent.com:80/announce -a http://tracker.ipv6tracker.org:80/announce' AWK_S=' BEGIN { FRONTMATTER=0 } /^---$/ { FRONTMATTER=!FRONTMATTER; getline; # strip empty line below end of frontmatter next; } FRONTMATTER==0 { print $0; } ' ## Constant definitions jekyll build --future JSON='_site/site.json' LANGS=(en pt fr eo) # jp zh es de IGNORED_PAGES=(site.json sitemap.xml) ## Helper function definitions slugify() { echo "${1}" | \ tr '[:upper:]' '[:lower:]' | \ perl -ne 'tr/\000-\177//cd; s|/|-|g; s/[^\w\s-.]//g; s/^\s+|\s+$//g; s/[-\s.]+/-/g; print;' } contains-element() { local e match="$1" shift for e; do [[ "$e" == "$match" ]] && return 0; done return 1 } fail-attr() { ATTRIBUTE="${1}" URL="${2}" red "Undefined '${ATTRIBUTE}' for ${URL}." >&2 exit 1 } get-lang() { echo "${1}" | base64 --decode | jq -r .lang } get-url() { # Remove leading / to match more closely the filesystem hierarchy echo "${1}" | base64 --decode | jq -r .url | sed 's_^/__' } get-date() { echo "${1}" | base64 --decode | jq -r .date | awk '{print $1}' } get-x() { echo "${2}" | base64 --decode | jq -r ".$1" } is-ignored() { URL="$1" EXTENSION="${URL##*.}" if contains-element "${URL}" "${IGNORED_PAGES[@]}" || [[ "$EXTENSION" == 'atom' ]]; then return 0 else return 1 fi } ## Assertions assert-frontmatter() { F="$1" DESIRED_LAYOUT="$2" PREFIX="${3:-}" EXTENSION="${4:-md}" LLANG="$(get-lang "$F")" REF="$(get-x ref "$F")" URL="$(get-url "$F")" LAYOUT="$(get-x layout "$F")" TITLE="$(get-x title "$F")" [[ -z "${LLANG}" ]] && fail-attr 'lang' "${URL}" [[ -z "${REF}" ]] && fail-attr 'ref' "${URL}" [[ -z "${TITLE}" ]] && fail-attr 'title' "${URL}" if ! contains-element "${LLANG}" "${LANGS[@]}"; then red "Invalid lang '${LLANG}' in ${URL}." >&2 exit 1 fi if [[ "${DESIRED_LAYOUT}" != "${LAYOUT}" ]]; then red "Layout mismatch: expected '${DESIRED_LAYOUT}', got '${LAYOUT}'." red "Page: ${URL}." exit 1 fi if [[ "$PREFIX" = '_podcasts/' ]]; then AUDIO="$(get-x audio "$F")" SLUG="$(get-x slug "$F")" [[ -z "$AUDIO" ]] && fail-attr 'audio' "${URL}" [[ -z "$SLUG" ]] && fail-attr 'slug' "${URL}" TITLE_SLUG="$(slugify "$TITLE")" if [[ "$SLUG" != "$TITLE_SLUG" ]]; then red "slug and title don't match." red "slug: '$SLUG'" red "title slug: '$TITLE_SLUG'" exit 1 fi for audiofmt in flac ogg; do DATE="$(get-date "$F")" URL_BASENAME="$(basename "$(get-url "$F")")" AUDIO="resources/podcasts/${DATE}-${SLUG}.$audiofmt" if [[ ! -f "$AUDIO" ]]; then red "Missing audio file '$AUDIO'." exit 1 fi done OGG="resources/podcasts/$DATE-$SLUG.ogg" TORRENT="$OGG.torrent" WEBSEED="https://euandre.org/$OGG" if [ ! -f "$TORRENT" ]; then yellow "Missing torrent $TORRENT, generating..." # shellcheck disable=2086 NOTES="$(awk "$AWK_S" "_podcasts/$DATE-$SLUG.md")" mktorrent $TRACKERS \ -f \ -v \ -d \ -c "$NOTES" \ -n "$TITLE.ogg" \ -w "$WEBSEED" \ -o "$TORRENT" \ "$OGG" fi fi if [[ "$PREFIX" = '_screencasts/' ]]; then VIDEO="$(get-x video "$F")" SLUG="$(get-x slug "$F")" [[ -z "$VIDEO" ]] && fail-attr 'video' "${URL}" [[ -z "$SLUG" ]] && fail-attr 'slug' "${URL}" TITLE_SLUG="$(slugify "$TITLE")" if [[ "$SLUG" != "$TITLE_SLUG" ]]; then red "slug and title don't match." red "slug: '$SLUG'" red "title slug: '$TITLE_SLUG'" exit 1 fi DATE="$(get-date "$F")" URL_BASENAME="$(basename "$(get-url "$F")")" VIDEO="resources/screencasts/${DATE}-${SLUG}.webm" if [[ ! -f "$VIDEO" ]]; then red "Missing video file '$VIDEO'." exit 1 fi WEBM="resources/screencasts/$DATE-$SLUG.webm" TORRENT="$WEBM.torrent" WEBSEED="https://euandre.org/$WEBM" if [ ! -f "$TORRENT" ]; then yellow "Missing torrent $TORRENT, generating..." # shellcheck disable=2086 NOTES="$(awk "$AWK_S" "_screencasts/$DATE-$SLUG.md")" mktorrent $TRACKERS \ -f \ -v \ -d \ -c "$NOTES" \ -n "$TITLE.webm" \ -w "$WEBSEED" \ -o "$TORRENT" \ "$WEBM" fi fi if [[ "$DESIRED_LAYOUT" != 'page' ]]; then DATE="$(get-date "$F")" URL_BASENAME="$(basename "$(get-url "$F")")" FILE="${PREFIX}${DATE}-${URL_BASENAME%.html}.${EXTENSION}" [[ -f "${FILE}" ]] || { red "date/filename mismatch: '${FILE}' does not exist." exit 1 } if [[ "$LLANG" = 'en' ]]; then TITLE_SLUG="$(slugify "$TITLE")" if [[ "$TITLE_SLUG" != "$REF" ]]; then red "ref isn't the slug of the title." red "ref: '$REF'" red "title slug: '$TITLE_SLUG'" exit 1 fi DESIRED_FILE="${PREFIX}${DATE}-${TITLE_SLUG}.${EXTENSION}" if [[ ! -f "$DESIRED_FILE" ]]; then red "File can't be guessed from date+slug: '$DESIRED_FILE' does not exist" exit 1 fi fi fi } echo Linting pages... >&2 for page in $(jq -r '.pages[] | @base64' "${JSON}"); do URL="$(get-url "$page")" if ! is-ignored "${URL}"; then assert-frontmatter "${page}" 'page' fi done echo Linting articles... >&2 for article in $(jq -r '.articles[] | @base64' "${JSON}"); do assert-frontmatter "$article" 'post' '_articles/' done echo Linting pastebins... >&2 for pastebin in $(jq -r '.pastebins[] | @base64' "${JSON}"); do assert-frontmatter "$pastebin" 'post' '_pastebins/' done echo Linting tils... >&2 for til in $(jq -r '.tils[] | @base64' "${JSON}"); do assert-frontmatter "$til" 'post' '_tils/' done echo Linting slides... >&2 for slide in $(jq -r '.slides[] | @base64' "${JSON}"); do assert-frontmatter "$slide" 'slides' '_slides/' 'slides' done echo Linting podcasts... >&2 for podcast in $(jq -r '.podcasts[] | @base64' "${JSON}"); do assert-frontmatter "$podcast" 'cast' '_podcasts/' done echo Linting screencasts... >&2 for screencast in $(jq -r '.screencasts[] | @base64' "${JSON}"); do assert-frontmatter "$screencast" 'cast' '_screencasts/' done echo Asserting unique refs... >&2 KNOWN_IDS=() assert-unique-ref() { TYPE="$2" for page in $1; do URL="$(get-url "$page")" if ! is-ignored "${URL}"; then LLANG="$(get-lang "$page")" REF="$(get-x ref "$page")" ID="${TYPE}:${LLANG}:${REF}" if contains-element "${ID}" "${KNOWN_IDS[@]}"; then printf '%s\n' "${KNOWN_IDS[@]}" red "Duplicated lang:ref match: '${ID}'." >&2 red "Page: ${URL}." >&2 exit 1 fi KNOWN_IDS+=("${ID}") # printf '%s\n' "${KNOWN_IDS[@]}" fi done } assert-unique-ref "$(jq -r '.pages[] | @base64' "${JSON}")" 'page' assert-unique-ref "$(jq -r '.articles[] | @base64' "${JSON}")" 'article' assert-unique-ref "$(jq -r '.tils[] | @base64' "${JSON}")" 'til' assert-unique-ref "$(jq -r '.pastebins[] | @base64' "${JSON}")" 'pastebin' assert-unique-ref "$(jq -r '.slides[] | @base64' "${JSON}")" 'slides' assert-unique-ref "$(jq -r '.podcasts[] | @base64' "${JSON}")" 'podcasts' assert-unique-ref "$(jq -r '.screencasts[] | @base64' "${JSON}")" 'screencasts' echo Done. >&2