Files
klammertext/sks/kutil/kutil.py

222 lines
6.8 KiB
Python
Raw Normal View History

import os
import re
import inspect
import html_util
from html_util import E
def black_text():
return "\033[0;30m"
def blue_text(text):
return f"\033[0;34m{text}\033[0m"
def red_text(text):
return f"\033[31m{text}\033[0m"
def msg(text=""):
frame = inspect.currentframe().f_back
print(blue_text(f"[{os.path.basename(frame.f_code.co_filename)}:{frame.f_lineno}]"), text)
def escape(s):
result = s
#result = re.sub(r"\b", r"\b", result)
#result = re.sub(r"\t", r"\t", result)
#result = re.sub("\f", r"\\f", result)
#result = re.sub("\v", r"\\v", result)
#result = re.compile("\\(.)").sub(r"\\\1", result)
return result
def klammertext_dir():
var = 'KLAMMERTEXT_HOME'
kdir = os.environ.get(var)
if kdir is None:
raise Exception(
f"The environment variable {var} must be defined as the top-level Klammertext directory")
return kdir
def cache_directory(relative_pathname, basename):
directory = relative_pathname
if not os.path.isdir(directory):
os.makedirs(directory)
result = directory + "/_klammertext_cache/" + basename
return result;
def sks_dirs():
result = 'kutil book document block font link section list image table code color'.split()
k = klammertext_dir()
result = [[e, f"{k}/sks/{e}"] for e in result]
return result
def sks_files_of_type(extension):
result = []
for base, sks_dir in sks_dirs():
filename = f'{sks_dir}/{base}.{extension}'
if os.path.exists(filename):
result.append([base, filename])
return result
def make_dir_if_necessary(d, delete_contents=False):
if not os.path.exists(d):
os.makedirs(d)
if delete_contents:
os.system(f'rm -rf {d}/*')
def protect_klammertext_special_characters(text):
result = text
result = re.compile(r"\^").sub("", result)
result = re.compile(r"@").sub("", result)
result = re.compile(r":").sub("̅C̅", result)
result = re.compile(r"\|").sub("̅B̅", result)
return result
# When did this ever make sense? Before the new word patterns, probably.
def bar_delimiter():
return "\3"
def double_bar_delimiter():
return "\4"
def caption_delimiter():
return "__CAPTION__"
def caption_marker(name, caption, delimiter=" - "):
caption = re.sub("\n", " ", caption)
d = caption_delimiter()
if caption:
caption = f"{delimiter}{caption}"
return f"{d}{name}{d}{caption}{d}"
Typed arguments, calculated tables, spans, closed-world fonts, top-level fnt/ and env/ Sync with klammertext-dev through b90b0e09: - Argument types end to end: :python_cast values are applied (Python @eval receives real bools/numbers/lists), argument values are validated against their argtype patterns with the argtype's description as the error message, argtypes can declare :default (overridable per declaration), and parameterized type families are supported: rest(N) casts a rest argument to an N-dimensional list (bar-count = dimension). - Unified indexed_range syntax (selector with parenthesized subsets, composable mnemonic names) for table lines and spans. - Table klammer: caption fonts fixed in both targets, :column_width / :leading / :colsep wired, :colspan and :rowspan render (HTML attributes; \multicolumn / \multirow), calculated cell values (:calc) with prefix operators, display-precision semantics, :calc_format and :decimal period|comma. - Fonts: closed-world resolution on the Klammertext font store (infrastructure in mac/font_store; no Google Fonts links or fetch). Default fonts live in the top-level fnt/; additional fonts install into KLAMMERTEXT_FONTS directories via kdesc --font (list, samples, preview, install — classification by font metadata). CSS font family names are quoted (digit-initial families were silently lost). - Environment files moved from mac/env/ to the top-level env/; shell profiles source env/runtime.env. Dead per-host variants removed. - Container: fnt/ ships in the image; curl removed (no network use).
2026-07-22 18:17:43 +02:00
def rest_split(s, dimensions=1):
"""Split bar-delimited text into nested lists, one level per dimension.
The delimiter for dimension n is a run of exactly n bar characters:
| separates elements, || separates lists of elements, ||| lists of
lists, and so on. This is the cast behind the rest(N) argument type.
One trailing top-level delimiter (the customary dangling separator
before a closing @) is removed; all other empty elements are
preserved, so a trailing | still makes an empty final cell.
"""
s = s.strip()
if not s:
return [] if dimensions > 0 else s
delimiter = "|" * dimensions
if s.endswith(delimiter) and not s.endswith("|" + delimiter):
s = s[:-len(delimiter)]
return _rest_split_level(s, dimensions)
def _rest_split_level(s, dimensions):
if dimensions <= 0:
return s.strip()
pattern = re.compile("(?<!\\|)" + "\\|" * dimensions + "(?!\\|)")
return [_rest_split_level(part, dimensions - 1) for part in pattern.split(s)]
def rest_args(s, dimensions=1):
Typed arguments, calculated tables, spans, closed-world fonts, top-level fnt/ and env/ Sync with klammertext-dev through b90b0e09: - Argument types end to end: :python_cast values are applied (Python @eval receives real bools/numbers/lists), argument values are validated against their argtype patterns with the argtype's description as the error message, argtypes can declare :default (overridable per declaration), and parameterized type families are supported: rest(N) casts a rest argument to an N-dimensional list (bar-count = dimension). - Unified indexed_range syntax (selector with parenthesized subsets, composable mnemonic names) for table lines and spans. - Table klammer: caption fonts fixed in both targets, :column_width / :leading / :colsep wired, :colspan and :rowspan render (HTML attributes; \multicolumn / \multirow), calculated cell values (:calc) with prefix operators, display-precision semantics, :calc_format and :decimal period|comma. - Fonts: closed-world resolution on the Klammertext font store (infrastructure in mac/font_store; no Google Fonts links or fetch). Default fonts live in the top-level fnt/; additional fonts install into KLAMMERTEXT_FONTS directories via kdesc --font (list, samples, preview, install — classification by font metadata). CSS font family names are quoted (digit-initial families were silently lost). - Environment files moved from mac/env/ to the top-level env/; shell profiles source env/runtime.env. Dead per-host variants removed. - Container: fnt/ ships in the image; curl removed (no network use).
2026-07-22 18:17:43 +02:00
return rest_split(s, dimensions)
def parse_length(target, s, rel_fraction):
def choose(html_value, tex_value):
return html_value if target == "html" else tex_value
pat = re.compile(r'(?:([0-9.]+)([a-z]+))|(none|f)|(?:"([^"]+)")')
#pat = re.compile(r'(?:([0-9.]+)([a-z]+))|(none|f)|(?:{([^}]+)})')
#pat = re.compile(r'(?:([0-9.]+)([a-z]+))|(f)')
match = pat.match(s)
if match is None: # But already checked by the klammer
raise Exception(f'The argument "{s}" is not a length')
#print(match.groups())
num, units, fit, text = match.groups()
num = float(num) if num else ""
num *= rel_fraction
if fit:
result = "f" # choose("100vw", "\\textwidth")
elif text:
text = re.compile(r"\{\}\\textbackslash\{\}").sub(r"\\", text)
if text[0] == "-":
result = choose("", f"\\textwidth - \\widthof{{ {text[1:]}}}")
else:
result = choose("", f"\\widthof{{ {text}}}")
elif units == "w":
result = choose(f"{100 * num}vw", f"{num}\\textwidth")
elif units == "h":
result = choose(f"{100 * num}vh", f"{num}\\textheight")
elif units == "pw":
result = choose(f"{100 * num}vw", f"{num}\\paperwidth")
elif units == "ph":
result = choose(f"{100 * num}vh", f"{num}\\paperheight")
elif units == "px":
result = choose(f"{round(num)}", f"{num}px")
else:
result = f"{num}{units}"
result = re.sub("\t", "\\t", result)
return result, num, units
def old_parse_length(target, s):
pat = re.compile(r'(?:([0-9.]+)([a-z]+))|(none|f)|("([^"]+)")')
match = pat.match(s)
if match is None:
raise Exception(f'The argument "{s}" is not a length')
if match.group(3) in {"none", "f"}:
return "f"
if s[0] == '"':
if target in {"tex", "pdf"}:
return f"\\widthof{{{match.group(5)} }}"
else:
return ""
num, units = match.groups()[:2]
if float(num) == 0:
return None
result = f"{num}{units}"
if target in {"tex", "pdf"}:
if units == "w":
result = fr"{num}\textwidth"
elif units == "h":
result = fr"{numb}\textheight"
elif target in {"html"}:
scale_x = None
scale_y = None
if units == "w":
#result = fr"{int(float(num)*100)}vw"
result = fr"calc({int(float(num)*100)}vw - 2rem)"
#result = 0
scale_x = num
result = "none"
elif units == "h":
result = fr"{int(float(num)*100)}vw"
#result = 0
scale_y = num
result = "none"
result = [result, scale_x, scale_y]
else:
raise Exception(f"Uknown length: '{s}'")
return result
def parse_lengths(target, s):
# Fill strings with space character for the split:
#print("parse_lengths:", s)
def replace(match):
return re.sub(r"\s", "~", match.group(1))
result = re.compile(r'("[^"]+")').sub(replace, s)
#print(result, result.split())
result = [parse_length(target, e)[0] for e in result.split()]
return result
paragraph_separator_re = re.compile(r'\n *\n', re.S)
def format_for_paragraphs(s):
result = s
if len(paragraph_separator_re.findall(result)) > 0:
result = '\n\n{}\n\n'.format(result)
return result