Literal @c, @source_listing with :marker, and a large-directory speedup

Three changes.

@c now takes its content literally, like @code -- it is the inline form
and @code the block form of the same thing.  The named close "c@" is
required, and characters that are special in a target no longer break
the file: @c a_b c@ renders correctly everywhere.  The Markdown
converter stops quoting inline code, since nothing needs protecting.

@source_file is renamed @source_listing.  Code read from a file is its
own klammer; @code is only for a block written inline (its never-
implemented :filename and :pattern options are removed).  The new
:marker P option lists the region between two lines that are exactly
//P, so the source file declares its own extractable regions.  A marker
missing or not appearing exactly twice is an error, never a fallback.

Rendering a document that sits in a large directory was paying a
recursive walk of that directory's whole tree on every @eval -- 27
seconds for a document that renders in a third of one.  The walk is now
a non-recursive look decided once per directory.

Assembled from dev commit 071b1b183de4.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-16 16:58:50 +02:00
parent 8b38a34841
commit bd39d9a369
8 changed files with 284 additions and 47 deletions

View File

@@ -324,35 +324,89 @@ class Code_fragment(klammer_base.Klammer_base):
def __init__(self, K):
super().__init__(K)
# code_text is a LITERAL parameter (sks/code/code.k), so its content
# reaches here exactly as written and NOTHING has escaped it -- the
# machine's target-character pass does not touch literal content. Both
# methods must therefore escape it themselves, with the same helpers the
# block form uses on its lines. Before 2026-08-16 the parameter was an
# ordinary string and the machine did the escaping; html() got away with
# handling "<" by hand and tex() with nothing at all.
def html(self):
#print(f"code: |{self.code_text}|")
result = self.code_text.strip()
result = undash(result)
#result = re.escape(result)
result = re.sub("<", "&lt;", result)
result = re.sub(" ", "&nbsp;", result)
#print(f"code: |{self.code_text}| -> |{result}|")
return f'<span class="code">{result}</span>'
return f'<span class="code">{html_line(undash(self.code_text.strip()))}</span>'
def tex(self):
return f"{{\\tt {self.code_text.strip()}}}"
return f"{{\\tt {tex_line(self.code_text.strip())}}}"
class Source(klammer_base.Klammer_base):
def extract_marked_region(src, marker, filename):
"""The region of `src` between two lines that are exactly "//<marker>".
The delimiter lines must consist SOLELY of "//" + marker and start in the
first column, so a marker cannot be matched inside indented code or in a
trailing comment. Both delimiters are the same text: the source brackets a
region rather than naming a start and a separate end.
A marker that is missing, or that appears only once, is an ERROR -- the
document asked for a region the file does not offer, and silently listing
the whole file (or nothing) would let the document drift from the code it
claims to quote, which is the one thing this option exists to prevent.
"""
delimiter = "//" + marker
lines = src.split("\n")
at = [i for i, line in enumerate(lines) if line == delimiter]
if len(at) < 2:
found = "once" if len(at) == 1 else "not at all"
raise Exception(
f'The marker "{marker}" appears {found} in "{filename}".\n'
f' A marked region is bracketed by TWO lines that are exactly\n'
f' "{delimiter}", each beginning in the first column.')
if len(at) > 2:
raise Exception(
f'The marker "{marker}" appears {len(at)} times in "{filename}"\n'
f' (lines {", ".join(str(i + 1) for i in at)}); a region needs exactly two.')
region = lines[at[0] + 1:at[1]]
while region and not region[0].strip():
region.pop(0)
while region and not region[-1].strip():
region.pop()
return "\n".join(region)
class Source(Code):
"""@source_listing -- a Code listing whose text comes from a FILE.
It IS a Code: @source_listing and @code differ only in where the text
comes from, so they must render identically, and subclassing is what
guarantees that rather than a second implementation that drifts.
It rendered separately until 2026-08-16, and was wrong in a way nothing
caught: html() quoted only "@" and tex() wrapped the raw text in a
verbatim environment. An @eval result is RE-READ as Klammertext, so an
unquoted "#" starts a text removal -- and since the html path had already
joined the source into one line, a file beginning "#include" produced an
EMPTY LISTING and exited 0. quote_specials() in this module documents
exactly that hazard, and this was the one place not using it. Inheriting
Code's rendering fixes both targets at once: tex_line()/html_line() quote
the Klammertext specials AND escape the target's own, so the author of the
source file needs to know about neither.
A verbatim environment could not have been made correct here, incidentally:
a quoted "^#" resolves to "\#" through the tex target's escape list, which
inside verbatim would print as "\#" rather than "#".
"""
def __init__(self, K):
super().__init__(K)
with open(self.filename) as fp:
self.src = fp.read()
def tex(self):
result = self.src
# result = re.sub("#", "^#", result)
# result = re.sub("\\^", "\\^", result)
result = f"\\begin{{verbatim}}\n{result}\n\\end{{verbatim}}\n"
return result
def html(self):
result = escape_newlines(self.src.strip()) + "\n"
result = re.sub("@", "^@", result)
result = E("div").body(result).cls("code_text").str()
return result
# Klammer_base, NOT Code: Code's constructor expands whitespace markers
# in self.text, and there is no "text" parameter here -- the text does
# not exist until the file has been read. File content carries no
# whitespace markers anyway, since those come from the katomizer.
klammer_base.Klammer_base.__init__(self, K)
try:
with open(self.filename) as fp:
text = fp.read()
except OSError as e:
raise Exception(
f'Cannot read the source listing "{self.filename}": {e.strerror}.\n'
f' A relative name resolves against the DOCUMENT\'s directory.')
if self.marker:
text = extract_marked_region(text, self.marker, self.filename)
self.text = text