A rest argument keeps an empty first element; a removal block keeps its neighbour; the Markdown converter reads paragraphs whole; Smart App Control on the Windows page (from dev 1083db946f05)

- A rest argument (a list's or a table's items) now strips exactly ONE
  leading bar -- the separator from what precedes it -- so a second one is
  an empty first element: a table with an empty header corner is written
  `@table |` then ` | Maskulinum | ...`. Before, every leading bar was
  dropped and such a header row shifted one column left. New shipped suite
  tst/rest_test.sh.
- Text written hard against a closing ]# (a period, a letter) is no longer
  removed with the block; a following space or line break still is.
- sks/tns/md_to_sks.py converts inline Markdown per paragraph, so emphasis
  that wraps is converted and not mis-paired; bold may contain italic;
  tables are unnumbered; links to sibling .md files converted alongside
  become links to their html pages; the arrow becomes @rightarrow@.
- doc/install/windows_source_install.md: Smart App Control must be off --
  it blocks MSYS2's unsigned toolchain, and its Evaluation state turns
  itself on later.
This commit is contained in:
ack committed 2026-10-04 13:18:06 +02:00
1 parent 6e9889f7ae
commit bc2b3d8461
8 files changed
+244 -23

No files matched your search

+58 -8
View File
@@ -17,15 +17,25 @@ grep. A file with no markers is inside the convertible subset.
First run: the Rectify user guide (1,805 lines), 2026-08-07. It rendered to
html and pdf without error or warning, with 99 markers -- 91 of them internal
anchor links, the one gap that appears at volume.
anchor links, the one gap that appears at volume. Second run: three German
vocabulary lists (4,200 lines, 152 tables), 2026-09-30, which rendered
without an error and with two silently wrong results -- an emphasis that
wrapped and a table's empty header corner -- so "renders clean" is not
"converted"; the fixes are in the spec's "What the second conversion
taught".
Usage: python3 md_to_sks.py <input.md> <output.kt>
Usage: python3 md_to_sks.py <input.md> <output.kt> [sibling.md ...]
The siblings are Markdown files converted alongside this one: a link to
one of them becomes a link to its html page (../<stem>/index.html, where
ktext writes it); a link to any other .md file is a gap.
"""
import re, sys, os, datetime
from collections import Counter
SPECIALS = "^@#|*" # ^ first: the other quotings introduce ^
marks = Counter()
SIBLINGS = set() # stems of the .md files converted together
def mark(kind, text):
"""A #[MD ... ]# marker.
@@ -56,7 +66,9 @@ def quote(s):
CODE = re.compile(r'`([^`\n]+)`')
LINK = re.compile(r'(?<!\!)\[([^\]]*)\]\(([^)]+)\)')
BOLD = re.compile(r'\*\*([^*]+)\*\*|__([^_]+)__')
# A bold run may hold italic runs ("**only for *sein* and *haben***"): the
# group admits a balanced *...* inside, so the closing ** is found past it.
BOLD = re.compile(r'\*\*((?:[^*]|\*[^*\s][^*]*\*)+)\*\*|__([^_]+)__')
# Markdown's underscore emphasis does not open or close INSIDE a word, and
# technical prose is full of identifiers like *name*_rectified_1.*ext* where
# the underscores are part of the name. So the closing _ must not be
@@ -99,10 +111,22 @@ def inline(text):
def link(m):
label, target = m.group(1), m.group(2)
# A link that cannot be converted keeps its words and records the
# loss. The marker goes BEFORE the words: a link is usually
# followed by punctuation, and a marker between the words and the
# period leaves the space before the marker standing ("words .").
if target.startswith("#"):
# An anchor into this document: no klammer gives an arbitrary
# heading an anchor, so keep the words and record the loss.
return emphasis(label) + " " + keep(mark("gap", f"link to {target}"))
# heading an anchor.
return keep(mark("gap", f"link to {target}")) + " " + emphasis(label)
if target.endswith(".md") and "/" not in target:
# Another Markdown file. "@link X.md" would be read as an
# anchor (href="#X.md"). Converted alongside this one, its html
# page is X/index.html beside ours; otherwise it is a gap.
stem = target[:-3]
if stem not in SIBLINGS:
return keep(mark("gap", f"link to {target}, not converted")) + " " + emphasis(label)
target = f"../{stem}/index.html"
return opening("@link " + quote(target) + " :text ") + emphasis(label) + closing()
def emphasis(t):
@@ -115,6 +139,9 @@ def inline(text):
the wrong list: IndexError, found by converting a second document.
"""
t = LINK.sub(link, t)
# An arrow is the SKS klammer (Andy, 2026-10-01): U+2192, and the
# halfwidth U+FFEB his documents use.
t = re.sub(r'[\u2192\uffeb]', lambda m: keep(OPEN + "@rightarrow@" + CLOSE), t)
t = STRIKE.sub(lambda m: emphasis(m.group(1)) + " " +
keep(mark("gap", "strikethrough")), t)
t = BOLD.sub(lambda m: opening("@b ") + (m.group(1) or m.group(2)) + closing(), t)
@@ -230,7 +257,9 @@ def convert(path):
rows.append([inline(c) for c in cells(src[i])])
i += 1
body = " ||\n".join(" | ".join(r) for r in rows)
opts = f" :cell_hpos {' '.join(align)}" if align and set(align) != {"l"} else ""
# A Markdown table has no caption and no number.
opts = " :number false"
opts += f" :cell_hpos {' '.join(align)}" if align and set(align) != {"l"} else ""
# A Markdown table says nothing about column widths, and
# Klammertext's default "fit" never wraps -- a prose column then
# runs off the page (the pdf target warns). Give the widest
@@ -263,8 +292,28 @@ def convert(path):
out.append("")
continue
out.append(inline(line) if line.strip() else "")
i += 1
if not line.strip():
out.append(""); i += 1
continue
# A PARAGRAPH goes through inline() whole, its line breaks kept.
# Converting line by line left a literal "*" on each line of an
# emphasis that wrapped, and the leftovers then paired with EACH
# OTHER, so the wrong words came out italic -- 33 paragraphs of the
# vocabulary lists, visible only in a diff.
para = [line]; i += 1
while i < n and src[i].strip() and not (HEADING.match(src[i]) or
TABLEROW.match(src[i]) or FENCE.match(src[i]) or
BULLET.match(src[i])):
if ORDERED.match(src[i]):
# A numbered line hard against the line above is a wrapped
# line that happens to begin with a number ("1. and 3.
# Sg."). CommonMark reads it as a list; the writer did not
# mean one. Kept in the paragraph, and said.
out.append(mark("judgment", "a line beginning with a number "
"was kept in its paragraph, not read as a list"))
para.append(src[i]); i += 1
out.append(inline("\n".join(para)))
return out, title, stripped_numbers, dropped_toc
@@ -312,6 +361,7 @@ def convert_list(block):
def main():
src_path, dst_path = sys.argv[1], sys.argv[2]
SIBLINGS.update(os.path.splitext(os.path.basename(a))[0] for a in sys.argv[3:])
body, title, stripped, dropped_toc = convert(src_path)
today = datetime.date.today().isoformat()
head = [f"#[MD source: {os.path.abspath(src_path)}",