feat(argtype): :alone - the value of an option written without one
An optional argument has three values: the default (the name is absent), the argument type's :alone value (the name is written alone), and a written value. :alone is declared by the argument type only, never by a klammer's parameter declaration -- a default is what one klammer means by silence, but a bare option name must read the same way in every klammer. The bool type declares :alone true, which is the whole of the convention that a bare boolean option means true; there is no boolean special case in the engine. A type whose pattern matches running text cannot declare :alone, since an option's value runs to the next bar or option name and would swallow the following text. kdesc and `ktext -m` now show [default: X] and [alone: Y] per argument type. In the Standard Klammer Set: @code :number becomes a bool (it was an untyped string tested only for truthiness, so a bare :number was a no-op); decimal_mark declares :alone comma; table_hline and table_vline declare :alone all. The 35 bools that default false gained the bare form for free. Tests: tst/alone_test.sh (21 cases) joins the shipped suite. (from dev 6024f49c2859)
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
# Klammertext distribution test suite (subset).
|
||||
#
|
||||
# Runs the six shell regression suites:
|
||||
# Runs the seven shell regression suites:
|
||||
# cond_test.sh — @cond argument delimitation
|
||||
# deftype_test.sh — the four klammer definition modes + redefinition table
|
||||
# escape_test.sh — target character escaping and quoted specials
|
||||
# filename_test.sh — filenames with spaces (quoting, " / " lists, rescue)
|
||||
# alone_test.sh — an optional argument's three values (default, the
|
||||
# argument type's :alone value, a written value)
|
||||
# modulepath_test.sh — @eval finds modules beside the file that names them
|
||||
# editor_test.sh — editor support (doc/edit): indentation and table
|
||||
# alignment; needs python3, uses Emacs when installed
|
||||
@@ -17,5 +19,6 @@ test:
|
||||
./deftype_test.sh
|
||||
./escape_test.sh
|
||||
./filename_test.sh
|
||||
./alone_test.sh
|
||||
./modulepath_test.sh
|
||||
./editor_test.sh
|
||||
|
||||
240
tst/alone_test.sh
Executable file
240
tst/alone_test.sh
Executable file
@@ -0,0 +1,240 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# alone_test.sh — Regression tests for an argument type's :alone value.
|
||||
#
|
||||
# An optional argument has three possible values, not two:
|
||||
#
|
||||
# 1. the option name is not written at all -> the default
|
||||
# 2. the name is written alone, with no value -> the type's :alone value
|
||||
# 3. the name is written with a value -> that value
|
||||
#
|
||||
# The :alone value is declared by the argument type (@@@argtype ... :alone),
|
||||
# never by a klammer's parameter declaration. A default is what a klammer
|
||||
# means by silence and is properly per-klammer; a bare option name must read
|
||||
# the same way in every klammer, or a writer cannot know what it means
|
||||
# without consulting each signature.
|
||||
#
|
||||
# The bool type declares :alone true, which is where the convention that a
|
||||
# bare boolean option means true comes from. This is a declaration like any
|
||||
# other type's, not an engine special case for booleans.
|
||||
#
|
||||
# A type whose pattern matches running text cannot declare an :alone value:
|
||||
# an option's value runs to the next bar or option name, so ":opt some words"
|
||||
# would take "some words" as the value and the alone value would never be
|
||||
# reached. That is a definition-time error.
|
||||
#
|
||||
# Engine tier: these tests run with -k none and define their own argtypes
|
||||
# and klammers inline, so they do not depend on the Standard Klammer Set.
|
||||
#
|
||||
# Usage: ./alone_test.sh (LSan suppressions come from env/runtime.env)
|
||||
# Exit code: 0 if all tests pass, 1 otherwise.
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
KTEXT=ktext
|
||||
K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set}
|
||||
|
||||
red=$'\033[31m'
|
||||
green=$'\033[32m'
|
||||
bold=$'\033[1m'
|
||||
reset=$'\033[0m'
|
||||
|
||||
# strip leading/trailing blank lines and surrounding whitespace
|
||||
trim() { awk '{ sub(/[ \t\r]+$/, "") } { line[NR]=$0 } END { f=1; while (f<=NR && line[f]=="") f++; l=NR; while (l>=1 && line[l]=="") l--; for (i=f;i<=l;i++) print line[i] }'; }
|
||||
|
||||
# check_eq TEST_NAME EXPECTED KTEXT_ARGS...
|
||||
# Runs ktext, expects exit status 0, and compares trimmed stdout to EXPECTED.
|
||||
check_eq() {
|
||||
local test_name="$1"
|
||||
local expected="$2"
|
||||
shift 2
|
||||
|
||||
local output status
|
||||
output=$("$KTEXT" "$@" 2>/tmp/alone_test_err.$$)
|
||||
status=$?
|
||||
output=$(printf '%s' "$output" | trim)
|
||||
|
||||
if [ $status -ne 0 ]; then
|
||||
echo "${red}FAIL${reset} $test_name — ktext exited $status"
|
||||
echo " stderr: $(head -3 /tmp/alone_test_err.$$)"
|
||||
FAIL=$((FAIL + 1))
|
||||
return
|
||||
fi
|
||||
if [ "$output" = "$expected" ]; then
|
||||
echo "${green}PASS${reset} $test_name"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "${red}FAIL${reset} $test_name"
|
||||
echo " expected: [$expected]"
|
||||
echo " got: [$output]"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# check_error TEST_NAME PATTERN KTEXT_ARGS...
|
||||
# Runs ktext, expects a NONZERO exit status and PATTERN in the message.
|
||||
check_error() {
|
||||
local test_name="$1"
|
||||
local pattern="$2"
|
||||
shift 2
|
||||
|
||||
local output status
|
||||
output=$("$KTEXT" "$@" 2>&1)
|
||||
status=$?
|
||||
|
||||
if [ $status -eq 0 ]; then
|
||||
echo "${red}FAIL${reset} $test_name — expected an error but ktext succeeded"
|
||||
FAIL=$((FAIL + 1))
|
||||
return
|
||||
fi
|
||||
if echo "$output" | grep -qF "$pattern"; then
|
||||
echo "${green}PASS${reset} $test_name"
|
||||
PASS=$((PASS + 1))
|
||||
else
|
||||
echo "${red}FAIL${reset} $test_name — expected error to contain [$pattern]"
|
||||
echo " output: $(echo "$output" | head -5)"
|
||||
FAIL=$((FAIL + 1))
|
||||
fi
|
||||
}
|
||||
|
||||
# A type with both a default and an alone value, and a klammer using it.
|
||||
DEPTH='@@@argtype depth | a table of contents depth :pattern \d+ :default 0 :alone 3 @@@'
|
||||
DK='@@d :n.depth : [*n*] @@'
|
||||
|
||||
# A type with an alone value but no default.
|
||||
MARK='@@@argtype mark | a mark character :pattern [-*+] :alone * @@@'
|
||||
MK='@@m :c.mark : [*c*] @@'
|
||||
|
||||
echo "${bold}Argument type :alone value tests${reset}"
|
||||
echo "================================"
|
||||
echo
|
||||
|
||||
# --- The three values of an optional argument ---
|
||||
|
||||
check_eq \
|
||||
" 1. name absent — the default" \
|
||||
"[0]" \
|
||||
-k none -s "$DEPTH $DK @d@" -d
|
||||
|
||||
check_eq \
|
||||
" 2. name written alone — the type's alone value" \
|
||||
"[3]" \
|
||||
-k none -s "$DEPTH $DK @d :n @" -d
|
||||
|
||||
check_eq \
|
||||
" 3. name written with a value — that value" \
|
||||
"[7]" \
|
||||
-k none -s "$DEPTH $DK @d :n 7 @" -d
|
||||
|
||||
# --- A type with an alone value but no default ---
|
||||
|
||||
check_eq \
|
||||
" 4. no default declared — absent is empty" \
|
||||
"[]" \
|
||||
-k none -s "$MARK $MK @m@" -d
|
||||
|
||||
check_eq \
|
||||
" 5. no default declared — alone still applies" \
|
||||
"[*]" \
|
||||
-k none -s "$MARK $MK @m :c @" -d
|
||||
|
||||
# --- The alone value belongs to the type, so every parameter of that
|
||||
# type gets it, and a parameter default does not disturb it ---
|
||||
|
||||
check_eq \
|
||||
" 6. two parameters of one type share the alone value" \
|
||||
"[3][3]" \
|
||||
-k none -s "$DEPTH @@d2 :a.depth :b.depth : [*a*][*b*] @@ @d2 :a :b @" -d
|
||||
|
||||
check_eq \
|
||||
" 7. a parameter default overrides the type default, not the alone value" \
|
||||
"[5]|[3]" \
|
||||
-k none -s "$DEPTH @@d3 :n.depth 5 : [*n*] @@ @d3@|@d3 :n @" -d
|
||||
|
||||
# --- bool: the convention that a bare boolean option means true ---
|
||||
|
||||
check_eq \
|
||||
" 8. bool written alone is true" \
|
||||
"[true]" \
|
||||
-k none -s '@@b :f.bool : [*f*] @@ @b :f @' -d
|
||||
|
||||
check_eq \
|
||||
" 9. bool written with false stays false" \
|
||||
"[false]" \
|
||||
-k none -s '@@b :f.bool : [*f*] @@ @b :f false @' -d
|
||||
|
||||
check_eq \
|
||||
"10. bool absent with no default is empty" \
|
||||
"[]" \
|
||||
-k none -s '@@b :f.bool : [*f*] @@ @b@' -d
|
||||
|
||||
check_eq \
|
||||
"11. bool absent with a true parameter default" \
|
||||
"[true]" \
|
||||
-k none -s '@@b :f.bool true : [*f*] @@ @b@' -d
|
||||
|
||||
check_eq \
|
||||
"12. bool false explicitly against a true default" \
|
||||
"[false]" \
|
||||
-k none -s '@@b :f.bool true : [*f*] @@ @b :f false @' -d
|
||||
|
||||
# --- The value reaching @eval ---
|
||||
|
||||
check_eq \
|
||||
"13. python value of a bool written alone" \
|
||||
"True" \
|
||||
-k none -s '@@b :f.bool : @eval repr(K.f) @ @@ @b :f @' -d
|
||||
|
||||
check_eq \
|
||||
"14. python value of an absent bool" \
|
||||
"None" \
|
||||
-k none -s '@@b :f.bool : @eval repr(K.f) @ @@ @b@' -d
|
||||
|
||||
check_eq \
|
||||
"15. python value of a user type written alone" \
|
||||
"'3'" \
|
||||
-k none -s "$DEPTH @@d4 :n.depth : @eval repr(K.n) @ @@ @d4 :n @" -d
|
||||
|
||||
# --- Types that cannot delimit a bare option name ---
|
||||
|
||||
check_error \
|
||||
"16. :alone refused on a type matching running text" \
|
||||
"cannot declare an :alone value" \
|
||||
-k none -s '@@@argtype loose | anything at all :alone x @@@' -d
|
||||
|
||||
check_error \
|
||||
"17. :alone refused on an explicit match-everything pattern" \
|
||||
"cannot declare an :alone value" \
|
||||
-k none -s '@@@argtype loose | anything :pattern (?:.^|\n)* :alone x @@@' -d
|
||||
|
||||
check_error \
|
||||
"18. an alone value must match its own type's pattern" \
|
||||
"does not match its own pattern" \
|
||||
-k none -s '@@@argtype depth | a depth :pattern \d+ :alone many @@@' -d
|
||||
|
||||
# --- Delimitation: an option value still runs to the next bar or option
|
||||
# name, so text after a bare name is taken as the value and rejected
|
||||
# by the type. This is what makes the guard above necessary.
|
||||
|
||||
check_error \
|
||||
"19. text after a bare option name is taken as its value" \
|
||||
"does not match" \
|
||||
-k none -s "$DEPTH $DK @d :n some words @" -d
|
||||
|
||||
check_eq \
|
||||
"20. a bar separates a bare option name from following text" \
|
||||
"[3]two" \
|
||||
-k none -s "$DEPTH @@d5 :n.depth | t : [*n*]*t* @@ @d5 :n | two @" -d
|
||||
|
||||
# --- A type with no alone value is unchanged: a bare name is empty ---
|
||||
|
||||
check_eq \
|
||||
"21. bare option of a type with no alone value is empty" \
|
||||
"[]" \
|
||||
-k none -s '@@s :t.word : [*t*] @@ @s :t @' -d
|
||||
|
||||
echo
|
||||
echo "================================"
|
||||
echo "Passed: $PASS Failed: $FAIL"
|
||||
rm -f /tmp/alone_test_err.$$
|
||||
[ $FAIL -eq 0 ]
|
||||
Reference in New Issue
Block a user