#!/bin/sh # Check that the daemon source and docs/PROTOCOL.md have not drifted apart: # # 1. every command in g_commands[] (src/commands.c) is listed in a # PROTOCOL.md table whose first column is "Command", and vice versa; # 2. every error code passed to fail()/failf()/add_error() in src/*.c is # documented in PROTOCOL.md section 5.3, and vice versa (documented but # unused codes are warnings only, some are reserved). # # Run from the repo root: sh scripts/check-consistency.sh # Exits 0 when there is no drift, 1 when drift is found, 2 on parse errors. # # Environment overrides (used by the self-tests): # REPO_ROOT repository root, default "." (run from the repo root) # COMMANDS_C command table source, default $REPO_ROOT/src/commands.c # PROTOCOL_MD protocol document, default $REPO_ROOT/docs/PROTOCOL.md # SRC_GLOB shell glob of sources scanned for error codes, # default $REPO_ROOT/src/*.c # # Commands deliberately kept out of the PROTOCOL.md tables. Prefer documenting # the command in a table over adding it here. Keep sorted, space separated. ALLOW_UNDOCUMENTED_COMMANDS="" set -eu export LC_ALL=C REPO_ROOT=${REPO_ROOT:-.} COMMANDS_C=${COMMANDS_C:-$REPO_ROOT/src/commands.c} PROTOCOL_MD=${PROTOCOL_MD:-$REPO_ROOT/docs/PROTOCOL.md} SRC_GLOB=${SRC_GLOB:-$REPO_ROOT/src/*.c} if [ ! -f "$COMMANDS_C" ]; then echo "check-consistency: command source not found: $COMMANDS_C" >&2 exit 2 fi if [ ! -f "$PROTOCOL_MD" ]; then echo "check-consistency: protocol document not found: $PROTOCOL_MD" >&2 exit 2 fi tmp=$(mktemp -d "${TMPDIR:-/tmp}/bokf-consistency-XXXXXX") trap 'rm -rf "$tmp"' EXIT HUP INT TERM # --- extract what the code implements -------------------------------------- awk ' /g_commands[[:space:]]*\[[[:space:]]*\][[:space:]]*=/ { in_table = 1; next } in_table && /^[[:space:]]*\};/ { in_table = 0 } in_table && /^[[:space:]]*\{[[:space:]]*"/ { line = $0 sub(/^[^{]*\{[[:space:]]*"/, "", line) sub(/".*/, "", line) if (line != "") print line } ' "$COMMANDS_C" | sort -u > "$tmp/impl_commands" if [ ! -s "$tmp/impl_commands" ]; then echo "check-consistency: no commands parsed from $COMMANDS_C" >&2 exit 2 fi # --- extract what the docs promise ----------------------------------------- # Only rows of tables whose header first cell is "Command" count; prose, # argument names and other tables must not produce command names. awk ' /^\|/ { n = split($0, cells, "|") cell = cells[2] gsub(/^[[:space:]]+/, "", cell) gsub(/[[:space:]]+$/, "", cell) if (cell == "Command") { in_table = 1; next } if (in_table) { rest = cell while (match(rest, /`[a-z][a-z0-9_.]*`/)) { print substr(rest, RSTART + 1, RLENGTH - 2) rest = substr(rest, RSTART + RLENGTH) } } next } { in_table = 0 } ' "$PROTOCOL_MD" | sort -u > "$tmp/doc_commands" if [ ! -s "$tmp/doc_commands" ]; then echo "check-consistency: no command tables parsed from $PROTOCOL_MD" >&2 exit 2 fi # Error codes are the backticked ALL_CAPS words inside section 5.3. awk ' /^###[[:space:]]+5\.3/ { in_section = 1; next } in_section && /^###/ { in_section = 0 } in_section { rest = $0 while (match(rest, /`[A-Z][A-Z0-9_]+`/)) { print substr(rest, RSTART + 1, RLENGTH - 2) rest = substr(rest, RSTART + RLENGTH) } } ' "$PROTOCOL_MD" | sort -u > "$tmp/doc_codes" if [ ! -s "$tmp/doc_codes" ]; then echo "check-consistency: no error codes parsed from $PROTOCOL_MD 5.3" >&2 exit 2 fi # --- compare ---------------------------------------------------------------- : > "$tmp/allow_commands" for name in $ALLOW_UNDOCUMENTED_COMMANDS; do printf '%s\n' "$name" >> "$tmp/allow_commands" done sort -u -o "$tmp/allow_commands" "$tmp/allow_commands" cat "$tmp/doc_commands" "$tmp/allow_commands" | sort -u > "$tmp/known_commands" comm -23 "$tmp/impl_commands" "$tmp/known_commands" > "$tmp/cmd_undocumented" comm -13 "$tmp/impl_commands" "$tmp/doc_commands" > "$tmp/cmd_unimplemented" for f in $SRC_GLOB; do [ -f "$f" ] || continue grep -E '(^|[^A-Za-z0-9_])(fail|failf|add_error)[[:space:]]*\(' "$f" | grep -oE '"[A-Z][A-Z0-9_]{1,}"' || true done | tr -d '"' | sort -u > "$tmp/used_codes" if [ ! -s "$tmp/used_codes" ]; then echo "check-consistency: no error-code call sites parsed from $SRC_GLOB" >&2 exit 2 fi comm -23 "$tmp/used_codes" "$tmp/doc_codes" > "$tmp/code_undocumented" comm -13 "$tmp/used_codes" "$tmp/doc_codes" > "$tmp/code_unused" # --- report ----------------------------------------------------------------- status=0 problems=0 warnings=0 section() { echo echo "== $1 ==" } section "commands: implemented in $COMMANDS_C but not documented in $PROTOCOL_MD" if [ -s "$tmp/cmd_undocumented" ]; then while IFS= read -r name; do echo "command: undocumented: $name" problems=$((problems + 1)) done < "$tmp/cmd_undocumented" status=1 else echo "(none)" fi section "commands: documented in $PROTOCOL_MD but not implemented" if [ -s "$tmp/cmd_unimplemented" ]; then while IFS= read -r name; do echo "command: documented but unimplemented: $name" problems=$((problems + 1)) done < "$tmp/cmd_unimplemented" status=1 else echo "(none)" fi section "error codes: used in src but not documented in $PROTOCOL_MD 5.3" if [ -s "$tmp/code_undocumented" ]; then while IFS= read -r code; do echo "error-code: undocumented: $code" problems=$((problems + 1)) done < "$tmp/code_undocumented" status=1 else echo "(none)" fi section "warnings (not failures)" if [ -s "$tmp/code_unused" ]; then while IFS= read -r code; do echo "warning: documented but unused in src: $code" warnings=$((warnings + 1)) done < "$tmp/code_unused" echo "(documented codes may be reserved for future use)" else echo "(none)" fi echo if [ "$status" -eq 0 ]; then echo "consistency: ok ($problems problems, $warnings warnings)" else echo "consistency: FAILED ($problems problems, $warnings warnings)" fi exit "$status"