Document language for dates; @eval pathname resolution and :cwd (from dev bc7cd62b6f68)

@date/@datetime gain :lang (German: "16. Juni 1910") over a document-wide
Language state variable, and :number for the numeric form (en 6/16/1910,
de 16.06.1910 per DIN 5008).  @eval finds Python modules next to the file
that names them regardless of the cwd, and the new :cwd option runs an
eval in a chosen working directory (@source_file uses it to resolve
against the document).  Two new test suites ship in tst/.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 20:25:49 +02:00
parent f84603ee19
commit 61b21d1397
12 changed files with 309 additions and 15 deletions

View File

@@ -12,7 +12,9 @@ A word or phrase displayed verbatim in a line
@@c :: @eval code_format.Code_fragment(K) eval@
@@
@@source_file filename : @eval code_format.Source(K) @ @@
# :cwd makes the filename resolve against the DOCUMENT's directory, not
# the directory ktext happens to run in.
@@source_file filename : @eval :cwd *K_input_dir* code_format.Source(K) @ @@
#[

View File

@@ -1,14 +1,21 @@
@@date.k :days.int 0 :
Date formatted as "16 June 1910", offset by ^:days days from today
(^:days 1 is tomorrow, ^:days -1 is yesterday)
@@date.k :days.int 0 :lang.language :number.bool false :
Date formatted as "16 June 1910" (^:lang de: "16. Juni 1910"), offset by
^:days days from today (^:days 1 is tomorrow, ^:days -1 is yesterday).
^:lang selects the language of the month name and the date form; without
it the document-wide Language state variable applies (default en).
^:number true gives the numeric form, which is language-specific in
order, separator, and padding: "6/16/1910" (en, month first),
"16.06.1910" (de, day first, zero-padded per DIN 5008)
@@
@@date.html :: @eval date.date(K) eval@ @@
@@date.tex :: @eval date.date(K) eval@ @@
@@date.txt :: @eval date.date(K) eval@ @@
@@datetime.k :days.int 0 :
Date and time formatted as "16 June 1910, 13:10", offset by ^:days days
from today (^:days 1 is tomorrow, ^:days -1 is yesterday)
@@datetime.k :days.int 0 :lang.language :number.bool false :
Date and time formatted as "16 June 1910, 13:10" (^:lang de:
"16. Juni 1910, 13:10"), offset by ^:days days from today (^:days 1 is
tomorrow, ^:days -1 is yesterday). ^:lang and ^:number select the
language and the numeric form as for ^@date
@@
@@datetime.html :: @eval date.datetime(K) eval@ @@
@@datetime.tex :: @eval date.datetime(K) eval@ @@

View File

@@ -1,17 +1,76 @@
# Implementation of the @date and @datetime klammers (date.k). Each takes
# the current date and time, offset by the :days argument (positive is
# future, negative is past), and formats it.
# future, negative is past), and formats it in the requested language.
#
# Language selection is two-level: the klammer's :lang argument overrides
# the document-wide Language state variable (declared in sks/kutil/kutil.k,
# default en; set it for a whole document with @@@state Language :value de).
#
# :number selects the purely numeric form instead — which is language-
# specific in order, separator, and padding: en 5/15/1955 (month first,
# unpadded), de 15.05.1955 (day first, zero-padded per DIN 5008).
#
# Languages are OWN TABLES, deliberately not locales: strftime's %B follows
# the process-wide C locale, which must be generated on the host (the
# containers ship minimal locale support) and is global mutable state in the
# embedded interpreter — the same reason @table's :decimal is implemented by
# character translation. Adding a language is adding one LANGUAGES entry:
# twelve month names and the date pattern (day/month/year; the day carries
# no leading zero). The time suffix ", HH:MM" is language-independent.
import datetime as dt
LANGUAGES = {
'en': {
'months': ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November',
'December'],
'date': '{day} {month} {year}', # 16 June 1910
'number': '{m}/{day}/{year}', # 6/16/1910
},
'de': {
'months': ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
'Juli', 'August', 'September', 'Oktober', 'November',
'Dezember'],
'date': '{day}. {month} {year}', # 16. Juni 1910
'number': '{dd}.{mm}.{year}', # 16.06.1910 (DIN 5008)
},
}
def offset(days):
return dt.datetime.now() + dt.timedelta(days=days or 0)
def language(K):
"""The language table for K: :lang argument, else the Language state
variable, else English. Returns (table, error_text)."""
code = getattr(K, 'lang', '') or getattr(K, 'Language', '') or 'en'
if code not in LANGUAGES:
return None, ('ERROR: @date/@datetime: unknown language "%s" '
'(available: %s)'
% (code, ', '.join(sorted(LANGUAGES))))
return LANGUAGES[code], None
def format_date(t, lang, number=False):
pattern = lang['number'] if number else lang['date']
return pattern.format(day=t.day, m=t.month,
dd='%02d' % t.day, mm='%02d' % t.month,
month=lang['months'][t.month - 1],
year=t.year)
def date(K):
return offset(K.days).strftime("%d %B %Y").lstrip("0")
lang, error = language(K)
if error:
return error
return format_date(offset(K.days), lang, K.number)
def datetime(K):
return offset(K.days).strftime("%d %B %Y, %H:%M").lstrip("0")
lang, error = language(K)
if error:
return error
t = offset(K.days)
return format_date(t, lang, K.number) + ', %02d:%02d' % (t.hour, t.minute)

View File

@@ -31,7 +31,7 @@ class Image(klammer_base.Klammer_base):
self.basename = klammer_base.unescape_ktesc(self.basename)
self.source, self.pwidth, self.pheight, self.file_error = self.cache.get(self.K_target, self.basename)
if self.file_error:
self.file_error_message = f'\nERROR: File "{self.K_input_filename}" not found'
self.file_error_message = f'\nERROR: Image "{self.basename}" not found'
self.as_string = as_string
if width:
self.width = width
@@ -42,7 +42,7 @@ class Image(klammer_base.Klammer_base):
self.rel_fraction = self.pwidth / self.rel_pwidth
if self.file_error:
self.file_error_message = f'\nERROR: File "{self.K_input_filename}" not found'
self.file_error_message = f'\nERROR: Image "{self.rel}" (the :rel image) not found'
def html(self):
img_dir = f"{self.K_output_dir}/{self.K_output_basename}/{self.Image_output_dir}"

View File

@@ -80,3 +80,17 @@ An <id> is the value of the ^:id argument for an image.
:pattern [\s\S]*
:python_cast (lambda s: __import__("kutil").filename_list(s))
@@@
@@@argtype language |
an ISO 639-1 language code (two lowercase letters) selecting the language
of text a klammer generates, e.g. en (English) or de (German). The
languages actually available are listed by the klammer that uses the
argument (^@date and ^@datetime); an unknown code reports them.
:pattern [a-z][a-z]
@@@
# The document-wide language for generated text. A klammer's own :lang
# argument overrides it; see the language argtype above. Consumers today:
# @date and @datetime (month names and date form). Set it for a whole
# document with @@@state Language :value de @@@
@@@state Language :desc Language (ISO 639-1) for generated text :value en @@@