Files
klammertext/sks/list/list.py
Andy Kopra 8a2699a253 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

466 lines
14 KiB
Python

import pprint
import re
import textwrap
import kutil
import klammer_base
import itertools
import math
from html_util import E
class List(klammer_base.Klammer_base):
def __init__(self, K, list_type):
super().__init__(K)
self.list_type = list_type
self.items = self.list_items
word_count = max([len(e.strip().split()) for e in self.items])
self.compressed = False #and word_count < int(self.List_compressed_word_max)
def html(self):
body = ""
word_count = 0
for e in self.items:
body += f"<li>\n{kutil.format_for_paragraphs(e.strip())}\n</li>\n"
cls = "compressed" if self.compressed else ""
result = E(self.list_type).cls(cls).body(body.strip()).str().strip() + "\n"
return result
def tex(self):
body = "\n\n".join([f"\\item {e}" for e in self.items])
command = {'ol' : 'enumerate', 'ul' : 'itemize'}[self.list_type]
init = ""
if self.__dict__.get("initial") and self.initial != 1:
init = "\\addtocounter{enumi}{" + str(self.initial - 1) + "}"
#topsep = '[noitemsep,topsep=0pt]'
topsep = "[noitemsep,topsep=0pt," if self.compressed else "[topsep=0pt,itemsep=2pt,"
topsep += "leftmargin=16pt]"
listsep = '\\setlist{nolistsep}' if self.cmp else ''
result = f'{listsep}\n\\begin{{{command}}}{topsep}{init}\n{body}\n\\end{{{command}}}\n'
return result
def txt(self):
n = 1
result = ""
for item in self.items:
prefix = f"{n:3}. " if self.list_type == "ol" else "" # U+2022
print("prefix", prefix)
formatted_item = "\n".join(
textwrap.wrap(item.strip(), width=80, subsequent_indent=" "*(len(prefix))))
result += f"{prefix}{formatted_item}\n"
if not self.compressed:
result += "\n"
n += 1
print(result)
return result
class Define(klammer_base.Klammer_base):
def __init__(self, K):
super().__init__(K)
self.items = self.descriptions
#self.show()
#pprint.pprint(self.items)
def html(self):
def fontify(s):
return {"r" : s,
"i" : f"<i>{s}</i>",
"b" : f"<b>{s}</b>",
"t" : f'<span class="monospace">{s}</span>',
"c" : f'<span class="monospace">{s}</span>',
"s" : f'<span class="sansserif">{s}</span>'}[self.font]
result = "\n\n".join([f"<dt>{fontify(term)}</dt>\n<dd>{definition}</dd>"
for term,definition in self.items])
result = f"<dl>\n{result}\n</dl>"
return result
def tex(self):
def fontify(s):
font = {'r' : r'\normalfont',
'i' : r'\normalfont\itshape',
'b' : r'\normalfont\bfseries',
't' : r'\normalfont\ttfamily',
'c' : r'\normalfont\ttfamily',
's' : r'\normalfont\sffamily'}[self.font]
return f"{font} {s}"
result = "\n\n".join([f"\\item[{fontify(term)}]\\hfill\n\\newline {definition}"
for term,definition in self.items])
result = f"\\begin{{description}}[labelindent=12pt,nosep,itemsep=6pt]\n{result}\n\\end{{description}}"
return result
def txt(self):
return "TXT DEFINITIONS"
class Lines(klammer_base.Klammer_base):
def __init__(self, K):
super().__init__(K)
def html(self):
#return " <br>".join(self.s.strip().split("\n"))
#return f'<pre style="font-family: var(--serif)">{self.s}</pre>'
#return f'<pre>{self.s}</pre>'
result = self.s
result = re.sub("\n", "<br>\n", result)
result = re.sub(" ", "&^#160;", result)
return result
def tex(self):
return r"\\\newline\n".join(self.s.strip().split("\n"))
def txt(self):
return self.s.strip()
"""
def lines(K):
return {'html' : html_lines,
'tex' : tex_lines,
'pdf' : tex_lines}[K._target](K)
def tex_lines(K):
return " \\\\\n".join(K.s.strip().split("\n"))
def html_lines(K):
return " <br>".join(K.s.strip().split("\n"))
"""
"""
def definition_item(K):
if K._format == 'html':
return '<dt>{}</dt>\n<dd>{}</dd>'.format(
format_for_paragraphs(K.term),
format_for_paragraphs(K.desc))
else:
leading = '' if K.inlist else '\\vspace*{-16pt}'
return '\\item[{}]\\leavevmode{}\n\n{}'.format(
K.term, leading, K.desc)
"""
"""
def list(K, list_type):
items = kutil.rest_args(K.list_items)
return {'html' : html_list,
'tex' : tex_list,
'pdf' : tex_list}[K._target](K, list_type, items)
def html_list(K, list_type, items):
body = "\n" + "".join([E("li").body(e).str()+"\n" for e in items])
result = E(list_type).body(body).str().rstrip()
return result
def tex_list(K, list_type, items):
body = "\n\n".join([f"\\item {e}" for e in items])
command = {'ol' : 'enumerate', 'ul' : 'itemize'}[list_type]
topsep = '[topsep=0pt]'
listsep = '\\setlist{nolistsep}' if K.cmp != 'false' else ''
result = f'{listsep}\n\\begin{{{command}}}{topsep}\n{body}\n\\end{{{command}}}\n'
return result
def describe(K):
items = "\n\n".join(kutil.rest_args(K.descriptions))
return {'html' : html_describe,
'tex' : tex_describe,
'pdf' : tex_describe}[K._target](K, items)
def html_describe(K, items):
return E("dl").body(items).str()
def tex_describe(K, items):
return f'\\begin{{description}}\n{items}\n\\end{{description}}'
def entry(K):
return {'html' : html_entry,
'tex' : tex_entry,
'pdf' : tex_entry}[K._target](K)
def html_entry(K):
#return f'<dt>{K.item}</dt>\n<dd>{K.description}</dd>'
return E("dt").body(K.item).str(0) + E("dd").body(K.description).str(0)
def tex_entry(K):
return f'\\item[{K.item}]\n{K.description}\n'
def lines(K):
return {'html' : html_lines,
'tex' : tex_lines,
'pdf' : tex_lines}[K._target](K)
def tex_lines(K):
return " \\\\\n".join(K.s.strip().split("\n"))
def html_lines(K):
return " <br>".join(K.s.strip().split("\n"))
"""
r"""
# List items
paragraph_separator_re = re.compile('\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
def li(K):
if K._format == 'html':
result = K.s.strip()
result = format_for_paragraphs(result)
result = '<li>{}</li>'.format(result)
else:
#result = '\\item{{\\Kinlisttrue {}}} '.format(K.s.strip())
#result = '\\item\\begin\\Kinlisttrue {}\\end '.format(K.s.strip())
#result = '\\item\\parbox{{\\linewidth}}{{\\Kinlisttrue {}}} '.format(K.s.strip())
#result = '\\item\\Kinlisttrue {} '.format(K.s.strip())
result = '\\item {} '.format(K.s.strip())
if K.keep:
result = '\\parbox{{\\linewidth}}{{{}}}'.format(result)
return result
def mli(K):
result = '@0item {} | @0ulc\n'.format(K.args[0])
result += "\n".join(['@0li {} li@'.format(e) for e in K.args[1:]]) + '\n'
result += ' ulc@ item@\n'
return result
# Ordered and unordered lists:
def list_html(K, list_type):
result = ''
if K.list_items[0][0:4] != '<li>':
result = "\n".join(['<li>{}</li>'.format(e) for e in K.list_items])
else:
result = K.list_items[0]
tag = 'ol'
if list_type == 'ul':
tag = 'ul style="list-style-type:{};"'.format(K.bullet)
cls = 'compressed' if K.cmp else ''
if list_type == 'ul' and K.bullet == 'none':
cls += ' nobullet'
cls = ' class="{}"'.format(cls.strip())
result = '<{}{}>\n{}\n</{}>\n'.format(tag, cls, result, list_type)
return result
def list_latex(K, list_type):
result = ''
first_word = K.list_items[0][:5]
if first_word not in {'\\item', '\\parb'}:
result = " " + "\n".join(['@0li {} li@'.format(e.strip()) for e in K.list_items])
else:
result = K.list_items[0]
#options = 'nosep,itemsep=1pt,topsep=0pt,partopsep=8pt,parsep=4pt'
options = 'nosep,itemsep={}pt'.format(2 if K.cmp else 6)
if K.cmp:
itemsep=',nosep'
else:
itemsep=''
'''
if list_type == 'ul' and K.bullet == 'none' and K.indent:
left_margin = ',leftmargin=18pt'
else:
left_margin = ''
'''
left_margin = ''
if K.in_define:
if list_type == 'ul':
left_margin = ',leftmargin=4pt'
else:
left_margin = ',leftmargin=16pt'
if K.parsep:
parsep = K.parsep
else:
parsep = 2 if K.cmp else 6
#options = '[nosep,parsep={}pt{},{}]'.format(parsep, left_margin,itemsep)
options = '[parsep={}{}{}]'.format(parsep, left_margin, itemsep)
'''
if K.cmp:
options = '[nosep]'
else:
options = ''
'''
#options = '[itemsep={}]'.format(K.itemsep) if K.itemsep else ''
#options = '[topsep=0pt,parsep={}pt]'.format(2 if K.cmp else 6)
#options = '\\setlength{{\\topsep}{0pt}}\n\\setlength{{\parsep}}{{{}}}\n'.format(
bullet = ''
if list_type == 'ul' and K.bullet == 'none':
bullet = '\\renewcommand\\labelitemi{\\hspace*{-8pt}}'
itemsep = '\n\\setlength{{\\itemsep}}{{{}}}\n'.format(K.itemsep) if K.itemsep else ''
#result = '''\\listvmargin%
result = '''\\begin{{{0}}}{1}{2}
{3}
{4}
\\end{{{0}}}
'''.format('itemize' if list_type == 'ul' else 'enumerate',
options, itemsep, bullet, result.strip())
result = re.compile('\n\n', re.S).sub('\n', result)
result = re.compile(r' +\\begin', re.S).sub(r'\\begin', result)
if K.uncover:
N = 1
def replace(match):
nonlocal N
result = '{}<{}-> '.format(match.group(1), N)
N += 1
return result
result = re.compile(r'(\\item(\[|\s))').sub(replace, result)
return result
#\\listvmargin
def ul(K):
if K._format == 'html':
return list_html(K, 'ul')
elif K._format == 'latex':
return list_latex(K, 'ul')
else:
util.format_not_defined(K._format, 'ul')
def ol(K):
if K._format == 'html':
return list_html(K, 'ol')
elif K._format == 'latex':
return list_latex(K, 'ol')
else:
util.format_not_defined(K._format, 'ol')
# Definitions
def definition_item(K):
if K._format == 'html':
return '<dt>{}</dt>\n<dd>{}</dd>'.format(
format_for_paragraphs(K.term),
format_for_paragraphs(K.desc))
else:
leading = '' if K.inlist else '\\vspace*{-16pt}'
return '\\item[{}]\\leavevmode{}\n\n{}'.format(
K.term, leading, K.desc)
def dfont(K):
if K._format == 'latex':
result = {'r' : '\\normalfont',
'i' : '\\normalfont\\itshape',
'b' : '\\normalfont\\bfseries',
't' : '\\normalfont\\ttfamily',
's' : '\\normalfont\\sffamily\\large'
}[K.font]
elif K._format == 'html':
s = K.term
result = {'r' : s,
'i' : '<i>{}</i>'.format(s),
'b' : '<b>{}</b>'.format(s),
't' : '<span class="monospace">{}</span>'.format(s),
's' : '<span class="sansserif">{}</span>'.format(s)
}[K.font]
else:
error.Klammertext_error(
'There is not defined format named "{}".'.format(K._format))
return result
def escape_code_term(s):
def replace(match):
left, term, right = match.groups()
term = re.sub('\[', r'$\\lbrack$\\,', term)
term = re.sub('\]', r'\\,$\\rbrack$', term)
#term = re.sub('\[', '\\[', term)
#term = re.sub('\]', '\\]', term)
term = re.sub('--', '-{}-', term)
result = '{}{}{}'.format(left, term, right)
return result
pat = re.compile(r'(\\item\[)(.*?)(\]\\)', re.S)
return pat.sub(replace, s)
def define(K):
if K._format == 'latex':
#itemsep=1pt,topsep=2pt,partopsep=8pt,parsep=4pt,%
options = 'nosep,parsep=4pt,leftmargin=32pt'
fontname = {'r' : '\\normalfont',
'i' : '\\normalfont\\itshape',
'b' : '\\normalfont\\bfseries',
't' : '\\normalfont\\ttfamily',
's' : '\\normalfont\\sffamily',
'si' : '\\normalfont\\sffamily\\itshape',
'sb' : '\\normalfont\\sffamily\\bfseries',
'c' : '\\normalfont',
}[K.font]
term = K.s
if K.font == 'c':
term = escape_code_term(term)
#term = re.compile('item\[(.*?)\]', re.S).sub(r'item[\\verb+\1+]', term)
#term = re.compile('
return '''
\\listvmargin \\begin{{description}}[{},
labelindent=16pt,%
style=nextline,font={}]
%\\raggedright
{}
\\end{{description}} \\listvmargin\n'''.format(options,fontname, term)
elif K._format == 'html':
term_class = {'r' : 'normal',
'i' : 'italic',
'b' : 'bold',
't' : 'monospace',
's' : 'sansserifbody',
'si' : 'sansserifitalic',
'sb' : 'sansserifbold',
'c' : 'normal',
}[K.font]
term = K.s
if K.font == 'c':
#term = re.sub('&#8211;', '&#8208;'*2, term)
term = re.sub('&#8211;', '- __UNSPACE__ -', term)
body = re.sub('<dt>', '<dt class="{}">'.format(term_class), term)
return '''
<dl>
{}
</dl>'''.format(body)
"""
def make_column_lists(lst, count):
cols = math.ceil(len(lst) / count)
result = []
for i in range(count):
#print(i * cols, (i + 1) * cols)
result.append(lst[i * cols : (i+1) * cols])
return list(itertools.zip_longest(*result, fillvalue=""))
def xmake_column_lists(lst, column_count):
count = len(lst)
row_count = math.ceil(count / column_count)
print(count, column_count, row_count)
i = 0
while i < count:
i += 1
def columns(K):
rows = make_column_lists(K.items.split(), K.n)
result = "@table\n" # :lines false :header_line false :leading 1.1 :vmargin false |\n"
for row in rows:
#print("row:", row)
result += " | ".join(row) + " ||\n"
result += "table@\n"
if K.K_target == "latex":
result = f"\\vspace*{{-12pt}}\n{result}"
return result