Klammertext runs natively on Windows; a Windows source-install guide; the bundled sh for @eval :shell

Assembled from klammertext-dev f344a37d0f6e. The commands, the library,
the Standard Klammer Set, the reflection klammerset and the Python
binding build and run natively on Windows (MSYS2 UCRT64 gcc, python.org
Python), and both test tiers pass there. doc/install/windows_source_install.md
is the fifth install guide: a source install parallel to the Linux and
macOS ones, verified by following it on a pristine Windows 11.

Behaviour on every system: paths Klammertext prints use forward slashes;
output is LF; the command line is UTF-8 on Windows; @eval :shell is a
POSIX shell command everywhere -- on Windows it runs the sh Klammertext
ships, BusyBox for Windows (win/busybox/, GPLv2, with its licence and the
source pointer). KLAMMERTEXT_FONTS and KLAMMERTEXT_KLAMMERSETS are lists in
the system's PATH convention (';' on Windows). On Linux and macOS nothing
observable changes.
This commit is contained in:
ack committed 2026-09-25 00:32:55 +02:00
1 parent f9af1ff869
commit fe475e774a
55 files changed
+1182 -153

No files matched your search

+1 -1
View File
@@ -136,7 +136,7 @@ echo "-- the output policy: three categories, two streams --"
# For ktext, category 3 is a DOCUMENT that may be piped, so nothing else may
# share the stream. It did: K::log and msg() both wrote to stdout, so
# "ktext -d -v 1 > doc.txt" put the whole argument dump inside the document.
OUTF=$(mktemp /tmp/kout.XXXXXX); ERRF=$(mktemp /tmp/kerr.XXXXXX)
OUTF=$(mktemp "${TMPDIR:-/tmp}"/kout.XXXXXX); ERRF=$(mktemp "${TMPDIR:-/tmp}"/kerr.XXXXXX)
trap 'rm -f "$OUTF" "$ERRF"' EXIT
ktext --klammersets none -s 'hello' -d -v 1 > "$OUTF" 2> "$ERRF"
+2 -2
View File
@@ -118,7 +118,7 @@ check_error() {
FRAC='@@frac a | b : *a*/*b* @@'
# A file for the @read non-strictness cases (21-21b).
READ_FIXTURE=$(mktemp /tmp/cond_read.XXXXXX)
READ_FIXTURE=$(mktemp "${TMPDIR:-/tmp}"/cond_read.XXXXXX)
printf 'READ-FIXTURE\n' > "$READ_FIXTURE"
trap 'rm -f "$READ_FIXTURE"' EXIT
@@ -248,7 +248,7 @@ check_eq "21b. ... so a missing file in a discarded branch is not an error" "ok"
# An @eval side effect is the observable test: the branch either touched the
# file or it did not.
SIDE=$(mktemp -u /tmp/cond_side.XXXXXX)
SIDE=$(mktemp -u "${TMPDIR:-/tmp}"/cond_side.XXXXXX)
"$KTEXT" --klammersets none -s "@cond false | @eval :shell touch $SIDE @ | ok @" -d >/dev/null 2>&1
if [ -f "$SIDE" ]; then
echo "${red}FAIL${reset} 22. an @eval in a discarded branch ran"; FAIL=$((FAIL+1)); rm -f "$SIDE"
+8 -1
View File
@@ -59,7 +59,7 @@ fi
# string subscript then evaluates to 0, so all five fixtures overwrote one
# slot and the whole suite compared the wrong report against the wrong test.
# Files have no such floor and read the same on both systems.
REPORTS=$(mktemp -d /tmp/coverage_reports.XXXXXX)
REPORTS=$(mktemp -d "${TMPDIR:-/tmp}"/coverage_reports.XXXXXX)
trap 'rm -rf "$REPORTS"' EXIT
for f in basic undecidable cycle split clean; do
@@ -296,6 +296,12 @@ echo "-- the file column survives a symlinked KLAMMERTEXT_HOME --"
link=$(mktemp -u)/kt_home_link
mkdir -p "$(dirname "$link")"
ln -s "$K" "$link"
if [ ! -L "$link" ]; then
# MSYS2 on Windows makes ln -s a COPY unless winsymlinks is configured;
# a copy has no second spelling to test, so the case does not apply.
echo "SKIP 58. a symlinked KLAMMERTEXT_HOME still shortens the path (ln -s made a copy, not a link)"
rm -rf "$link"
else
out=$(KLAMMERTEXT_HOME="$link" "$link/bin/kdesc" --klammersets none \
-i "$link/tst/coverage/basic.k" --coverage all 2>&1 \
| grep -E '^ +@ab ')
@@ -308,6 +314,7 @@ case "$out" in
echo " got: [$out]"
FAIL=$((FAIL+1));;
esac
fi
echo
echo "====================="
+9 -6
View File
@@ -91,17 +91,20 @@ check() { # check NAME FILE_A FILE_B
}
# --- the shared core: API and CLI (required) -------------------------------
if ! command -v python3 >/dev/null 2>&1; then
echo "editor_test.sh: python3 not found" >&2
# The interpreter's name: python3 on Linux and macOS; on Windows python.org
# installs "python" only, and env/runtime.env exports PYTHON there.
PYTHON=${PYTHON:-python3}
if ! command -v "$PYTHON" >/dev/null 2>&1; then
echo "editor_test.sh: $PYTHON not found" >&2
exit 1
fi
if ! PYTHONDONTWRITEBYTECODE=1 python3 "$FIX/editor_driver.py" "$SHARED_DIR" "${py_args[@]}"; then
if ! PYTHONDONTWRITEBYTECODE=1 "$PYTHON" "$FIX/editor_driver.py" "$SHARED_DIR" "${py_args[@]}"; then
echo "editor_test.sh: the Python driver failed" >&2
exit 1
fi
# --- the Sublime adapters resolve to the shared core -----------------------
if PYTHONDONTWRITEBYTECODE=1 python3 -c "
if PYTHONDONTWRITEBYTECODE=1 "$PYTHON" -c "
import sys
sys.path.insert(0, '$SUBLIME_DIR')
import Klammertext, Klammertext_indent, Klammertext_align
@@ -116,7 +119,7 @@ else
fi
# --- the language server protocol test -------------------------------------
if PYTHONDONTWRITEBYTECODE=1 python3 "$FIX/ls_test.py" "$SHARED_DIR" "$FIX" >"$OUT/ls.log" 2>&1; then
if PYTHONDONTWRITEBYTECODE=1 "$PYTHON" "$FIX/ls_test.py" "$SHARED_DIR" "$FIX" >"$OUT/ls.log" 2>&1; then
echo "${green}PASS${reset} language server protocol ($(grep -c '^PASS' "$OUT/ls.log") checks)"
PASS=$((PASS + 1))
else
@@ -255,7 +258,7 @@ fi
mkdir -p "$OUT/vsix"
cp -R "$EDIT_DIR/vscode" "$EDIT_DIR/shared" "$OUT/vsix/"
if ( cd "$OUT/vsix/vscode" && ./make_vsix.sh >/dev/null 2>&1 ) && \
PYTHONDONTWRITEBYTECODE=1 python3 -c "
PYTHONDONTWRITEBYTECODE=1 "$PYTHON" -c "
import sys, zipfile
names = set(zipfile.ZipFile('$OUT/vsix/vscode/klammertext.vsix').namelist())
need = {'extension.vsixmanifest', '[Content_Types].xml',
+2 -2
View File
@@ -170,7 +170,7 @@ echo "-- and the converse: a # INSIDE literal content is CONTENT --"
# in the same string is not yet literal while that string is scanned. "-s" is
# processed before the input files, so this ordering is what a real document
# has -- the klammer set is loaded first.
LITUSE=$(mktemp /tmp/escape_lit.XXXXXX).kt
LITUSE=$(mktemp "${TMPDIR:-/tmp}"/escape_lit.XXXXXX).kt
printf '@lit a # b lit@\n' > "$LITUSE"
# "-d" is required here and nowhere else in this suite: with a FILE input ktext
# writes a file instead of displaying, so stdout would be empty.
@@ -191,7 +191,7 @@ echo "-- 25a-e: only a DECLARED option name opens an option region --"
# never wrote, in content promised to be read as nothing but text.
# OPT declares one option, :flag, so both directions can be asserted.
OPT='@@opt.k :flag.bool | t.literal : A literal klammer with an option @@ @@opt.t :: [*flag*/*t*] @@'
OPTUSE=$(mktemp /tmp/escape_opt.XXXXXX).kt
OPTUSE=$(mktemp "${TMPDIR:-/tmp}"/escape_opt.XXXXXX).kt
opt_case() { # opt_case NAME EXPECTED USE
printf '%s\n' "$3" > "$OPTUSE"
check_eq "$1" "$2" "$OPTUSE" --klammersets none -t t -d -s "$T $OPT"
+10 -3
View File
@@ -38,8 +38,8 @@ green=$'\033[32m'
bold=$'\033[1m'
reset=$'\033[0m'
OUTF=$(mktemp /tmp/eval_out.XXXXXX)
ERRF=$(mktemp /tmp/eval_err.XXXXXX)
OUTF=$(mktemp "${TMPDIR:-/tmp}"/eval_out.XXXXXX)
ERRF=$(mktemp "${TMPDIR:-/tmp}"/eval_err.XXXXXX)
trap 'rm -f "$OUTF" "$ERRF"' EXIT
plain() { sed 's/\x1b\[[0-9;]*m//g'; }
@@ -232,7 +232,14 @@ fi
# :haskell without runghc: strip PATH so the case is deterministic whether or
# not GHC is installed; ktext itself is invoked by absolute path (which works
# since the 2026-08-21 construct_command_pathname fix).
env PATH=/nonexistent "$K/bin/ktext" --klammersets none \
# A PATH without runghc. On Windows a program finds its DLLs through PATH,
# so there it keeps the directories of libklammertext.so and python312.dll
# -- neither holds a runghc, which is all the case needs.
NORUNGHC_PATH=/nonexistent
if command -v cygpath >/dev/null 2>&1; then
NORUNGHC_PATH="/nonexistent:$(cygpath -u "$K")/bin:$(dirname "$(command -v python)")"
fi
env PATH="$NORUNGHC_PATH" "$K/bin/ktext" --klammersets none \
-s '@eval :haskell main = putStrLn "x" @' -d >"$OUTF" 2>"$ERRF"
hstatus=$?
if [ $hstatus -ne 0 ] && tr '\n' ' ' < "$ERRF" | grep -qF "requires runghc"; then
+9
View File
@@ -117,10 +117,19 @@ check "2b. ... and reported at -v 1" \
"ALPHA" "interpreting \"my file.kt\" as one filename" \
my file.kt -d --klammersets none -v 1
case "$(uname -s)" in
MINGW*|MSYS*)
# The MSYS shell rewrites a bare "/" argument into the MSYS root
# (C:/msys64/) before a native program sees it; a Windows user in
# PowerShell or cmd is not affected, so the feature is not what fails.
echo "SKIP 3. standalone / separates a filename list (the MSYS shell rewrites a bare /)";;
*)
check "3. standalone / separates a filename list" \
"ALPHA
BETA" "" \
my file.kt / b.kt -d --klammersets none
;;
esac
check "4. space in a directory component" \
"GAMMA" "" \
+9 -6
View File
@@ -94,7 +94,10 @@ shows " 4a. -u describes the Unicode syntax" '\^0418\^' "$KDESC" -u
# The rendered glyph, as UTF-8 BYTE escapes: macOS ships bash 3.2, which
# expands $'\xHH' but not $'\U...' (that difference failed this suite's
# sibling on Olion, 2026-08-23).
shows " 4b. ... and shows the characters" $'\xf0\x93\x8a\xaa' "$KDESC" -u
# Byte semantics for this one grep: MSYS2's grep under its UTF-8 locale does
# not match a four-byte character that the C locale matches. Only here --
# under LC_ALL=C a ".{72,}" width test (10a) would count bytes, not characters.
LC_ALL=C shows " 4b. ... and shows the characters" $'\xf0\x93\x8a\xaa' "$KDESC" -u
echo
echo "-- the multi-letter topics --"
@@ -174,7 +177,7 @@ shows "27b. an unknown typeface word names the flag" 'Unrecognized typeface comm
# ~/.klammertext/fonts yet, so an error there would fail every new user's
# first "kdesc --typeface". A typeface an earlier directory also holds is
# shadowed, and the LATER row says by which directory.
FONTS=$(mktemp -d /tmp/kdesc_fonts.XXXXXX)
FONTS=$(mktemp -d "${TMPDIR:-/tmp}"/kdesc_fonts.XXXXXX)
shows "27c. --typeface lists the default set" 'Klammertext default typefaces' "$KDESC" --typeface
shows "27d. a missing search directory is a row" 'directory does not exist' \
env KLAMMERTEXT_FONTS="$FONTS/none" "$KDESC" --typeface
@@ -201,25 +204,25 @@ echo "-- provenance: -i shows what the INPUT defines --"
# 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)
PROV=$(mktemp "${TMPDIR:-/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)
DERIVE=$(mktemp "${TMPDIR:-/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 +ansi 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)
REDEF=$(mktemp "${TMPDIR:-/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)
DECL=$(mktemp -d "${TMPDIR:-/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"
+1 -1
View File
@@ -139,7 +139,7 @@ GREET='@@greet name : Hello, *name*. @@ @greet World @'
OWNTARGET='@@@target foo | Foo output @@@ @@bar.foo : B @@ text'
# A file for --read to pull in.
READFIXTURE=$(mktemp /tmp/kdiag_read.XXXXXX)
READFIXTURE=$(mktemp "${TMPDIR:-/tmp}"/kdiag_read.XXXXXX)
printf 'READFILE\n' > "$READFIXTURE"
trap 'rm -f "$READFIXTURE"' EXIT
+3 -3
View File
@@ -247,8 +247,8 @@ check_fails "11. invalid symbol rejected" \
# Runtime fixtures: a document directory holding a local klammerset, and a
# separate directory serving as a KLAMMERTEXT_KLAMMERSETS stage.
DOCDIR=$(mktemp -d /tmp/klammerset_doc.XXXXXX)
ENVDIR=$(mktemp -d /tmp/klammerset_env.XXXXXX)
DOCDIR=$(mktemp -d "${TMPDIR:-/tmp}"/klammerset_doc.XXXXXX)
ENVDIR=$(mktemp -d "${TMPDIR:-/tmp}"/klammerset_env.XXXXXX)
trap 'rm -rf "$DOCDIR" "$ENVDIR" /tmp/klammerset_test_err.$$' EXIT
mkdir -p "$DOCDIR/locset" "$ENVDIR/envset" "$ENVDIR/locset"
@@ -416,7 +416,7 @@ check_eq "23c. ... as is naming the same set twice on the command line" \
# "kdesc --klammersets" could not be a complete answer. It could:
# the embedded Python shares the process, so an @eval doing os.environ
# changed what a later getenv returned. The environment is now read once.
LATE=$(mktemp -d /tmp/klammerset_late.XXXXXX)
LATE=$(mktemp -d "${TMPDIR:-/tmp}"/klammerset_late.XXXXXX)
mkdir -p "$LATE/late"
printf '@@@klammerset late | Appeared mid-run @@@\n@@@target lt | T @@@\n@@l.lt : LATE @@\n' \
> "$LATE/late/late.k"
+11 -8
View File
@@ -39,12 +39,15 @@ green=$'\033[32m'
bold=$'\033[1m'
reset=$'\033[0m'
OUTF=$(mktemp /tmp/kpy_out.XXXXXX)
ERRF=$(mktemp /tmp/kpy_err.XXXXXX)
WORK=$(mktemp -d /tmp/kpy_work.XXXXXX)
OUTF=$(mktemp "${TMPDIR:-/tmp}"/kpy_out.XXXXXX)
ERRF=$(mktemp "${TMPDIR:-/tmp}"/kpy_err.XXXXXX)
WORK=$(mktemp -d "${TMPDIR:-/tmp}"/kpy_work.XXXXXX)
trap 'rm -rf "$OUTF" "$ERRF" "$WORK"' EXIT
export PYTHONPATH="$K/kpy${PYTHONPATH:+:$PYTHONPATH}"
# Python splits PYTHONPATH on the system's list separator: ':' on Linux and
# macOS, ';' on Windows (an MSYS2 shell runs a native python.org Python).
if command -v cygpath >/dev/null 2>&1; then sep=';'; else sep=':'; fi
export PYTHONPATH="$K/kpy${PYTHONPATH:+$sep$PYTHONPATH}"
# A DEBUG=1 build instruments the module with AddressSanitizer, and an
# instrumented extension imported into an uninstrumented python needs the
@@ -57,9 +60,9 @@ export PYTHONPATH="$K/kpy${PYTHONPATH:+:$PYTHONPATH}"
# the engine throws dies with "CHECK failed: asan_interceptors.cpp ...
# real___cxa_throw != 0" (every error case of this suite, 2026-09-08).
# Linux only (LD_PRELOAD); macOS ASan runs are not supported here.
MODULE=$(ls "$K"/kpy/klammertext.*.so 2>/dev/null | head -1)
MODULE=$(ls "$K"/kpy/klammertext.*.so "$K"/kpy/klammertext.*.pyd 2>/dev/null | head -1)
if [ -z "$MODULE" ]; then
echo "${red}FAIL${reset} kpy/ is not built (no kpy/klammertext.*.so)"
echo "${red}FAIL${reset} kpy/ is not built (no kpy/klammertext.*.so or .pyd)"
exit 1
fi
if nm -D "$MODULE" 2>/dev/null | grep -q '__asan_init'; then
@@ -200,7 +203,7 @@ import os, klammertext as kt
M = kt.Machine(klammersets="none", target="txt", files=["doc.kt"])
M.read_files()
out = M.output_filename
print("files", M.apply(), "|", os.path.basename(out), "|", os.path.dirname(out) == os.getcwd())
print("files", M.apply(), "|", os.path.basename(out), "|", os.path.normpath(os.path.dirname(out)) == os.getcwd())
EOF
expect " 7. files= and read_files(); output_filename beside the input" 'files hello from a file \| doc.txt \| True'
@@ -219,7 +222,7 @@ import os, klammertext as kt
M = kt.Machine(klammersets="none", target="txt", files=["doc.kt"], output="out/renamed")
M.read_file("doc.kt")
M.apply()
print("output", os.path.relpath(M.write()), os.path.exists("out/renamed.txt"))
print("output", os.path.relpath(M.write()).replace(os.sep, "/"), os.path.exists("out/renamed.txt"))
EOF
expect " 9. output= is -o: directory plus basename" 'output out/renamed.txt True'
+7
View File
@@ -38,6 +38,10 @@ reset=$'\033[0m'
# same directory and fail on the Mac only. `pwd -P` is POSIX; macOS has no
# GNU realpath by default.
T="$(cd "$(mktemp -d)" && pwd -P)"
# Under MSYS2 the physical path comes back in POSIX form (/tmp/...), which a
# native ktext cannot open; the Windows spelling is what the fixtures and the
# expectations must carry.
command -v cygpath >/dev/null 2>&1 && T="$(cygpath -m "$T")"
trap 'rm -rf "$T"' EXIT
mkdir -p "$T/seta" "$T/setb" "$T/elsewhere"
@@ -51,6 +55,9 @@ check() { # check NAME EXPECTED ARGS...
local name="$1" expected="$2"; shift 2
local out
out=$(ktext "$@" --klammersets none -d 2>/dev/null)
# A working directory Python reports on Windows has backslashes; the
# expectation, built from the shell's Windows-form path, has slashes.
case "$(uname -s)" in MINGW*|MSYS*) out=${out//\\//};; esac
if [ "$out" = "$expected" ]; then
echo "${green}PASS${reset} $name"; PASS=$((PASS + 1))
else
+3 -3
View File
@@ -38,9 +38,9 @@ green=$'\033[32m'
bold=$'\033[1m'
reset=$'\033[0m'
OUTF=$(mktemp /tmp/verb_out.XXXXXX)
ERRF=$(mktemp /tmp/verb_err.XXXXXX)
TMPD=$(mktemp -d /tmp/verb_dir.XXXXXX)
OUTF=$(mktemp "${TMPDIR:-/tmp}"/verb_out.XXXXXX)
ERRF=$(mktemp "${TMPDIR:-/tmp}"/verb_err.XXXXXX)
TMPD=$(mktemp -d "${TMPDIR:-/tmp}"/verb_dir.XXXXXX)
trap 'rm -f "$OUTF" "$ERRF"; rm -rf "$TMPD"' EXIT
plain() { sed 's/\x1b\[[0-9;]*m//g'; }