#!/bin/bash
#
# kdesc_test.sh — the kdesc command's interface.
#
# The flag structure was reorganised 2026-08-11
# (notes/modifying_the_kdesc_arguments.md) on two rules:
#
#   * a flag a user reaches for often gets a single letter (-k klammers,
#     -t targets, -c character codes, -i input); a more specialised topic gets
#     a multi-letter name (--argtypes, --katoms, --rewrite, --optionsets,
#     --coverage, --klammersets, --font);
#   * -v says how much to show about the command's PROCESSING and never what
#     its RESULT contains.  So the katom regex column is "--katoms full" and
#     the coverage file column is "--coverage all", not verbosity levels.
#
# "-k" is the one rename that changes a name's MEANING rather than retiring
# it: it used to show katom types (now --katoms).  A name-based guard cannot
# catch that -- the old spelling still works and does something else -- so
# these tests pin the new meaning down.
#
# Usage: ./kdesc_test.sh   (needs KLAMMERTEXT_HOME set; kdesc on PATH)
# Exit code: 0 if all tests pass, 1 otherwise.

PASS=0
FAIL=0
KDESC=kdesc
K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set}
DIR="$(cd "$(dirname "$0")" && pwd)/coverage"

red=$'\033[31m'
green=$'\033[32m'
bold=$'\033[1m'
reset=$'\033[0m'

plain() { sed 's/\x1b\[[0-9;]*m//g'; }

# GNU coreutils' "timeout" is NOT present on macOS, and Homebrew's is named
# "gtimeout", so a bare "timeout" made this suite fail WHOLESALE there -- every
# case, because the command never ran at all (found on Olion, 2026-08-16).  The
# guard is a safety net against a hung command, not part of what is being
# tested, so it is optional: bound the command where the tool exists, run it
# directly where it does not.
if command -v timeout >/dev/null 2>&1; then
    limited() { timeout 60 "$@"; }
elif command -v gtimeout >/dev/null 2>&1; then
    limited() { gtimeout 60 "$@"; }
else
    limited() { "$@"; }
fi

# shows NAME PATTERN CMD... — exits 0 and the output matches PATTERN.
shows() {
    local name="$1" pattern="$2"; shift 2
    local out status
    out=$(limited "$@" 2>&1 | plain); status=$?
    if [ $status -gt 128 ]; then
        echo "${red}FAIL${reset} $name — died by signal $((status-128))"; FAIL=$((FAIL+1)); return
    fi
    if printf '%s' "$out" | grep -Eq -- "$pattern"; then
        echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
    else
        echo "${red}FAIL${reset} $name — no line matching [$pattern]"
        echo "       got: $(printf '%s' "$out" | head -2)"; FAIL=$((FAIL+1))
    fi
}

# absent NAME PATTERN CMD... — the output does NOT match PATTERN.
absent() {
    local name="$1" pattern="$2"; shift 2
    if limited "$@" 2>&1 | plain | grep -Eq -- "$pattern"; then
        echo "${red}FAIL${reset} $name — unexpected match [$pattern]"; FAIL=$((FAIL+1))
    else
        echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
    fi
}

echo "${bold}kdesc interface tests${reset}"
echo "====================="
echo

echo "-- the single-letter flags --"
shows " 1. -k lists klammers"              '@table'            "$KDESC" -k
shows " 2. -t lists targets"               'html'              "$KDESC" -t
shows " 3. -c lists character codes"       '.'                 "$KDESC" -c
shows " 4. -i reads the named input"       '@x'                "$KDESC" -i "$DIR/split.k" -k

echo
echo "-- the multi-letter topics --"
shows " 5. --katoms lists katom types"     'katom'             "$KDESC" --katoms
shows " 6. --argtypes lists argtypes"      'fraction'          "$KDESC" --argtypes
shows " 7. --rewrite lists rewrites"       '.'                 "$KDESC" --rewrite
shows " 8. --optionsets lists sets"        'caption_args'      "$KDESC" --optionsets

echo
echo "-- result detail is a word, not a verbosity level --"
shows " 9. --katoms full adds the regex column"  'Regex'        "$KDESC" --katoms full
absent "10. --katoms alone omits it"             'Regex'        "$KDESC" --katoms
shows "11. --coverage all adds the file column"   'sks/table/table\.k' "$KDESC" --coverage all
absent "12. --coverage alone omits it"            'sks/table/table\.k' "$KDESC" --coverage
# "all" is one word for both kinds of missing information: if you want some of
# it you probably want all of it.  It shows an empty REPORTING category -- a
# designer's checklist of the shapes a klammer set can have -- but not an
# empty PROBLEM one, which would state the opposite of the "Needs attention"
# banner above it.
# Against a FIXTURE, not the SKS: which categories are empty there changes as
# the klammer set is worked on, and a test pinned to that state fails for a
# reason that has nothing to do with what it is testing.  (It did: adopting
# ".*" in the SKS made "All targets, declared" non-empty.)
shows  "12a. --coverage all shows an empty reporting category" \
       'All targets, declared \(0\)' "$KDESC" --klammersets none -i "$DIR/cycle.k" --coverage all
absent "12b. ... hidden without it" \
       'All targets, declared \(0\)' "$KDESC" --klammersets none -i "$DIR/cycle.k" --coverage
absent "12c. an empty problem category stays hidden" \
       'Declared but never defined \(0\)' "$KDESC" --klammersets none -i "$DIR/cycle.k" --coverage all
absent "13. -v adds no result detail"             'Regex'        "$KDESC" --katoms -v 3

echo
echo "-- -v is about processing only --"
shows "14. -v names the input it read"     'input_filenames'   "$KDESC" -i "$DIR/clean.k" -t -v 1
absent "15. ... and is silent without it"  'input_filenames'   "$KDESC" -i "$DIR/clean.k" -t

echo
echo "-- the klammer search --"
shows "16. a name matches"                 '@table'            "$KDESC" -k table
absent "17. ... and others are excluded"   '@document'         "$KDESC" -k table
shows "18. matching is case-insensitive"   '@table'            "$KDESC" -k TABLE
shows "19. a description word matches"     '@'                 "$KDESC" -k verbatim
# The words after -k are one phrase, and whitespace is collapsed on both
# sides -- a description written across several lines in a .k file must still
# match a phrase typed on one.
shows "20. several words are one phrase"   '@'                 "$KDESC" -k displayed verbatim
shows "21. ... with runs of space collapsed" '@'               "$KDESC" -k displayed    verbatim
shows "22. no match says so"  'No klammer names or descriptions contained "zzqq"\.' "$KDESC" -k zzqq
absent "23. ... and lists nothing"         '^  @'              "$KDESC" -k zzqq

echo
echo "-- the subcommand words --"
shows "24. --coverage help explains"       'Coverage commands'  "$KDESC" --coverage help
shows "25. --katoms help explains"         'Katom commands'     "$KDESC" --katoms help
shows "26. an unknown coverage word"       'Unrecognized coverage command' "$KDESC" --coverage nonsense
shows "27. an unknown katom word"          'Unrecognized katom command'    "$KDESC" --katoms nonsense

echo
echo "-- provenance: -i shows what the INPUT defines --"
# A klammerset is loaded so the input can be ANALYSED against it, but it is
# not what the user asked about.  A designer wants the klammers this file
# defines; a reader of an unfamiliar document wants the custom ones it
# carries.  So "-i" restricts every listing to definitions from outside the
# loaded klammersets -- while the klammersets stay loaded.
PROV=$(mktemp /tmp/kdesc_prov.XXXXXX.k)
printf '@@shout.k s : Say it loudly @@\n@@shout.html :: <b>*s*</b> @@\n' > "$PROV"
shows  "32. -i lists the input's own klammer"   '@shout'   "$KDESC" -i "$PROV" -k
absent "33. ... and not the klammerset's"       '@table'   "$KDESC" -i "$PROV" -k
shows  "34. without -i the klammerset is shown" '@table'   "$KDESC" -k
# The klammerset is still LOADED: a coverage derived from a klammer it
# supplies proves the analysis saw it even though the report does not.
DERIVE=$(mktemp /tmp/kdesc_derive.XXXXXX.k)
printf '@@wave.k s : Wave @@\n@@wave :: @i *s* @ @@\n' > "$DERIVE"
shows  "35. coverage is derived through the unlisted klammerset" \
       '@wave +html pdf tex txt +from @i' "$KDESC" -i "$DERIVE" --coverage
absent "36. ... and the klammerset is not reported" '@table' "$KDESC" -i "$DERIVE" --coverage
# A klammer the input REDEFINES came from the klammerset, but the input
# changed it -- which is exactly what a reader needs to know.
REDEF=$(mktemp /tmp/kdesc_redef.XXXXXX.k)
printf '@@i.html ::: <em class="mine">*text*</em> @@\n' > "$REDEF"
shows  "37. a redefined klammer is the input's too" '@i' "$KDESC" -i "$REDEF" -k
# An input that declares its own klammerset is still the input.
DECL=$(mktemp -d /tmp/kdesc_decl.XXXXXX)
mkdir -p "$DECL/own"
printf '@@@klammerset own | A designer set @@@\n@@yell.k s : Yell @@\n@@yell.html :: <b>*s*</b> @@\n' \
    > "$DECL/own/own.k"
shows  "38. an input declaring a klammerset is not filtered out" \
       '@yell' "$KDESC" -i "$DECL/own/own.k" -k
rm -rf "$PROV" "$DERIVE" "$REDEF" "$DECL"

echo
echo "-- the usage text --"
shows "28. -k shows its optional argument" '\-k \[<text>\]'    "$KDESC"
shows "29. -i shows its filename"          '\-i <filename>'    "$KDESC"
# Ordered by likely use: the single letters come before the long names.
# Line numbers, not a multi-line pattern -- grep is line-oriented.
usage=$(limited "$KDESC" 2>&1 | plain)
k_line=$(printf '%s\n' "$usage" | grep -n -- '-k \[<text>\]' | head -1 | cut -d: -f1)
katoms_line=$(printf '%s\n' "$usage" | grep -n -- '--katoms' | head -1 | cut -d: -f1)
if [ -n "$k_line" ] && [ -n "$katoms_line" ] && [ "$k_line" -lt "$katoms_line" ]; then
    echo "${green}PASS${reset} 30. -k is listed before --katoms"; PASS=$((PASS+1))
else
    echo "${red}FAIL${reset} 30. usage order: -k at line $k_line, --katoms at $katoms_line"
    FAIL=$((FAIL+1))
fi

echo
echo "-- a no-result search is not an error --"
limited "$KDESC" -k zzqq >/dev/null 2>&1
if [ $? -eq 0 ]; then
    echo "${green}PASS${reset} 31. finding nothing exits 0"; PASS=$((PASS+1))
else
    echo "${red}FAIL${reset} 31. finding nothing exits nonzero"; FAIL=$((FAIL+1))
fi

echo
echo "====================="
echo "Results: ${PASS} passed, ${FAIL} failed"
[ "$FAIL" -eq 0 ] || exit 1
exit 0
