diff --git a/README.md b/README.md index 9baac9e..04c7bc1 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ are regenerated on each release — patches cannot be merged directly. Report problems (or send patches) to the author; accepted changes are applied to the development tree and appear in a following snapshot. -This snapshot was assembled from development commit `6d767149c26c`. +This snapshot was assembled from development commit `4e8c3db2882d`. ## License diff --git a/tst/Makefile b/tst/Makefile index bed511c..2d7af5d 100644 --- a/tst/Makefile +++ b/tst/Makefile @@ -1,10 +1,12 @@ # Klammertext distribution test suite (subset). # -# Runs the four shell regression suites: +# Runs the five 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) +# editor_test.sh — editor support (doc/edit): indentation and table +# alignment; needs python3, uses Emacs when installed # # Requires KLAMMERTEXT_HOME set and `ktext` on PATH (build it with `make -C com`). @@ -14,3 +16,4 @@ test: ./deftype_test.sh ./escape_test.sh ./filename_test.sh + ./editor_test.sh diff --git a/tst/editor/align_boundary.kt b/tst/editor/align_boundary.kt new file mode 100644 index 0000000..bafef2f --- /dev/null +++ b/tst/editor/align_boundary.kt @@ -0,0 +1,5 @@ +@table + 123456789012345678901234567890 | x || + abcdefghijklmnopqrstuvwxyzabcde | y || + short | z || +@ diff --git a/tst/editor/align_boundary_expected.kt b/tst/editor/align_boundary_expected.kt new file mode 100644 index 0000000..8457c10 --- /dev/null +++ b/tst/editor/align_boundary_expected.kt @@ -0,0 +1,5 @@ +@table + 123456789012345678901234567890 | x || + abcdefghijklmnopqrstuvwxyzabcde | y || + short | z || +@ diff --git a/tst/editor/align_colspan.kt b/tst/editor/align_colspan.kt new file mode 100644 index 0000000..240c253 --- /dev/null +++ b/tst/editor/align_colspan.kt @@ -0,0 +1,4 @@ +@table + one | two | three || + spanning | wide || +@ diff --git a/tst/editor/align_colspan_expected.kt b/tst/editor/align_colspan_expected.kt new file mode 100644 index 0000000..185a7bc --- /dev/null +++ b/tst/editor/align_colspan_expected.kt @@ -0,0 +1,4 @@ +@table + one | two | three || + spanning | wide || +@ diff --git a/tst/editor/align_empty_cells.kt b/tst/editor/align_empty_cells.kt new file mode 100644 index 0000000..c13ecc2 --- /dev/null +++ b/tst/editor/align_empty_cells.kt @@ -0,0 +1,4 @@ +@table + a | | b || + cc | dd | ee || +@ diff --git a/tst/editor/align_empty_cells_expected.kt b/tst/editor/align_empty_cells_expected.kt new file mode 100644 index 0000000..050785c --- /dev/null +++ b/tst/editor/align_empty_cells_expected.kt @@ -0,0 +1,4 @@ +@table + a | | b || + cc | dd | ee || +@ diff --git a/tst/editor/align_escapes.kt b/tst/editor/align_escapes.kt new file mode 100644 index 0000000..bfde32e --- /dev/null +++ b/tst/editor/align_escapes.kt @@ -0,0 +1,4 @@ +@table + a ^| b | c || + dd | ee || +@ diff --git a/tst/editor/align_escapes_expected.kt b/tst/editor/align_escapes_expected.kt new file mode 100644 index 0000000..11685d5 --- /dev/null +++ b/tst/editor/align_escapes_expected.kt @@ -0,0 +1,4 @@ +@table + a ^| b | c || + dd | ee || +@ diff --git a/tst/editor/align_mixed.kt b/tst/editor/align_mixed.kt new file mode 100644 index 0000000..f0d2808 --- /dev/null +++ b/tst/editor/align_mixed.kt @@ -0,0 +1,12 @@ +Some text before. +@table :caption Test + First item | Second | A third item that's longer || + Row 2 | Text | Not as long || + :column_width f f f + A cell that is way way too long to align nicely here | x | y || + Multi | line cell + continues here | z || + @b bold @ | @frac 1 | 2 @ || + Last | row | here +@ +After. diff --git a/tst/editor/align_mixed_expected.kt b/tst/editor/align_mixed_expected.kt new file mode 100644 index 0000000..ffabb28 --- /dev/null +++ b/tst/editor/align_mixed_expected.kt @@ -0,0 +1,12 @@ +Some text before. +@table :caption Test + First item | Second | A third item that's longer || + Row 2 | Text | Not as long || + :column_width f f f + A cell that is way way too long to align nicely here | x | y || + Multi | line cell + continues here | z || + @b bold @ | @frac 1 | 2 @ || + Last | row | here +@ +After. diff --git a/tst/editor/align_too_wide.kt b/tst/editor/align_too_wide.kt new file mode 100644 index 0000000..4008ab9 --- /dev/null +++ b/tst/editor/align_too_wide.kt @@ -0,0 +1,4 @@ +@table + xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx || + xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx || +@ diff --git a/tst/editor/align_too_wide_expected.kt b/tst/editor/align_too_wide_expected.kt new file mode 100644 index 0000000..4008ab9 --- /dev/null +++ b/tst/editor/align_too_wide_expected.kt @@ -0,0 +1,4 @@ +@table + xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx || + xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx | xxxxxxxxxxxxxxxxxxxxxxxxxxxx || +@ diff --git a/tst/editor/editor_driver.el b/tst/editor/editor_driver.el new file mode 100644 index 0000000..f1c267d --- /dev/null +++ b/tst/editor/editor_driver.el @@ -0,0 +1,30 @@ +;;; editor_driver.el --- drive the Emacs editor units over fixtures -*- lexical-binding: t; -*- + +;; Usage: +;; emacs --batch -L EMACSDIR -l klammertext-mode -l klammertext-indent +;; -l klammertext-align -l editor_driver.el MODE INFILE OUTFILE ... +;; +;; MODE is "indent" or "align"; each triple applies that tool to INFILE and +;; writes the result to OUTFILE. Called by editor_test.sh. + +(let ((args command-line-args-left)) + (setq command-line-args-left nil) ; keep batch Emacs from visiting them + (while args + (let ((mode (pop args)) + (in (pop args)) + (out (pop args))) + (with-temp-buffer + (insert-file-contents in) + (klammertext-mode) + (cond + ((string= mode "indent") + (indent-region (point-min) (point-max))) + ((string= mode "align") + (goto-char (point-min)) + (when (search-forward "|" nil t) + (goto-char (1- (point)))) + (klammertext-align-table)) + (t (error "editor_driver.el: unknown mode %s" mode))) + (write-region (point-min) (point-max) out nil 'silent))))) + +;;; editor_driver.el ends here diff --git a/tst/editor/editor_driver.py b/tst/editor/editor_driver.py new file mode 100644 index 0000000..76b26e6 --- /dev/null +++ b/tst/editor/editor_driver.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +"""Drive the Sublime Text editor cores over fixture files, outside Sublime. + +Usage: editor_driver.py SUBLIME_DIR (MODE INFILE OUTFILE)... + +MODE is `indent` or `align`; each triple applies that tool to INFILE and +writes the result to OUTFILE. The Sublime plugin files import without the +`sublime` module (their try/except guard), so the pure cores run under plain +python3. Also asserts the built-in error path (no enclosing table). +Called by editor_test.sh; exits nonzero on an internal error. +""" + +import sys + + +def apply_indent(KI, s): + bols = [0] + [i + 1 for i, ch in enumerate(s) + if ch == '\n' and i + 1 < len(s)] + out = s + for a, b, new in sorted(KI.reindent_lines(s, bols), reverse=True): + out = out[:a] + new + out[b:] + return out + + +def apply_align(KA, s): + caret = s.index('|') if '|' in s else 0 + span = KA.enclosing_span(s, caret, KA.ALIGN_KLAMMERS) + if span is None: + return s + _name, cs, ce = span + edits, _msg = KA.compute_edits(s[cs:ce]) + out = s + for a, b, new in sorted(edits, reverse=True): + out = out[:cs + a] + new + out[cs + b:] + return out + + +def main(): + sublime_dir = sys.argv[1] + sys.path.insert(0, sublime_dir) + import Klammertext_indent as KI + import Klammertext_align as KA + + args = sys.argv[2:] + for k in range(0, len(args), 3): + mode, infile, outfile = args[k:k + 3] + with open(infile) as f: + s = f.read() + if mode == 'indent': + out = apply_indent(KI, s) + elif mode == 'align': + out = apply_align(KA, s) + else: + sys.exit("editor_driver.py: unknown mode: " + mode) + with open(outfile, 'w') as f: + f.write(out) + + # Error path: no enclosing table klammer. + assert KA.enclosing_span("no table here\n", 3, KA.ALIGN_KLAMMERS) is None + + +if __name__ == '__main__': + main() diff --git a/tst/editor/indent_defs.kt b/tst/editor/indent_defs.kt new file mode 100644 index 0000000..3d392b0 --- /dev/null +++ b/tst/editor/indent_defs.kt @@ -0,0 +1,12 @@ +@@note.k s :label Note :color 1.0,1.0,0.9 +:level.int 0 + : Rectangular block for a special note @@ + + @@quote.txt :: +@eval block.block_indent(K) eval@ + @@ + +@@@state Word_max +:desc Maximum words + :value 7 + @@@ diff --git a/tst/editor/indent_defs_expected.kt b/tst/editor/indent_defs_expected.kt new file mode 100644 index 0000000..50b8475 --- /dev/null +++ b/tst/editor/indent_defs_expected.kt @@ -0,0 +1,12 @@ +@@note.k s :label Note :color 1.0,1.0,0.9 + :level.int 0 + : Rectangular block for a special note @@ + +@@quote.txt :: + @eval block.block_indent(K) eval@ +@@ + +@@@state Word_max + :desc Maximum words + :value 7 +@@@ diff --git a/tst/editor/indent_document.kt b/tst/editor/indent_document.kt new file mode 100644 index 0000000..39355ca --- /dev/null +++ b/tst/editor/indent_document.kt @@ -0,0 +1,10 @@ +@document :structure plain :text + This is a normal paragraph, at the left margin. + + @ol +One + | Two + @ + + More text after the list. + @ diff --git a/tst/editor/indent_document_expected.kt b/tst/editor/indent_document_expected.kt new file mode 100644 index 0000000..bfd8c44 --- /dev/null +++ b/tst/editor/indent_document_expected.kt @@ -0,0 +1,10 @@ +@document :structure plain :text +This is a normal paragraph, at the left margin. + +@ol + One +| Two +@ + +More text after the list. +@ diff --git a/tst/editor/indent_escapes.kt b/tst/editor/indent_escapes.kt new file mode 100644 index 0000000..3fdc984 --- /dev/null +++ b/tst/editor/indent_escapes.kt @@ -0,0 +1,5 @@ +@ol + Item one + | ^| a literal bar item +^@ a literal at line + @ diff --git a/tst/editor/indent_escapes_expected.kt b/tst/editor/indent_escapes_expected.kt new file mode 100644 index 0000000..8f11d5e --- /dev/null +++ b/tst/editor/indent_escapes_expected.kt @@ -0,0 +1,5 @@ +@ol + Item one +| ^| a literal bar item + ^@ a literal at line +@ diff --git a/tst/editor/indent_list.kt b/tst/editor/indent_list.kt new file mode 100644 index 0000000..9e73868 --- /dev/null +++ b/tst/editor/indent_list.kt @@ -0,0 +1,11 @@ +@ol +Item one + | Item two +| Item three + @ol +Embedded item one +| Embedded item two + @ + | Item four +| Item five + @ diff --git a/tst/editor/indent_list_expected.kt b/tst/editor/indent_list_expected.kt new file mode 100644 index 0000000..555d47a --- /dev/null +++ b/tst/editor/indent_list_expected.kt @@ -0,0 +1,11 @@ +@ol + Item one +| Item two +| Item three + @ol + Embedded item one + | Embedded item two + @ +| Item four +| Item five +@ diff --git a/tst/editor/indent_named_close.kt b/tst/editor/indent_named_close.kt new file mode 100644 index 0000000..eaa44ba --- /dev/null +++ b/tst/editor/indent_named_close.kt @@ -0,0 +1,10 @@ +@document :structure plain :text + Text at the margin. + @quote +Quoted text. + quote@ + document@ + + @@mydef arg : +Body of the definition. + mydef@@ diff --git a/tst/editor/indent_named_close_expected.kt b/tst/editor/indent_named_close_expected.kt new file mode 100644 index 0000000..829f817 --- /dev/null +++ b/tst/editor/indent_named_close_expected.kt @@ -0,0 +1,10 @@ +@document :structure plain :text +Text at the margin. +@quote + Quoted text. +quote@ +document@ + +@@mydef arg : + Body of the definition. +mydef@@ diff --git a/tst/editor/indent_table.kt b/tst/editor/indent_table.kt new file mode 100644 index 0000000..dd00898 --- /dev/null +++ b/tst/editor/indent_table.kt @@ -0,0 +1,4 @@ +@table +First item | Second | A third item that's longer || + Row 2 | Text | Not as long || + @ diff --git a/tst/editor/indent_table_expected.kt b/tst/editor/indent_table_expected.kt new file mode 100644 index 0000000..0876a4f --- /dev/null +++ b/tst/editor/indent_table_expected.kt @@ -0,0 +1,4 @@ +@table + First item | Second | A third item that's longer || + Row 2 | Text | Not as long || +@ diff --git a/tst/editor/indent_untouched.kt b/tst/editor/indent_untouched.kt new file mode 100644 index 0000000..e92476d --- /dev/null +++ b/tst/editor/indent_untouched.kt @@ -0,0 +1,14 @@ +@code +def f(x): + if x: + return 1 +code@ +@eval +def g(y): + return y + 1 +g(2) +@ +@@@state Word_max + :desc A description line + :value 7 +@@@ diff --git a/tst/editor/indent_untouched_expected.kt b/tst/editor/indent_untouched_expected.kt new file mode 100644 index 0000000..e92476d --- /dev/null +++ b/tst/editor/indent_untouched_expected.kt @@ -0,0 +1,14 @@ +@code +def f(x): + if x: + return 1 +code@ +@eval +def g(y): + return y + 1 +g(2) +@ +@@@state Word_max + :desc A description line + :value 7 +@@@ diff --git a/tst/editor_test.sh b/tst/editor_test.sh new file mode 100755 index 0000000..f99a9ed --- /dev/null +++ b/tst/editor_test.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# +# editor_test.sh — Regression tests for the Klammertext editor support +# (the Emacs mode's indentation/alignment units and their Sublime Text ports). +# +# Why this lives in tst/: the editor code implements the LANGUAGE's structural +# layer — @-run length, bar-run dimension, ^-escapes, # removal, literal +# spans, nesting depth — independently of both the SKS and the Klammermachine. +# tst/ is the SKS-independent tier, and these suites check that the +# independent implementations of Klammertext's structure agree with the +# canonical formatting conventions AND with each other. The klammer names +# that appear in the fixtures (@ol, @table, @document, @code) are seeded +# configuration of the editor tools, not SKS dependencies; nothing here runs +# ktext or loads a klammer set. +# +# What is checked, for every fixture pair .kt / _expected.kt in +# tst/editor/: +# * the Sublime Python core's output equals the expected file +# * idempotence: the tool applied to the expected file leaves it unchanged +# * when Emacs is installed: the Emacs unit's output equals the expected +# file, and equals the Python output byte for byte (the sync check across +# the SYNC-noted policy lists) — skipped gracefully otherwise +# +# The editor code is located automatically: doc/emacs + doc/sublime in the +# development tree, doc/edit/emacs + doc/edit/sublime in the distribution. +# +# Usage: ./editor_test.sh Requires python3; Emacs is optional. +# Exit code: 0 if all tests pass, 1 otherwise. + +PASS=0 +FAIL=0 + +red=$'\033[31m' +green=$'\033[32m' +bold=$'\033[1m' +reset=$'\033[0m' + +HERE="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(cd "$HERE/.." && pwd)" +if [ -d "$ROOT/doc/emacs" ]; then + EMACS_DIR="$ROOT/doc/emacs" + SUBLIME_DIR="$ROOT/doc/sublime" +elif [ -d "$ROOT/doc/edit/emacs" ]; then + EMACS_DIR="$ROOT/doc/edit/emacs" + SUBLIME_DIR="$ROOT/doc/edit/sublime" +else + echo "editor_test.sh: cannot locate the editor support (doc/emacs or doc/edit/emacs)" >&2 + exit 1 +fi +FIX="$HERE/editor" +OUT="$(mktemp -d)" +trap 'rm -rf "$OUT"' EXIT + +INDENT_FIXTURES="indent_list indent_document indent_table indent_untouched + indent_defs indent_escapes indent_named_close" +ALIGN_FIXTURES="align_mixed align_empty_cells align_boundary align_colspan + align_escapes align_too_wide" + +# Build the (MODE INFILE OUTFILE) triples: each fixture is run from its input +# and from its expected file (the idempotence check). +py_args=() +el_args=() +add_fixture() { # add_fixture MODE NAME + local mode="$1" name="$2" + py_args+=("$mode" "$FIX/$name.kt" "$OUT/$name.py.out") + py_args+=("$mode" "$FIX/${name}_expected.kt" "$OUT/$name.py.idem") + el_args+=("$mode" "$FIX/$name.kt" "$OUT/$name.el.out") + el_args+=("$mode" "$FIX/${name}_expected.kt" "$OUT/$name.el.idem") +} +for f in $INDENT_FIXTURES; do add_fixture indent "$f"; done +for f in $ALIGN_FIXTURES; do add_fixture align "$f"; done + +# --- run the Sublime Python cores (required) ------------------------------- +if ! command -v python3 >/dev/null 2>&1; then + echo "editor_test.sh: python3 not found" >&2 + exit 1 +fi +if ! PYTHONDONTWRITEBYTECODE=1 python3 "$FIX/editor_driver.py" "$SUBLIME_DIR" "${py_args[@]}"; then + echo "editor_test.sh: the Python driver failed" >&2 + exit 1 +fi + +# --- run the Emacs units (optional) ---------------------------------------- +HAVE_EMACS=0 +if command -v emacs >/dev/null 2>&1; then + if emacs --batch -L "$EMACS_DIR" \ + -l klammertext-mode -l klammertext-indent -l klammertext-align \ + -l "$FIX/editor_driver.el" "${el_args[@]}" 2>"$OUT/emacs.log"; then + HAVE_EMACS=1 + else + echo "${red}FAIL${reset} the Emacs driver failed:" >&2 + cat "$OUT/emacs.log" >&2 + FAIL=$((FAIL + 1)) + fi +else + echo "(Emacs not installed — the Emacs half is skipped; the Python cores still run)" +fi + +# --- compare --------------------------------------------------------------- +check() { # check NAME FILE_A FILE_B + if diff -q "$2" "$3" >/dev/null 2>&1; then + echo "${green}PASS${reset} $1" + PASS=$((PASS + 1)) + else + echo "${red}FAIL${reset} $1" + diff "$2" "$3" | head -10 + FAIL=$((FAIL + 1)) + fi +} + +for f in $INDENT_FIXTURES $ALIGN_FIXTURES; do + check "$f (python)" "$OUT/$f.py.out" "$FIX/${f}_expected.kt" + check "$f (python idempotent)" "$OUT/$f.py.idem" "$FIX/${f}_expected.kt" + if [ "$HAVE_EMACS" = 1 ]; then + check "$f (emacs)" "$OUT/$f.el.out" "$FIX/${f}_expected.kt" + check "$f (emacs idempotent)" "$OUT/$f.el.idem" "$FIX/${f}_expected.kt" + check "$f (emacs == python)" "$OUT/$f.el.out" "$OUT/$f.py.out" + fi +done + +echo +echo "${bold}editor_test: $PASS passed, $FAIL failed${reset}" +[ "$FAIL" -eq 0 ]