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

@@ -1,15 +1,27 @@
@@code.k :filename :pattern
@@code.k
@hpos_args :hpos left @
@caption_args :caption_side top @
| text.literal :
A source file displayed verbatim
A block of code, given here and displayed uninterpreted. Code that lives in a
file is ^@source_listing instead; ^@code is only for a block written inline.
It carried ":filename" and ":pattern" until 2026-08-16. Neither was ever
implemented -- "filename" is read only by ^@source_listing and "pattern" by
nothing at all -- and because a literal parameter's content begins at the first
katom when no bar is written, "^@code :filename f :pattern p" DISPLAYED THOSE
WORDS as the listing rather than acting on them.
@@
@@code.html,tex :: @eval code_block.Code(K) @ @@
@@c.k code_text :
A word or phrase displayed verbatim in a line
@@c.k code_text.literal :
A word or phrase displayed verbatim in a line. Like ^@code, its content is
LITERAL -- nothing in it is interpreted as Klammertext -- so it must be closed
with the named delimiter "c^@". ^@c is the in-line form and ^@code the block
form of the same thing; before 2026-08-16 this parameter was an ordinary
string, so a "^#" or a bare "^@" inside it was read as Klammertext and usually
failed the file, which is not what "verbatim" can mean.
@@
@@c.html,tex :: @eval code_block.Code_fragment(K) eval@
@@ -17,5 +29,19 @@ A word or phrase displayed verbatim in a line
# :cwd makes the filename resolve against the DOCUMENT's directory, not
# the directory ktext happens to run in.
@@source_file.k filename : Display the contents of the file verbatim. @@
@@source_file.html,tex :: @eval :cwd *K_input_dir* code_block.Source(K) @ @@
@@source_listing.k filename :marker
@hpos_args :hpos left @
@caption_args :caption_side top @
: Code read from a file and displayed uninterpreted. *filename* is the file;
without ":marker" the whole file is listed.
":marker P" lists only the region BETWEEN two lines that consist solely of
"//P" and begin in the first column. The source file therefore declares its
own extractable regions and the document asks for one by name, so the two
cannot drift apart silently: renaming or reformatting the code does not change
what is extracted, and a region that disappears is an error rather than a
quietly different listing.
Named "^@source_file" until 2026-08-16. # retired-ok
@@
@@source_listing.html,tex :: @eval :cwd *K_input_dir* code_block.Source(K) @ @@

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