summaryrefslogtreecommitdiff
path: root/macros/luatex
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2025-01-02 03:04:34 +0000
committerNorbert Preining <norbert@preining.info>2025-01-02 03:04:34 +0000
commit2eea71934e853f5e45633ccf0f7915b4f133919a (patch)
treeb5d38f640b78d108926cd12a371f857bdecbb202 /macros/luatex
parenta22c35c9797ac178b8552f754a91fa976d850a53 (diff)
CTAN sync 202501020304
Diffstat (limited to 'macros/luatex')
-rw-r--r--macros/luatex/generic/penlightplus/README.md2
-rw-r--r--macros/luatex/generic/penlightplus/penlightplus.lua440
-rw-r--r--macros/luatex/generic/penlightplus/penlightplus.pdfbin87726 -> 78426 bytes
-rw-r--r--macros/luatex/generic/penlightplus/penlightplus.sty162
-rw-r--r--macros/luatex/generic/penlightplus/penlightplus.tex305
-rw-r--r--macros/luatex/latex/luatbls/README.md27
-rw-r--r--macros/luatex/latex/luatbls/luatbls.lua310
-rw-r--r--macros/luatex/latex/luatbls/luatbls.pdfbin0 -> 73500 bytes
-rw-r--r--macros/luatex/latex/luatbls/luatbls.sty163
-rw-r--r--macros/luatex/latex/luatbls/luatbls.tex410
10 files changed, 1286 insertions, 533 deletions
diff --git a/macros/luatex/generic/penlightplus/README.md b/macros/luatex/generic/penlightplus/README.md
index cc80374347..04db3bf991 100644
--- a/macros/luatex/generic/penlightplus/README.md
+++ b/macros/luatex/generic/penlightplus/README.md
@@ -5,7 +5,7 @@ Additions to the Penlight Lua Libraries.
# License
-Copyright (C) 2021-2023 Kale Ewasiuk
+Copyright (C) 2021-2025 Kale Ewasiuk
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/macros/luatex/generic/penlightplus/penlightplus.lua b/macros/luatex/generic/penlightplus/penlightplus.lua
index 9bfc57605a..53b023941d 100644
--- a/macros/luatex/generic/penlightplus/penlightplus.lua
+++ b/macros/luatex/generic/penlightplus/penlightplus.lua
@@ -1,6 +1,6 @@
--% Kale Ewasiuk (kalekje@gmail.com)
---% 2024-09-30
---% Copyright (C) 2021-2024 Kale Ewasiuk
+--% 2025-01-01
+--% Copyright (C) 2021-2025 Kale Ewasiuk
--%
--% Permission is hereby granted, free of charge, to any person obtaining a copy
--% of this software and associated documentation files (the "Software"), to deal
@@ -42,6 +42,8 @@ if debug ~= nil then
end
+
+
-- http://lua-users.org/wiki/SplitJoin -- todo read me!!
penlight.tex = {} -- adding a sub-module for tex related stuff
@@ -65,6 +67,17 @@ function penlight.hasval(x) -- if something has value
end
+
+function penlight._Gdot(s)
+ -- return a global with nots
+ o = _G
+ for _, a in ipairs(s:split('.')) do
+ o = o[a]
+ end
+ return o
+end
+
+
-- Some simple and helpful LaTeX functions -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
-- xparse defaults
@@ -182,6 +195,18 @@ function penlight.tex.pkgerror(pkg, msg1, msg2, stop)
end
+if not penlight.debug_available then
+ penlight.tex.pkgwarn('penlight', 'lua debug library is not available, recommend re-compiling with the --luadebug option')
+end
+
+
+function penlight.tex.errorif(exp, pkg, msg1, msg2, stop)
+ if penlight.hasval(exp) then
+ penlight.tex.pkgerror(pkg, msg1, msg2, stop)
+ end
+end
+
+
--definition helpers -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
function penlight.tex.defmacro(cs, val, g) -- , will not work if val contains undefined tokens (so pre-define them if using..)
@@ -264,6 +289,8 @@ end
-- when nesting commands, this makes it helpful to not worry about brackets
penlight.tex._NumBkts = 0
+penlight.tex._EndEnvs = {}
+
--prt(opencmd('textbf')..opencmd('texttt')..'bold typwriter'..close_bkt_cnt())
function penlight.tex.opencmd(cmd)
@@ -291,6 +318,24 @@ end
+
+
+function penlight.tex.openenv(env, opt)
+ if opt == nil then opt = '' else opt = '['..opt..']' end
+ tex.sprint('\\begin{' .. env .. '}'..opt)
+ table.insert(penlight.tex._EndEnvs, 1, '\\end{'..env..'}')
+end
+
+function penlight.tex.closeenv(num)
+ num = num or #penlight.tex._EndEnvs
+ for i=1, num do
+ tex.sprint(penlight.tex._EndEnvs[1])
+ table.remove(penlight.tex._EndEnvs, 1)
+ end
+end
+
+
+
function penlight.tex.aliasluastring(s, d)
s = s:delspace():upper():tolist()
d = d:delspace():upper():tolist()
@@ -371,11 +416,11 @@ local number = P{"number",
function penlight.char(num)
- return string.char(string.byte("a")+num-1)
+ return string.char(string.byte("a")+tonumber(num)-1)
end
function penlight.Char(num)
- return string.char(string.byte("A")+num-1)
+ return string.char(string.byte("A")+tonumber(num)-1)
end
@@ -475,6 +520,48 @@ function str_mt.__index.parsekv(s, t) -- parsekv string
return penlight.luakeys.parse(s, t)
end
+function str_mt.__index.splitstrip(s, sep, stri) --
+ sep = sep or ','
+ return penlight.List(s:split(sep)):map(function(x) return penlight.stringx.strip(x, stri) end)
+end
+
+
+function str_mt.__index.split2(s, sep1, sep2, stri) --
+ sep1 = sep1 or ','
+ sep2 = sep2 or '='
+ if stri == nil then stri = true end
+ stri = penlight.hasval(strip)
+ local splitfunc = string.split
+ if stri then
+ splitfunc = string.splitstrip
+ end
+ return penlight.List(splitfunc(s,sep1)):map(function(x) return splitfunc(x, sep2) end)
+end
+
+
+
+
+function str_mt.__index.hasnonum(s)
+ -- string only contains letters and symbols
+ --assert_string(1,s) -- todo
+ return string.find(s,'^[%D]+$') == 1
+end
+
+function str_mt.__index.hasnoalpha(s)
+ -- string only contains numbers and symbols
+ --assert_string(1,s) -- todo
+ return string.find(s,'^[%A]+$') == 1
+end
+
+function str_mt.__index.isvarlike(s)
+ -- string is like a variable, does not start with a number, then followd by letter, number, or underscore
+ --assert_string(1,s) -- todo
+ return string.find(s,'^[%a_][%w%d_]*$') == 1
+end
+
+
+
+
-- -- -- -- function stuff
function penlight.clone_function(fn)
@@ -506,27 +593,28 @@ end
function penlight.seq.check_neg_index(i, len, fallback)
-i = tostring(i):delspace()
-if i == '' then return fallback end
-i = tonumber(i)
-if i == nil then
- local _ = 1*'"Attempted to use seqstr indexing with negative number, but length of list not provided"'
- return fallback -- fallback is the number to fall back on if i isn't provided
-end
-len = tonumber(len)
-if i < 0 then
- if len == nil then
+ i = tostring(i):delspace()
+ if i == '' then return fallback end
+ i = tonumber(i)
+ if i == nil then
local _ = 1*'"Attempted to use seqstr indexing with negative number, but length of list not provided"'
- return pl.utils.raise("Attempted to use seqstr indexing with negative number, but length of list not provided")
+ return fallback -- fallback is the number to fall back on if i isn't provided
end
- i = len + 1 + i -- negative index
-end
-return i
+ len = tonumber(len)
+ if i < 0 then
+ if len == nil then
+ local _ = 1*'"Attempted to use seqstr indexing with negative number, but length of list not provided"'
+ return penlight.utils.raise("Attempted to use seqstr indexing with negative number, but length of list not provided")
+ end
+ i = len + 1 + i -- negative index
+ end
+ return i
end
penlight.seq.train_element_sep = ','
penlight.seq.train_range_sep = ':'
+
function penlight.seq.train(s, len)
-- parse a range given a string indexer
-- syntax is: s = 'i1, i2, r1:r2' where i1 and i2 are individual indexes.
@@ -538,8 +626,16 @@ function penlight.seq.train(s, len)
local t = penlight.List() -- list of indexes
local check_neg = penlight.seq.check_neg_index
- for _, r in ipairs(s:split(penlight.seq.train_element_sep)) do
- if r:find(penlight.seq.train_range_sep) then
+ local steps = s:split(penlight.seq.train_element_sep)
+ --penlight.wrth(steps,'abc')
+ for _, r in ipairs(steps) do
+ --penlight.wrth(r,'seq.train = '..s)
+ r = penlight.stringx.strip(r)
+ if r == '*' then -- if the string has no numbers and no :, it is a key
+ t:append(r)
+ elseif string.isvarlike(r) then -- if the string has no numbers and no :, it is a key
+ t:append(r)
+ elseif r:find(penlight.seq.train_range_sep) then
r = r:split(penlight.seq.train_range_sep) -- if it's a range
t:extend(penlight.List.range(check_neg(r[1], len, 1),
check_neg(r[2], len, len),
@@ -547,8 +643,8 @@ function penlight.seq.train(s, len)
else
t:append(check_neg(r, len))
end
- end
- return t
+ end
+return t
end
function penlight.seq.itrain(s, len)
@@ -563,6 +659,24 @@ end
+function penlight.seq.tbltrain(tbl, s) -- iterate over a table using the train syntax
+ local inds = penlight.seq.train(s, #tbl) -- indexes to use
+ local star = inds:index('*')
+ if star ~= nil then
+ inds:pop(star)
+ inds:inject(penlight.tablex.kkeys(tbl), star)
+ end
+ local i = 0
+ return function ()
+ i = i + 1 -- i of indexes
+ if i <= #inds then
+ local v = tbl[inds[i]]
+ --penlight.wrth(v)
+ --if v == nil then penlight.test.asserteq(v, true) end -- todo make a generic lua error message function
+ return inds[i], v
+ end
+ end
+end
@@ -598,6 +712,16 @@ end
-- table stuff below
+
+function penlight.tablex.concatenate(t1,t2)
+ -- todo is this needed
+ for i=1,#t2 do
+ t1[#t1+1] = t2[i]
+ end
+ return t1
+end
+
+
function penlight.tablex.strinds(t) -- convert indices that are numbers to string indices
local t_new = {}
for i, v in pairs(t) do -- ensure all indexes are strings
@@ -611,6 +735,7 @@ function penlight.tablex.strinds(t) -- convert indices that are numbers to strin
end
+
function penlight.tablex.listcontains(t, v)
return penlight.tablex.find(t, v) ~= nil
end
@@ -642,6 +767,20 @@ function penlight.tablex.fmt(t, fmt, strinds)
end
+function penlight.tablex.list2comma(t)
+ t = penlight.List(t)
+ local s = ''
+ if #t == 1 then
+ s = t[1]
+ elseif #t == 2 then
+ s = t:join(' and ')
+ elseif #t >= 3 then
+ s = t:slice(1,#t-1):join(', ')..', and '..t[#t]
+ end
+ return s
+end
+
+
function penlight.tablex.map_slice(func, T, j1, j2)
if type(j1) == 'string' then
return penlight.array2d.map_slice(func, {T}, ','..j1)[1]
@@ -653,6 +792,18 @@ end
penlight.array2d.map_slice1 = penlight.tablex.map_slice
+
+function penlight.tablex.kkeys(t)
+ local keys = {}
+ for k, _ in penlight.utils.kpairs(t) do
+ keys[#keys+1] = k
+ end
+ return keys
+end
+
+
+
+
-- todo option for multiple filters with AND logic, like the filter files??
function penlight.tablex.filterstr(t, exp, case)
-- case = case sensitive
@@ -802,11 +953,8 @@ end
-if not penlight.debug_available then
- penlight.tex.pkgwarn('penlight', 'lua debug library is not available, recommend re-compiling with the --luadebug option')
-else
- penlight.COMP = require'pl.comprehension'.new() -- for comprehensions
-
+if penlight.debug_available then
+ penlight.COMP = require'penlight.comprehension'.new() -- for comprehensions
local _parse_range = penlight.clone_function(penlight.array2d.parse_range)
function penlight.array2d.parse_range(s) -- edit parse range to do numpy string if no letter passed
@@ -820,6 +968,25 @@ end
+function penlight.List:inject(l2, pos)
+ pos = pos or 1
+ if pos < 1 then
+ pos = #self + pos + 1
+ end
+ l2 = penlight.List(l2):reverse()
+ for i in l2:iter() do
+ self:insert(pos, i)
+ end
+ return self
+end
+
+
+
+
+
+
+
+
-- https://tex.stackexchange.com/questions/38150/in-lualatex-how-do-i-pass-the-content-of-an-environment-to-lua-verbatim
penlight.tex.recordedbuf = ""
@@ -868,6 +1035,14 @@ function penlight.tex.makePDFtablekv(kv)
return t_new
end
+function penlight.tex.updatePDFtable(k, v, o) -- update pdf table
+ if o == nil then o = true end
+ k = k:strip():upfirst()
+ if penlight.hasval(o) or (__PDFmetadata__[k] == nil) then
+ __PDFmetadata__[penlight.tex.checkPDFkey(k)] = penlight.tex.makePDFvarstr(v)
+ end
+end
+
penlight.tex.writePDFmetadata = function(t) -- write PDF metadata to xmpdata file
t = t or __PDFmetadata__
@@ -908,31 +1083,11 @@ function penlight.tex.makeInTextstr(s)
return s
end
---todo decide on above or below
-function penlight.tex.list2comma(t)
- local s = ''
- if #t == 1 then
- s = t[1]
- elseif #t == 2 then
- s = t:join(' and ')
- elseif #t >= 3 then
- s = t:slice(1,#t-1):join(', ')..', and '..t[#t]
- end
- return s
-end
-function penlight.tex.split2comma(s, d)
- local t = penlight.List(s:split(d)):map(string.strip)
- penlight.tex.prt(penlight.tex.list2comma(t))
-end
-function penlight.tex.split2items(s, d)
- local t = penlight.List(s:split(d)):map(string.strip)
- for n, v in ipairs(t) do
- penlight.tex.prtn('\\item '..v)
- end
-end
+
+
function penlight.toggle_luaexpr(expr)
@@ -951,7 +1106,7 @@ function penlight.caseswitch(s, c, kv)
local sw = kvtbl[c] -- the returned switch
if sw == nil then -- if switch not found
if s == penlight.tex.xTrue then -- if star, throw error
- pl.tex.pkgerror('penlight', 'case: "'..c..'" not found in key-vals: "'..kv..'"')
+ penlight.tex.pkgerror('penlight', 'case: "'..c..'" not found in key-vals: "'..kv..'"')
sw = ''
else
sw = kvtbl['__'] or '' -- use __ as not found case
@@ -962,125 +1117,6 @@ end
-penlight.tbls = {}
-
-penlight.rec_tbl = ''
-penlight.rec_tbl_opts = {}
-
-
-function penlight.tbl(s)
- return penlight.get_tbl_item(s)
-end
-
-function penlight.get_tbl_name(s)
- if s == '' then
- return penlight.rec_tbl
- end
- return s
-end
-
-function penlight.get_tbl(s)
- s = penlight.get_tbl_name(s)
- return penlight.tbls[s]
-end
-
-function penlight.get_tbl_index(s, undec)
- undec = undec or false -- flag for allowing undeclared indexing
- local tbl = ''
- local key = ''
- local s_raw = s
- if s:find('%.') then
- local tt = s:split('.')
- tbl = tt[1]
- key = tt[2]
- elseif s:find('/') then
- local tt = s:split('/')
- tbl = tt[1]
- if tbl == '' then tbl = penlight.rec_tbl end
- key = tonumber(tonumber(tt[2]))
- if key < 0 then key = #penlight.tbls[tbl]+1+key end
- else
- tbl = penlight.rec_tbl
- key = tonumber(s) or s
- if type(key) == 'number' and key < 0 then key = #penlight.tbls[tbl]+1+key end
- end
- if tbl == '' then tbl = penlight.rec_tbl end
-
- if (penlight.tbls[tbl] == nil) or ((not undec) and (penlight.tbls[tbl][key] == nil)) then
- penlight.tex.pkgerror('penlightplus', 'Invalid tbl index attempt using: "'..s_raw..'". We tried to use tbl: "' ..tbl..'" and key: "'..key..'"')
- end
- return tbl, key
-end
-
-
-function penlight.get_tbl_item(s, p) -- get item with string, p means print value
- p = p or false
- local tbl, key = penlight.get_tbl_index(s)
- local itm = penlight.tbls[tbl][key]
- if p then
- tex.sprint(tostring(itm))
- end
- return itm
-end
-
-
-function penlight.set_tbl_item(s, v)
- tbl, key = penlight.get_tbl_index(s)
- penlight.tbls[tbl][key] = v
-end
-
-function penlight.check_recent_tbl_undefault()
- local undefaults = {}
- if penlight.rec_tbl_opts ~= nil then
- local defaults = penlight.tablex.union(
- penlight.rec_tbl_opts.defs or {},
- penlight.rec_tbl_opts.defaults or {}
- )
- for k, v in pairs(penlight.tbls[penlight.rec_tbl]) do
- if defaults[k] == nil then
- undefaults[#undefaults+1] = k
- end
- end
- if penlight.hasval(undefaults) then
- penlight.tex.pkgerror('penlightplus',
- 'Invalid keys passed to tbl keyval: ' .. (', '):join(undefaults) ..
- ' ; choices are: ' .. (', '):join(penlight.tablex.keys(defaults))
- )
- end
- end
-end
-
-function penlight.def_tbl(ind, def, g)
- local _tbl, _key = penlight.get_tbl_index(ind)
- if def == '' then def = 'dtbl'.._tbl.._key end
- token.set_macro(def, tostring(penlight.tbls[_tbl][_key]), g)
-end
-
-function penlight.def_tbl_all(ind, def)
- local _tbl = penlight.get_tbl_name(ind)
- if def == '' then def = 'dtbl'.._tbl end
- for k, v in pairs(penlight.tbls[_tbl]) do
- token.set_macro(def..k, tostring(v))
- end
-end
-
--- TODO TODO TODO get error working xy def, and referene which table for key-vals
-penlight.tbl_xysep = '%s+' -- spaces separate x-y coordinates
-function penlight.def_tbl_coords(ind, def)
- local tbl, key = penlight.get_tbl_index(ind)
- local str = penlight.tbls[tbl][key]
- if def == '' then def = 'dtbl'..tbl..key end
- local x, y = str:strip():splitv(penlight.tbl_xysep)
- if (not penlight.hasval(x)) or (not penlight.hasval(y)) then
- penlight.tex.pkgerror('penlightplus', 'def_tbl_coords function could not parse coordiantes given as "'..str..'" ensure two numbers separated by space are given!', '', true)
- end
- token.set_macro(def..'x', tostring(x))
- token.set_macro(def..'y', tostring(y))
-end
-
-
-
-
@@ -1136,3 +1172,69 @@ if penlight.hasval(__PL_GLOBALS__) then
end
+
+
+
+
+
+
+
+-- graveyard
+
+--todo decide on above or below
+
+
+
+penlight.tex.list2comma = penlight.tablex.list2comma
+
+function penlight.tex.split2comma(s, d)
+ local t = penlight.List(s:split(d)):map(string.strip)
+ penlight.tex.prt(penlight.tex.list2comma(t))
+end
+
+function penlight.tex.split2items(s, d)
+ local t = penlight.List(s:split(d)):map(string.strip)
+ for n, v in ipairs(t) do
+ penlight.tex.prtn('\\item '..v)
+ end
+end
+
+
+--
+--\subsection*{Splitting strings}
+--Splitting text (or a cmd) into oxford comma format via:
+--\cmd{\splittocomma[expansion level]{text}{text to split on}}:
+--
+--\begin{LTXexample}[width=0.3\linewidth]
+-- \splittocomma{ j doe }{\and}-\\
+--\splittocomma{ j doe \and s else }{\and}-\\
+--\splittocomma{ j doe \and s else \and a per }{\and}-\\
+--\splittocomma{ j doe \and s else \and a per \and f guy}{\and}-
+--
+--\def\authors{j doe \and s else \and a per \and f guy}
+--\splittocomma[o]{\authors}{\and}
+--\end{LTXexample}
+--
+--The expansion level is up to two characters, \cmd{n|o|t|f}, to control the expansion of each argument.
+--
+--You can do a similar string split but to \cmd{\item} instead of commas with \cmd{\splittoitems}
+--\begin{LTXexample}
+--\begin{itemize}
+-- \splittoitems{kale\and john}{\and}
+-- \splittoitems{kale -john -someone else}{-}
+-- \splittoitems{1,2,3,4}{,}
+--\end{itemize}
+--\end{LTXexample}
+
+
+
+--
+--\NewDocumentCommand{\splittocomma}{ O{nn} m m }{%
+-- \MakeluastringCommands[nn]{#1}%
+-- \luadirect{penlight.tex.split2comma(\plluastringA{#2},\plluastringB{#3})}%
+--}
+--
+--\NewDocumentCommand{\splittoitems}{ O{NN} m m }{%
+-- \MakeluastringCommands[nn]{#1}%
+-- \luadirect{penlight.tex.split2items(\plluastringA{#2},\plluastringB{#3})}%
+--}
diff --git a/macros/luatex/generic/penlightplus/penlightplus.pdf b/macros/luatex/generic/penlightplus/penlightplus.pdf
index bb9b8027ff..4a5d6eece3 100644
--- a/macros/luatex/generic/penlightplus/penlightplus.pdf
+++ b/macros/luatex/generic/penlightplus/penlightplus.pdf
Binary files differ
diff --git a/macros/luatex/generic/penlightplus/penlightplus.sty b/macros/luatex/generic/penlightplus/penlightplus.sty
index cc37b096b1..8b0a2634d1 100644
--- a/macros/luatex/generic/penlightplus/penlightplus.sty
+++ b/macros/luatex/generic/penlightplus/penlightplus.sty
@@ -1,6 +1,6 @@
% Kale Ewasiuk (kalekje@gmail.com)
-% 2024-09-30
-% Copyright (C) 2021-2024 Kale Ewasiuk
+% 2025-01-01
+% Copyright (C) 2021-2025 Kale Ewasiuk
%
% Permission is hereby granted, free of charge, to any person obtaining a copy
% of this software and associated documentation files (the "Software"), to deal
@@ -22,7 +22,7 @@
% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
% OR OTHER DEALINGS IN THE SOFTWARE.
-\ProvidesPackage{penlightplus}[2024-09-30]
+\ProvidesPackage{penlightplus}[2025-01-01]
\RequirePackage{luacode}
\RequirePackage{luakeys}
@@ -56,38 +56,30 @@
}
+
\gdef\luastringT#1{\luastring{\unexpanded\expandafter\expandafter\expandafter{#1}}} % expand luastring twice
\global\let\luastringF\luastring % fully expanded luastring
-
-\xtokcycleenvironment\luastringeenv
- {\addcytoks{##1}}
- {\processtoks{##1}}
- {\addcytoks[1]{##1}}
- {\addcytoks{##1}}
- {}
- {\cytoks\expandafter{\expandafter\luastringO\expandafter{\the\cytoks}}}
-
-\def\luastringE#1{\luastringeenv#1\endluastringeenv}
+%
+%\xtokcycleenvironment\luastringeenv
+% {\addcytoks{##1}}
+% {\processtoks{##1}}
+% {\addcytoks[1]{##1}}
+% {\addcytoks{##1}}
+% {}
+% {\cytoks\expandafter{\expandafter\luastringO\expandafter{\the\cytoks}}}
+%
+%\def\luastringE#1{\luastringeenv#1\endluastringeenv}
%\gdef\luastringE#1{\begin{luastringeenv#1\endluastringeenv}
+
% allow control over expansion of arguments to a latex function
\NewDocumentCommand{\MakeluastringCommands}{O{} m }{% #1 the desired commands #2 defaults
\luadirect{penlight.tex.aliasluastring(\luastring{#2},\luastring{#1})}%
}
-\NewDocumentCommand{\splittocomma}{ O{nn} m m }{%
- \MakeluastringCommands[nn]{#1}%
- \luadirect{penlight.tex.split2comma(\plluastringA{#2},\plluastringB{#3})}%
-}
-
-\NewDocumentCommand{\splittoitems}{ O{NN} m m }{%
- \MakeluastringCommands[nn]{#1}%
- \luadirect{penlight.tex.split2items(\plluastringA{#2},\plluastringB{#3})}%
-}
-
@@ -99,9 +91,7 @@
\newtoggle{luaexpr}\togglefalse{luaexpr}
-\begin{luacode*}
-\end{luacode*}
\NewDocumentCommand{\ifluax}{m m O{}}{% if lua expression is true do {m} if not [o]
\luadirect{penlight.toggle_luaexpr(#1)}%
@@ -124,125 +114,3 @@
-
-%%% tbls below
-
-\NewDocumentCommand{\tblnew}{m}{\luadirect{% initialize a tbl and set blank
- penlight.tbls[\luastring{#1}] = {}
- penlight.rec_tbl = \luastring{#1}
-}}
-
-\NewDocumentCommand{\tblfrkv}{m +m O{}}{\luadirect{%
- penlight.rec_tbl_opts = penlight.luakeys.parse(\luastring{#3})
- penlight.tbls[\luastring{#1}] = penlight.luakeys.parse(string.subpar(\luastring{#2}), penlight.rec_tbl_opts)
- penlight.rec_tbl = \luastring{#1}
-}}
-
-\NewDocumentCommand{\tblfrkvCD}{m +m O{}}{\tblfrkv{#1}{#2}[#3]\tblkvundefcheck\tbldefall{}{}}
-%% tbl from key-vals, then check defaults, then define all keys using default format
-\NewDocumentCommand{\tblfrkvNCD}{m +m O{}}{\tblfrkvN{#1}{#2}[#3]\tblkvundefcheck\tbldefall{}{}}
-
-\NewDocumentCommand{\tblfrkvN}{m +m O{}}{\luadirect{%
- penlight.rec_tbl_opts = penlight.luakeys.parse(\luastring{#3})
- penlight.tbls[\luastring{#1}] = penlight.luakeys.parse(string.subpar(\luastringN{#2}), penlight.rec_tbl_opts)
- penlight.rec_tbl = \luastring{#1}
-}}
-
-\NewDocumentCommand{\tblfrcsv}{m +m O{}}{\tblfrkv{#1}{#2}[naked_as_value=true,#3]}
-
-\NewDocumentCommand{\tblfrcsvN}{m +m O{}}{\tblfrkvN{#1}{#2}[naked_as_value=true,#3]}
-
-
-\NewDocumentCommand{\tblkvundefcheck}{}{\luadirect{penlight.check_recent_tbl_undefault()}}% check defaults list and throw error if foreign keys were used
-
-
-
-\NewDocumentCommand{\tblapp}{m m}{\luadirect{% append to a table (ie using integer index) with a value (second arg) # todo option for string or number
- __tbl__ = penlight.get_tbl_name(\luastring{#1})
- table.insert(penlight.tbls[__tbl__], \luastring{#2})
-}}
-
-
-\NewDocumentCommand{\tblappN}{m m}{\luadirect{% append to a table (ie using integer index) with a value (second arg) # todo option for string or number
- __tbl__ = penlight.get_tbl_name(\luastring{#1})
- table.insert(penlight.tbls[__tbl__], \luastringN{#2})
-}}
-
-
-\NewDocumentCommand{\tblcon}{m m}{\luadirect{% concatenate to a table (ie using integer index) with a list of comma separated values (second arg) #
- __tbl__ = penlight.get_tbl_name(\luastring{#1})
- for k, v in ipairs(penlight.luakeys.parse(string.subpar(\luastring{#2}), {naked_as_value=true})) do
- table.insert(penlight.tbls[__tbl__], v)
- end
-}}
-
-
-\NewDocumentCommand{\tblconN}{m m}{\luadirect{% concatenate to a table (ie using integer index) with a list of comma separated values (second arg) #
- __tbl__ = penlight.get_tbl_name(\luastring{#1})
- for k, v in ipairs(penlight.luakeys.parse(string.subpar(\luastringN{#2}), {naked_as_value=true})) do
- table.insert(penlight.tbls[__tbl__], v)
- end
-}}
-
-\NewDocumentCommand{\tbladd}{m m}{\luadirect{% add a kv pair to a table
- __tbl__, __key__ = penlight.get_tbl_index(\luastring{#1}, true)
- penlight.tbls[__tbl__][__key__] = \luastring{#2}
-}}
-
-\NewDocumentCommand{\tbladdN}{m m m}{\luadirect{% add a kv pair to a table
- __tbl__, __key__ = penlight.get_tbl_index(\luastring{#1}, true)
- penlight.tbls[__tbl__][__key__] = \luastringN{#2}
-}}
-
-
-
-
-\NewDocumentCommand{\tblchg}{ m }{\luadirect{% change recent table
- penlight.rec_tbl = \luastring{#1}
-}}
-
-
-\NewDocumentCommand{\tblget}{m}{\luadirect{% get an item
- penlight.get_tbl_item(\luastring{#1}, true)
-}}
-
-\NewDocumentCommand{\tblset}{m m}{\luadirect{% set item with {value}
- penlight.set_tbl_item(\luastring{#1}, \luastring{#2})
-}}
-
-\NewDocumentCommand{\tblsetN}{m m}{\luadirect{% set item with {value}
- penlight.set_tbl_item(\luastring{#1}, \luastringN{#2})
-}}
-
-
-\NewDocumentCommand{\tbldef}{ m m }{\luadirect{penlight.def_tbl(\luastring{#1}, \luastring{#2})}}
-
-% define a table, use * to make global definition
-\NewDocumentCommand{\tblgdef}{ m m }{\luadirect{penlight.def_tbl(\luastring{#1}, \luastring{#2}, 'global')}}
-
-\NewDocumentCommand{\tbldefall}{ m m }{\luadirect{penlight.def_tbl_all(\luastring{#1}, \luastring{#2})}}
-
-\NewDocumentCommand{\tbldefxy}{ m m }{\luadirect{penlight.def_tbl_coords(\luastring{#1}, \luastring{#2})}}% define #2x and #2y from a space delimited x-y pair
-
-\NewDocumentCommand{\tblif}{m m O{}}{\ifluax{penlight.get_tbl_item(\luastring{#1})}{#2}[#3]}
-
-\NewDocumentCommand{\tblifv}{m m O{}}{\ifluaxv{penlight.get_tbl_item(\luastring{#1})}{#2}[#3]}
-
-\NewDocumentCommand{\tblprt}{m}{\luadirect{penlight.wrth(penlight.get_tbl(\luastring{#1}),'penlightplus table: '..\luastring{#1})}}
-
-
-
-
-% legacy code, delete this
-\let\kvtblundefcheck\tblkvundefcheck
-
-\NewDocumentCommand{\tbladdo}{m m m}{\luadirect{% add a kv pair to a table
- __tbl__ = penlight.get_tbl_name(\luastring{#1})
- penlight.tbls[__tbl__][\luastring{#2}] = \luastring{#3}
-}}
-
-\NewDocumentCommand{\tbladdNo}{m m m}{\luadirect{% add a kv pair to a table
- __tbl__ = penlight.get_tbl_name(\luastring{#1})
- penlight.tbls[__tbl__][\luastring{#2}] = \luastringN{#3}
-}}
-
diff --git a/macros/luatex/generic/penlightplus/penlightplus.tex b/macros/luatex/generic/penlightplus/penlightplus.tex
index bc719dcd9c..2b32dce4d8 100644
--- a/macros/luatex/generic/penlightplus/penlightplus.tex
+++ b/macros/luatex/generic/penlightplus/penlightplus.tex
@@ -1,6 +1,6 @@
% Kale Ewasiuk (kalekje@gmail.com)
-% 2024-09-30
-% Copyright (C) 2021-2024 Kale Ewasiuk
+% 2025-01-01
+% Copyright (C) 2021-2025 Kale Ewasiuk
%
% Permission is hereby granted, free of charge, to any person obtaining a copy
% of this software and associated documentation files (the "Software"), to deal
@@ -66,7 +66,9 @@
\section*{Package Options and Set-Up}
-This package first loads the \cmd{[import]penlight} package---see the documentation here \url{https://lunarmodules.github.io/Penlight/index.html}.\\
+This package first loads the LaTeX \cmd{penlight[import]} package
+(\url{https://ctan.org/pkg/penlight?lang=en}).
+Documentation for the Lua package can be found here: \url{https://lunarmodules.github.io/Penlight/index.html}.\\
The \texttt{pl} option may be passed to this package to create an alias for \cmd{penlight}.\\
The following global Lua variables are defined:
@@ -78,13 +80,14 @@ The following global Lua variables are defined:
\subsubsection*{globals option}
-If the package option \cmd{globals} is used, many additional globals are set for easier scripting.
+Since this package uses the penlight \cmd{import} option,
+all \cmd{stringx} functions are injected into the \cmd{string} meta-table and you can use them like so: \cmd{'first name':upfirst()}.
+But if the package option \cmd{globals} is used, many additional globals are set for easier scripting.
\cmd{pl.hasval}, \cmd{pl.COMP}, \cmd{pl.utils.kpairs}, \cmd{pl.utils.npairs} become globals.
\cmd{pl.tablex} is aliased as \cmd{pl.tbx and tbx} (which also includes all native Lua table functions), and
-\cmd{pl.array2d} is aliased as \cmd{pl.a2d and a2d}. Since this package uses the penlight \cmd{import} option,
-all \cmd{stringx} functions are injected into the \cmd{string} meta-table and you can use them like so: \cmd{'first name':upfirst()}.
+\cmd{pl.array2d} is aliased as \cmd{pl.a2d and a2d}.
-If you want global \cmd{pl.tex} functions and variables, call \cmd{pl.make_tex_global()}.\\
+%If you want global \cmd{pl.tex} functions and variables, call \cmd{pl.make_tex_global()}.\\
\subsection*{texlua usage}
@@ -115,6 +118,8 @@ Some functionality is added to penlight and Lua.
\llcmd{pl.hasval(x)} Python-like boolean testing\\
\llcmd{COMP'xyz'()} Python-like comprehensions:\\\url{https://lunarmodules.github.io/Penlight/libraries/pl.comprehension.html}\\
+\llcmd{_Gdot(s)} Return a global (may contain dots) from string
+
\cmd{clone_function(f)} returns a cloned function\\
\cmd{operator.strgt(a,b)} compares strings a greater than b (useful for sorting)\\
\cmd{operator.strlt(a,b)} compares strings a less than b (useful for sorting)\\
@@ -131,9 +136,15 @@ Some functionality is added to penlight and Lua.
\subsection*{string additions}
+\begin{luacode*}
+ pl.wrth(('a = 1, b =2 '):split2('=',',',false))
+\end{luacode*}
+
\llcmd{string.}\cmd{upfirst(s)} uppercase first letter\\
\llcmd{string.}\cmd{delspace(s)} delete all spaces\\
\llcmd{string.}\cmd{trimfl(s)}remove first and last chars\\
+\llcmd{string.}\cmd{splitstrip(s, sp, st)} split by sp (default comma) followed by strip (default whitespace)\\
+\llcmd{string.}\cmd{split2(s, sep1, sep2, st)} split a string twice (creates a 2d array), first by sep1 (default comma), then by sep2 (default =), with option to strip (default true)\\
\llcmd{string.}\cmd{appif(s, append, bool, alternate)}\\
\llcmd{string.}\cmd{gfirst(s, t)}return first matched patter from an array of patterns t\\
%\llcmd{string.}\cmd{gnum(s)} extract a number from a string\\
@@ -148,27 +159,48 @@ Some functionality is added to penlight and Lua.
a few improvements. \cmd{t} can be an array (reference items like \cmd{\$1} in the string),
and \cmd{fmt} can be a table of formats (keys correspond to those in \cmd{t}), or a string that
is processed by luakeys.\\
-\llcmd{string.}\cmd{parsekv(s, opts)} parse a string using \cmd{penlight.luakeys}. A string or table can be used for opts.
+\llcmd{string.}\cmd{parsekv(s, opts)} parse a string using a \cmd{luakeys} instance (\cmd{penlight.luakeys}). A kv-string or table can be used for opts.\\
+\llcmd{string.}\cmd{hasnoalpha(s)} string has no letters\\
+\llcmd{string.}\cmd{hasnonum(s)} string has no numbers\\
+\llcmd{string.}\cmd{isvarlike(s)} string is 'variable-like', starts with a letter or underscore and then is alphanumeric or has underscores after \\
+\begin{luacode*}
+ pl.wrth(('_'):isvarlike(), 'llll')
+ pl.wrth(('1_1k'):isvarlike(), ',,')
+ pl.wrth(('kale_1'):isvarlike(), '')
+ pl.wrth(('kale_1'):isvarlike(), '')
+\end{luacode*}
\subsection*{tablex additions}
\llcmd{tablex.}\cmd{fmt(t, f)} format a table with table or key-value string f\\
+\llcmd{tablex.}\cmd{list2comma(t)} Use oxford comma type listing, e.g. A, B, and C\\
\llcmd{tablex.}\cmd{strinds(t)} convert integer indexes to string indices (1 -> '1')\\
\llcmd{tablex.}\cmd{filterstr(t,e,case)} keep only values in table t that contain expression e, case insensitive by default.\\
\llcmd{tablex.}\cmd{mapslice(f,t,i1,i2)} map a function to elements between i1 and i2\\
\llcmd{tablex.}\cmd{listcontains(t,v)} checks if a value is in a array-style list \\
+\llcmd{tablex.}\cmd{kkeys(t)} returns keys that are non-numeric (like kpairs) \\
+
+\subsection*{List additions}
+\llcmd{List:}\cmd{inject(l2, pos)} injects a list (l2) into a list at position. Set pos=0 to inject at end.
+\begin{luacode*}
+ l = pl.List{1,2,3,4,5}
+ pl.wrth(l:inject({'a','b','c'},0), 'INJECTED')
+\end{luacode*}
\subsubsection*{seq additions}
-A syntax to produce sequences or a 'train' of numbers is provided. This may be useful for including pages from a pdf, or selecting rows of a table with a concise syntax.
-\cmd{seq.train(trn, len)} produces a pl.List according to the arguments (like choo-choo train)\\
-\cmd{seq.itrain(trn, len)} produces an iterator according to the arguments.
+A syntax to produce sequences or a 'train' of numbers is provided. This may be useful for including pages from a pdf, or selecting rows of a table with a concise syntax.\\
+\llcmd{seq.}\cmd{train(trn, len)} produces a pl.List according to the arguments\\
+\llcmd{seq.}\cmd{itrain(trn, len)} produces an iterator according to the arguments.\\
+\llcmd{seq.}\cmd{tbltrain(tbl, trn)} produces an iterator over a table
An example syntax for \cmd{trn} is \cmd{'i1, i2, r1:r2', etc.} where \cmd{i1} and \cmd{i2} are individual indexes/elements, separated by \cmd{,} and
\cmd{r1:r2} is a range (inclusive of end-point) denoted with a \cmd{:}. The range format follows python's numpy indexing, and
a 'stride' can be given by including a second colon like \cmd{::2 -> is 1,3,5,...}, or \cmd{2::3 -> 2,5,8,...}.
Negative numbers can be used to index relative to the length of the table, eg, \cmd{-1 -> len}, but
if length is not given, negative indexing cannot be used and a number after the first colon must be provided.
-A missing left-number on the colon assumes \cmd{1}, and missing right number assumes \cmd{len}. A missing 'stride' (number after the optional second colon) assumes a value of 1.
+A missing left-number on the colon assumes \cmd{1}, and missing right number assumes \cmd{len}. A missing 'stride' (number after the optional second colon) assumes a value of 1.\\
+Variable-like strings can be given in place of numbers, which are assumed to be keys for a table instead.\\
+For \cmd{tbltrain} a \cmd{*} can be passed to iterate over all keys.
The default colon and comma separators for ranges and elements can be set with \cmd{seq.train_range_sep} and \cmd{seq.train_element_sep}, respectively.
\begin{LTXexample}[width=0.5\linewidth]
@@ -178,6 +210,11 @@ The default colon and comma separators for ranges and elements can be set with \
5) do
tex.print(i..',')
end
+ local t = {'n1','n2',a='A',b='B',c='C'}
+ for k, v in
+ pl.seq.tbltrain(t, '*,c,1') do
+ tex.print(tostring(k)..'='..tostring(v)..'; ')
+ end
\end{luacode*}
\end{LTXexample}
@@ -187,6 +224,7 @@ The default colon and comma separators for ranges and elements can be set with \
\llcmd{add_bkt}\cmd{_cnt(n), }\cmd{close_bkt_cnt(n), reset_bkt_cnt} functions to keep track of adding curly brackets as strings. \cmd{add} will return \cmd{n} (default 1) \{'s and increment a counter. \cmd{close} will return \cmd{n} \}'s (default will close all brackets) and decrement.\\
\llcmd{_NumBkts} internal integer for tracking the number of brackets\\
\llcmd{opencmd(cs)} prints \cmd{\cs}\{ and adds to the bracket counters.\\
+\cmd{openenv(env,opts)} prints a \cmd{\begin{env}[opts]}, and stores the enironment in a list so it can be later closed with \cmd{closeenv{num}}
\\
\llcmd{xNoValue,}\cmd{xTrue,xFalse}: \cmd{xparse} equivalents for commands\\
\\
@@ -207,6 +245,7 @@ The default colon and comma separators for ranges and elements can be set with \
\llcmd{deccmd}\cmd{(cs, dft, overwrite)} declare a command. If \cmd{dft} (default) is \cmd{nil}, \cmd{cs} is set
to a package warning saying \cmd{'cs' was declared and used in document, but never set}. If \cmd{overwrite}
is true, it will overwrite an existing command (using \cmd{defcmd}), otherwise, it will throw error like \cmd{newcmd}.\\
+\\
\llcmd{get_ref_info(l)}accesses the \cmd{\r@label} and returns a table\\
@@ -268,7 +307,8 @@ For example, we can control the expansion of args 2 and 3 with arg 1:
\subsection*{Lua boolean expressions}
-\cmd{\ifluax{<Lua expr>}{<do if true>}[<do if false>]} and\\ \cmd{\ifluax{<Lua expr>}{<do if true>}[<do if false>]} for truthy (uses \cmd{penlight.hasval})
+\cmd{\ifluax{<Lua expr>}{<do if true>}[<do if false>]} and\\
+\cmd{\ifluaxv{<Lua expr>}{<do if true>}[<do if false>]} for truthy (uses \cmd{penlight.hasval}). The argument is expanded.
\begin{LTXexample}[width=0.3\linewidth]
\ifluax{3^3 == 27}{3*3*3 is 27}[WRONG]\\
@@ -276,12 +316,16 @@ For example, we can control the expansion of args 2 and 3 with arg 1:
\ifluax{not true}{tRuE}[fAlSe]\\
\ifluax{''}{TRUE}[FALSE]\\
\ifluaxv{''}{true}[false]\\
+\def\XXX{8}
+\ifluax{\XXX == 8}{Yes}[No]
\end{LTXexample}
\subsection*{Case-switch for Conditionals}
\cmd{\caseswitch{case}{kev-val choices}} The starred version will throw an error if the case is not found.
-Use \_\_ as a placeholder for a case that isn't matched.
+Use \_\_ as a placeholder for a case that isn't matched. The case is fully expanded and interpreted as a lua string.
+
+%TODO consider a case switch wth placeholder expressions? that would be cool. \_1 == 8 for example.
\begin{LTXexample}[width=0.3\linewidth]
\def\caseswitchexample{\caseswitch{\mycase}{dog=DOG, cat=CAT, __=INVALID}}
@@ -291,170 +335,13 @@ Use \_\_ as a placeholder for a case that isn't matched.
%\caseswitch*{\mycase}{dog=DOG, cat=CAT, __=INVALID}
-\subsection*{Creating and using Lua tables in LaTeX - tbl interace}
-
-\cmd{penlightplus} provides a Lua-table interface. Tables are stored in the
-\cmd{penlight.tbls} table.
-You can access a table item within lua by using:
-\cmd{penlight.tbl'i'}.
-
-%%%
-
-\cmd{\tblnew{t}} declares a new table with name \cmd{t}\\
-\cmd{\tblchg{t}} changes the 'recent' table\\
-\\
-\cmd{\tblfrkv{t}{key-val string}[luakeys opts]} new table from key-vals using \cmd{luakeys} \\
-\cmd{\tblfrkvN{t}{key-val string}[luakeys opts]} does not expand key-val string \cmd{luakeys} \\
-\cmd{\tblfrkvCD{t}{key-val string}[luakeys opts]} define tbl from key-val,
-check if any were not defined as defaults (see below), and then push all to definitions\\
-\\
-\cmd{\tblkvundefcheck} will throw an error if you use define a table from key-values
-and use a key that was not specified in the luakeys parse options via \cmd{opts.defaults} or \cmd{opts.defs}.\\
-\\
- \cmd{\tblfrcsv{t}{csv}} a shorthand \cmd{\tblfrkv{t}{csv}[naked_as_value=true,opts]}, a good way to convert a comma-separated list to an array\\
- \cmd{\tblfrcsvN{t}{csv}} same as above, but the csv is not expanded.\\
-\\
-\cmd{\tblset{i}{v}} sets a value of the table/index \cmd{i} to \cmd{v}\\
-\cmd{\tblsetN{i}{v}} same as above, but the value is not expanded.\\
-\\
-\cmd{\tblget{i}} gets the value and \cmd{tex.sprint()}s it\\
-\\
-\cmd{\tbladd{i}{v}} add a new value to a table using index method\\
-\cmd{\tbladdN{i}{v}} above, but don't expand the value argument\\
-\\
-\cmd{\tblcon{t}{csv}} concatenate an array-style csv\\
-\cmd{\tblconN{t}{csv}}\\
-\\
-\cmd{\tblapp{t}{v}} append a value (integer-wise) to a table\\
-\cmd{\tblappN{t}{v}}\\
-\\
-\cmd{\tbldef{i}{d}} pushes the value to macro \cmd{d}\\
-\cmd{\tbldefall{t}{d}} define all item in table \cmd{t} (use recent if blank) with format \cmd{d<key>} where d is your prefix. If d is blank, keys will be defined as \cmd{\dtbl<t><k>}
-\cmd{\tblgdef{i}{d}} pushes the defined value to a global\\
-\cmd{\tbldefxy{i}{d}} splits the value of item by spaces creates two definitions \cmd{\dx} and \cmd{\dy}. Useful for pasing tikz coordinates like \cmd{xy=0 5}\\
-For definiting tables, if \cmd{d} is blank, commands are defined as \cmd{dtbl<t><k>}\\
-\\
-\cmd{\iftbl{i}{tr}[fa]} runs code \cmd{ta} if the item is true else \cmd{fr}\\
-\cmd{\iftblv{i}{tr}[fa]} runs code \cmd{ta} if the item is truthy (using \cmd{pl.hasval}) else \cmd{fr}\\
-
-\cmd{\tblprt{t}} print the table in console
-
-There are 3 ways to use the index (placeholder \cmd{{i}} above, note that this argument is fully expanded).
-\cmd{t.key} where \cmd{t} is the table name and \cmd{key} is a string key,
-\cmd{t/int} where \cmd{int} is an integer index (ie. uses \cmd{t[int]}, note that negative indexes are allowered where -1 is the last element),
-or simply use \cmd{ind} without the table name, where the assumed table is the last one that was created or changed to, (passing a number will used as an integer index).
-\enlargethispage{2em}
-\begin{LTXexample}[width=0.3\linewidth]
-\tblfrkv{my}{a,b,c,first=john,last=smith}%
- [defaults={x=0,1=one,n=false,y=yes}]
-\tblget{my.a}\\
-\tblset{a}{tRuE!!}
-\tblget{a}\\
-\tblget{my.x}\\
-\tblget{.x}\\
-\tbladd{my.newkey}{val}\tblget{newkey}\\
-\tbladd{nk}{VAL}\tblget{nk}\\
-\tblif{n}{tr}[fa]\\
-\tblifv{n}{TR}[FA]\\
-\tblif{my.y}{Tr}[Fa]\\
-\tblifv{y}{tR}[fA]\\
-%% \kvtblundefcheck % would throw error
-\tbldef{my.first}{mydef} \mydef\\
-\tbldef{first}{}\dtblmyfirst\\
-{\tbldef{last}{mydef} \mydef} \mydef\\
-{\tblgdef{last}{mydef}} \mydef\\
-
-\tbldefall{}{}\dtblmyfirst\\
-\tbldefall{my}{DEF}\DEFfirst
-
-\tblset{my.a}{12 36}
-\tbldefxy{my.a}{coord} (\coordx,\coordy)
-\tbldefxy{my.a}{} (\dtblmyax,\dtblmyay)
-\tbldefxy{a}{} (\dtblmyax,\dtblmyay)
-
-\tblfrcsv{me}{a,b,"c,see",d,e}
-\tblget{me/1},\tblget{2}\\
-\tblget{3}\\
-\tblset{me/4}{D}\tblget{me/4}\tblget{/4}\\
-\tblset{5}{E}\tblget{5}\\
-\tblget{-2},\tblget{me/-1}\\
-\tblget{/-3}\\
-%% \tblget{k} % would throw error
-
-\tblfrkvCD{M}{a=A,b=B,d=D}[defaults={a,b,c,d}]
-\dtblMa \dtblMb \dtblMc \dtblMd
-
-\end{LTXexample}
-
-
-%\tblfrcsv{me}{
-%Hello=world,
-%
-%Bonjour=terre,
-%}
-%\tblget{Hello}
-%\tblget{Bonjour}
-
-
-
-
-\subsubsection*{A practical tbl example}
-
-\begin{LTXexample}
-\begin{luacode*}
- function prt_pyth()
- t = pl.tbls.pyth
- if not t.a then
- pl.tex.pkgerror('must pass a= to \\pyth')
- elseif not t.b then
- t.b = (tonumber(t.c)^2 -
- tonumber(t.a)^2)^0.5
- elseif not t.c then
- t.c = (tonumber(t.a)^2 +
- tonumber(t.b)^2)^0.5
- end
- local t = pl.tbx.fmt(t,'.'..t.d..'f') -- format table according to d decimals
- s = 'Right-angle sides a=$a and b=$b form a hypotenuse of c=$c'
- pl.tex.prt(s:fmt(t))
- end
-\end{luacode*}
-\NewDocumentCommand{\pyth}{m}{%
- \tblfrkv{pyth}{#1}[defaults={a=false,b=false,c=false,d=0,e=extras}]
- \luadirect{prt_pyth()}%
-}
-
-\pyth{a=3,c=5}\\
-\pyth{a=3.2,b=4.2,d=2}\\
-C: \tblget{c}
-
-\end{LTXexample}
-
-
-
-\subsection*{Splitting strings}
-Splitting text (or a cmd) into oxford comma format via:
-\cmd{\splittocomma[expansion level]{text}{text to split on}}:
+\subsection*{Create, modify, and use Lua tables from within LaTeX}
+This portion of the package has been moved to a another package called
+\cmd{luatbls} \\(\url{https://ctan.org/pkg/luatbls})
-\begin{LTXexample}[width=0.3\linewidth]
--\splittocomma{ j doe }{\and}-\\
--\splittocomma{ j doe \and s else }{\and}-\\
--\splittocomma{ j doe \and s else \and a per }{\and}-\\
--\splittocomma{ j doe \and s else \and a per \and f guy}{\and}-
-\def\authors{j doe \and s else \and a per \and f guy}
-\splittocomma[o]{\authors}{\and}
-\end{LTXexample}
-The expansion level is up to two characters, \cmd{n|o|t|f}, to control the expansion of each argument.
-You can do a similar string split but to \cmd{\item} instead of commas with \cmd{\splittoitems}
-\begin{LTXexample}
-\begin{itemize}
- \splittoitems{kale\and john}{\and}
- \splittoitems{kale -john -someone else}{-}
- \splittoitems{1,2,3,4}{,}
-\end{itemize}
-\end{LTXexample}
@@ -517,57 +404,43 @@ This might be useful if you're updating \cmd{__PDFmetadata__} by some other mean
-%
-%\pyth{} % erro
-%\begin{luacode*}
-% pl.tex.wrth(('a,b,c=3'):parsekv('!naked_as_value'), 'dumy')
-%\end{luacode*}
+%\writePDFmetadatakv{datxe=2024-02-01} % would throw error, invalid key
+%\writePDFmetadatakv[E]{keywords=\thekeywords} %
-% \tblfrkv{tbl_def}{kale=cool,paul=gay,craig=fun}
-% \tblfrkv{tbl}{kale,paul=gay} %[naked_as_value=true]
-%
-% \tblget{tbl}{kale}%
-% \tblget{tbl}{paul}%
-% \tblget{tbl}{craig}%
-%
-% \tblupd{tbl_def}{tbl}%
-%
-% \tblfrkvII{tbl}{kale=cool,paul=gay,craig=fun}{kale=weak,paul=sad}
-% \tblget{tbl}{craig}%
-% \tblget{tbl}{paul}%
-% \tblget{tbl}{kale}%
+%\luadirect{texio.write_nl(\luastringE{keywords=\thekeywords})}
+
+
+
+
+%%%%% SAND BOX
%
-% \NewDocumentCommand{\THINg}{ O{} m}{%
-% \tblfrkvII{setti}{color=red,size=small}{#1} % make settings and update based on [arg=]
-% {\color{\tblget{setti}{color}}\tblget{setti}{size} #2}
-% }
+%\xtokcycleenvironment\luastringAenv
+% {\addcytoks{##1}}
+% {\processtoks{##1}}
+% {\addcytoks[1]{##1}}
+% {\addcytoks{##1}}
+% {}
+% {\cytoks\expandafter{\expandafter\luastringO\expandafter{\the\cytoks}}}
%
-% \THINg[color=blue,size=tiny]{Kale}
+%\def\luastringA#1{\luastringAenv#1\endluastringAenv}
+%\def\zzz{Hi Mom}
+%\NewDocumentCommand{\testcommand}{m}{%
+% \luastringA{#1} % this works as requested :)
+%% \luadirect{texio.write_nl(\luastringA{#1}..' <<<')}
+% }
%
+%\luastringA{Hmmm. \zzz.}
+%\testcommand{Hmmm. \zzz.}
+
%
-%\begin{luacode*}
-%function prttol()
-% local dec = penlight.tbls.tol[4] or 1
-% penlight.wrth(dec,'??')
-% penlight.tbls.tol[3] = penlight.tbls.tol[3] or 3
-% penlight.tbls.tol[4] = penlight.tbls.tol[1]*(1.0-penlight.tbls.tol[3]/100.0) + 0.0
-% penlight.tbls.tol[5] = penlight.tbls.tol[1]*(1.0+penlight.tbls.tol[3]/100.0) + 0.0
-% --penlight.tbls.tol['k'] = 'fuckboi'
-% --ttt = pl.tbx.fmt(penlight.tbls.tol, '.3f')
-% penlight.wrth(('$1\\$2 (\\pmpct{$3} tolerance, $4\\ndash$5\\$2)'):fmt(penlight.tbls.tol, '4=.'..dec..'f, 5=.'..dec..'f'), 'XYZ')
-%end
-%\end{luacode*}
-%\NewDocumentCommand{\prttol}{ m }{\tblfrcsv{tol}{#1}\luadirect{prttol()}}% {50.0,kV,3,P} % 50\us (\pmpct{20} tolerance, 40=--60\us), P is optional and precision of the range (number of decimals)
-%
-%\prttol{50,kV,3}
-%
-%\begin{luacode*}
-% pl.wrth(pl.filterfiles('.',true,'.*%.tex'), 'FF')
-%\end{luacode*}
+
+%\pyth{} % erro
+
+
\end{document} \ No newline at end of file
diff --git a/macros/luatex/latex/luatbls/README.md b/macros/luatex/latex/luatbls/README.md
new file mode 100644
index 0000000000..b2fc6681de
--- /dev/null
+++ b/macros/luatex/latex/luatbls/README.md
@@ -0,0 +1,27 @@
+# luatbls
+
+Lua tables made accessible in LaTeX
+
+# License
+
+Copyright (C) 2025 Kale Ewasiuk
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/macros/luatex/latex/luatbls/luatbls.lua b/macros/luatex/latex/luatbls/luatbls.lua
new file mode 100644
index 0000000000..a14310fc6d
--- /dev/null
+++ b/macros/luatex/latex/luatbls/luatbls.lua
@@ -0,0 +1,310 @@
+--% Kale Ewasiuk (kalekje@gmail.com)
+--% 2025-01-01
+--% Copyright (C) 2025 Kale Ewasiuk
+--%
+--% Permission is hereby granted, free of charge, to any person obtaining a copy
+--% of this software and associated documentation files (the "Software"), to deal
+--% in the Software without restriction, including without limitation the rights
+--% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+--% copies of the Software, and to permit persons to whom the Software is
+--% furnished to do so, subject to the following conditions:
+--%
+--% The above copyright notice and this permission notice shall be included in
+--% all copies or substantial portions of the Software.
+--%
+--% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+--% ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+--% TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+--% PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+--% SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+--% ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+--% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+--% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+--% OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+local luatbls = {}
+
+luatbls._luakeys = require'luakeys'()
+
+luatbls._rec_tbl = ''
+luatbls._rec_tbl_opts = {}
+
+luatbls._xysep = '%s+' -- spaces separate x-y coordinates
+luatbls._tblv = '<v>'
+luatbls._tblk = '<k>'
+luatbls._cspfx = 'dtbl'
+luatbls._debug = false
+
+function luatbls._dprint(s1, s2)
+ if luatbls._debug then
+ penlight.wrth(s1, s1)
+ end
+end
+
+
+setmetatable(luatbls, {__call=function(t,s)return t._get_tbl_item(s) end})
+
+function luatbls._get_tbl_name(s)
+ s = s:strip()
+ if s == '' then
+ return luatbls._rec_tbl
+ end
+ for _, delim in ipairs{'.', '/', '|'} do
+ s = s:split(delim)[1] -- if tbl reference had a . | or /, an indexer was used
+ end
+ if luatbls[s] == nil then
+ local validtbls = penlight.List(penlight.tablex.kkeys(luatbls)):filter(function(s) return not s:startswith('_') end):join(', ')
+ penlight.tex.pkgerror('luatbls', 'Tried to access undefined tbl: "'..s..'". Valid tbls are: '..validtbls)
+ return luatbls._rec_tbl
+ end
+ return s
+end
+
+function luatbls._get_tbl(s)
+ s = luatbls._get_tbl_name(s)
+ return luatbls[s]
+end
+
+function luatbls._get_tbl_index(s, undec)
+ undec = undec or false -- flag for allowing undeclared indexing
+ local tbl = ''
+ local key = ''
+ local s_raw = s
+ if s:find('%.') then
+ local tt = s:split('.')
+ tbl = tt[1]
+ key = tt[2]
+ elseif s:find('/') then
+ local tt = s:split('/')
+ tbl = tt[1]
+ if tbl == '' then tbl = luatbls._rec_tbl end
+ key = tonumber(tonumber(tt[2]))
+ if key < 0 then key = #luatbls[tbl]+1+key end
+ else
+ tbl = luatbls._rec_tbl
+ key = tonumber(s) or s
+ if type(key) == 'number' and key < 0 then key = #luatbls[tbl]+1+key end
+ end
+ if tbl == '' then tbl = luatbls._rec_tbl end
+
+ if (luatbls[tbl] == nil) or ((not undec) and (luatbls[tbl][key] == nil)) then
+ penlight.tex.pkgerror('luatbls', 'Invalid tbl index attempt using: "'..s_raw..'". We tried to use tbl="' ..tbl..'" and key="'..key..'"'..
+ 'Note that "|" is forbidden here. The recent table is: '..luatbls._rec_tbl)
+ end
+ return tbl, key
+end
+
+function luatbls._get_tbl_seq(s)
+ local tblseq = s:split('|')
+ local tbl = nil
+ local seq = nil
+ if #tblseq == 1 then
+ tbl = luatbls._get_tbl_name('')
+ seq = tblseq[1]
+ if seq == '' then seq = ':,*' end
+ elseif #tblseq == 2 then
+ tbl = luatbls._get_tbl_name(tblseq[1])
+ seq = tblseq[2]
+ if seq == '' then seq = ':,*' end
+ end
+ return tbl, seq
+end
+
+
+
+function luatbls._get_tbl_item(s, p) -- get item with string, p means print value
+ p = p or false
+ local tbl, key = luatbls._get_tbl_index(s)
+ local itm = luatbls[tbl][key]
+ if p then
+ tex.sprint(tostring(itm))
+ end
+ return itm
+end
+
+
+function luatbls._set_tbl_item(s, v)
+ tbl, key = luatbls._get_tbl_index(s)
+ luatbls[tbl][key] = v
+end
+
+function luatbls._check_recent_tbl_undefault()
+ local undefaults = {}
+ if luatbls._rec_tbl_opts ~= nil then
+ local defaults = penlight.tablex.union(
+ luatbls._rec_tbl_opts.defs or {},
+ luatbls._rec_tbl_opts.defaults or {}
+ )
+ for k, v in pairs(luatbls[luatbls._rec_tbl]) do
+ if defaults[k] == nil then
+ undefaults[#undefaults+1] = k
+ end
+ end
+ if penlight.hasval(undefaults) then
+ penlight.tex.pkgerror('luatbls',
+ 'Invalid keys passed to tbl keyval: ' .. (', '):join(undefaults) ..
+ ' ; choices are: ' .. (', '):join(penlight.tablex.keys(defaults))
+ )
+ end
+ end
+end
+
+function luatbls._make_alpha_key(k)
+ if tonumber(k) ~= nil then
+ k = penlight.Char(tonumber(k))
+ end
+ return k
+end
+
+function luatbls._def_tbl(ind, def, g)
+ local _tbl, _key = luatbls._get_tbl_index(ind)
+ if def == '' then def = luatbls._cspfx.._tbl..luatbls._make_alpha_key(_key) end
+ luatbls._def_tbl_one(luatbls[_tbl][_key], def, g)
+end
+
+
+function luatbls._def_tbl_some(tblseq, def, g)
+ for t, k, v in luatbls._iter_tbls_vals(tblseq) do
+ if def == '' then def = luatbls._cspfx..t end
+ local def2 = def .. luatbls._make_alpha_key(k)
+ luatbls._def_tbl_one(v, def2, g)
+ end
+end
+
+function luatbls._def_tbl_one(v, cs, g)
+ if type(v) == 'table' then
+ for kk, vv in pairs(v) do
+ kk = luatbls._make_alpha_key(kk)
+ token.set_macro(cs..kk, tostring(vv), g)
+ end
+ else
+ token.set_macro(cs, tostring(v), g)
+ end
+end
+
+function luatbls._def_tbl_coords(ind, def)
+ local tbl, key = luatbls._get_tbl_index(ind)
+ local str = luatbls[tbl][key]
+ if def == '' then def = luatbls._cspfx..tbl..key end
+ local x, y = str:strip():splitv(luatbls._xysep)
+ if (not penlight.hasval(x)) or (not penlight.hasval(y)) then
+ penlight.tex.pkgerror('luatbls', '_def_tbl_coords function could not parse coordiantes given as "'..str..'" ensure two numbers separated by space are given!', '', true)
+ end
+ token.set_macro(def..'x', tostring(x))
+ token.set_macro(def..'y', tostring(y))
+end
+
+
+function luatbls._for_tbl_prt(k, v,cmd)
+ local cmd_new = cmd:gsub(luatbls._tblv, tostring(v)):gsub(luatbls._tblk, tostring(k)):gsub('(\\%w+) ', '%1') -- for some reason a space gets added to \cs, maybe
+ luatbls._dprint(cmd_new, '_for_tbl replacement')
+ tex.sprint(cmd_new)
+end
+
+function luatbls._for_tbl(tblseq, cmd)
+ for t, k, v in luatbls._iter_tbls_vals(tblseq) do
+ luatbls._for_tbl_prt(k, v,cmd)
+ end
+end
+
+function luatbls._for_tbl_e(tbl, cmd)
+ for k, v in pairs(tbl) do
+ luatbls._for_tbl_prt(k, v,cmd)
+ end
+end
+
+
+function luatbls._iter_tbls_vals(s)
+ if s:find('|') or ((s:find('%.') == nil) and (s:find('/') == nil)) then
+ local tbl, seq = luatbls._get_tbl_seq(s)
+ local keyval = {}
+ for key, val in penlight.seq.tbltrain(luatbls._get_tbl(tbl), seq) do
+ -- todo this should check validity of keys for sequences
+ keyval[#keyval+1] = {key, val}
+ end
+ luatbls._dprint(keyval, 'luatbls._iter_tbls_vals is iterating through tbl: '..tbl..' with sequence: '..seq)
+ local i = 0
+ return function()
+ i = i + 1
+ if i <= #keyval then
+ return tbl, keyval[i][1], keyval[i][2]
+ end
+ end
+ else
+ local tbl, key = luatbls._get_tbl_index(s)
+ local val = luatbls[tbl][key]
+ luatbls._dprint(keyval, 'luatbls._iter_tbls_vals is using tbl: '..tbl..' with key: '..key)
+ local i = 1
+ return function()
+ if i == 1 then
+ i = i + 1 -- only return the single value
+ return tbl, key, val
+ end
+ end
+ end
+end
+
+function luatbls._make_args(s, key, val)
+ local args = {val}
+ if s == nil then
+ return args
+ end
+ s = s:split(')')[1]
+ args = luatbls._luakeys.parse(s, {naked_as_value=true})
+ for i, v in ipairs(args) do
+ if v == luatbls._tblv then
+ args[i] = val
+ elseif v == luatbls._tblk then
+ args[i] = key
+ end
+ end
+ return args
+end
+
+function luatbls._make_func(f, key, val)
+ f = f:strip()
+ fargs = f:split('(')
+ f = fargs[1]
+ args = luatbls._make_args(fargs[2], key, val)
+ if f:startswith(':') then
+ f = f:sub(2,-1)
+ if type(val) == 'string' then
+ f = string[f]
+ elseif type(val) == 'table' then
+ f = penlight.tablex[f]
+ end
+ else
+ f = penlight._Gdot(f)
+ end
+ --luatbls._dprint() -- todo more debug printing
+ return f, args
+end
+
+
+
+function luatbls._make_newtbl(tblind, newtbl)
+ if tblind == '' then tblind = luatbls._rec_tbl..'|' end
+ if newtbl ~= '' then -- determine if new tbl is needed
+ local ogtbl = luatbls._get_tbl_name(tblind)
+ luatbls[newtbl] = penlight.tablex.deepcopy(luatbls[ogtbl])
+ luatbls._rec_tbl = newtbl
+ tblind, _ = string.gsub(tblind, ogtbl, newtbl, 1)
+ end
+ return tblind
+end
+
+function luatbls._apply_tbl(tblind, func, newtbl)
+ tblind = luatbls._make_newtbl(tblind, newtbl)
+ for _, f in pairs(func:split('|')) do
+ for tbl, key, val in luatbls._iter_tbls_vals(tblind) do
+ local thefunc, args = luatbls._make_func(f, key, val)
+ if thefunc == nil then
+ penlight.tex.pkgerror('luatbls', 'Tried to apply function: "'..f..'" to tbl value. It yielded no function. Ensure : is used for self-methods')
+ end
+ luatbls[tbl][key] = thefunc(penlight.utils.unpack(args))
+ end
+ end
+end
+
+return luatbls \ No newline at end of file
diff --git a/macros/luatex/latex/luatbls/luatbls.pdf b/macros/luatex/latex/luatbls/luatbls.pdf
new file mode 100644
index 0000000000..e8bc8a1694
--- /dev/null
+++ b/macros/luatex/latex/luatbls/luatbls.pdf
Binary files differ
diff --git a/macros/luatex/latex/luatbls/luatbls.sty b/macros/luatex/latex/luatbls/luatbls.sty
new file mode 100644
index 0000000000..cbbd5377a1
--- /dev/null
+++ b/macros/luatex/latex/luatbls/luatbls.sty
@@ -0,0 +1,163 @@
+% Kale Ewasiuk (kalekje@gmail.com)
+% 2025-01-01
+% Copyright (C) 2025 Kale Ewasiuk
+%
+% Permission is hereby granted, free of charge, to any person obtaining a copy
+% of this software and associated documentation files (the "Software"), to deal
+% in the Software without restriction, including without limitation the rights
+% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+% copies of the Software, and to permit persons to whom the Software is
+% furnished to do so, subject to the following conditions:
+%
+% The above copyright notice and this permission notice shall be included in
+% all copies or substantial portions of the Software.
+%
+% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+% ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+% TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+% PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+% SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+% ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+% OR OTHER DEALINGS IN THE SOFTWARE.
+
+\RequirePackage{luacode}
+\RequirePackage{luakeys}
+\RequirePackage{penlightplus}
+
+
+\luadirect{luatbls = require'luatbls'}
+
+
+\NewDocumentCommand{\tblnew}{m}{\luadirect{% initialize a tbl and set empty
+ luatbls[\luastring{#1}] = {}
+ luatbls._rec_tbl = \luastring{#1}
+}}
+
+
+\NewDocumentCommand{\tblchg}{ m }{\luadirect{% change recent table
+ luatbls._rec_tbl = luatbls._get_tbl_name(\luastring{#1})
+}}
+
+\NewDocumentCommand{\tblfrkv}{m +m O{}}{\luadirect{% parse a tbl from key-vals, naked are set to boolean
+ luatbls._rec_tbl_opts = luatbls._luakeys.parse(\luastring{#3})
+ luatbls[\luastring{#1}] = luatbls._luakeys.parse(string.subpar(\luastring{#2}), luatbls._rec_tbl_opts)
+ luatbls._rec_tbl = \luastring{#1}
+}}
+
+\NewDocumentCommand{\tblfrkvN}{m +m O{}}{\luadirect{%
+ luatbls._rec_tbl_opts = luatbls._luakeys.parse(\luastring{#3})
+ luatbls[\luastring{#1}] = luatbls._luakeys.parse(string.subpar(\luastringN{#2}), luatbls._rec_tbl_opts)
+ luatbls._rec_tbl = \luastring{#1}
+}}
+
+\NewDocumentCommand{\tblfrcsv}{m +m O{}}{\tblfrkv{#1}{#2}[naked_as_value=true,#3]}% naked are set to array
+
+\NewDocumentCommand{\tblfrcsvN}{m +m O{}}{\tblfrkvN{#1}{#2}[naked_as_value=true,#3]}
+
+\NewDocumentCommand{\tblkvundefcheck}{}{\luadirect{luatbls._check_recent_tbl_undefault()}}% check defaults list and throw error if foreign keys were used
+
+
+
+\NewDocumentCommand{\tblapp}{m m}{\luadirect{% append to a table (ie using integer index) with a value (second arg)
+ __tbl__ = luatbls._get_tbl_name(\luastring{#1})
+ table.insert(luatbls[__tbl__], \luastring{#2})
+}}
+
+
+\NewDocumentCommand{\tblappN}{m m}{\luadirect{% append to a table (ie using integer index) with a value (second arg) #
+ __tbl__ = luatbls._get_tbl_name(\luastring{#1})
+ table.insert(luatbls[__tbl__], \luastringN{#2})
+}}
+
+
+\NewDocumentCommand{\tblcon}{m m}{\luadirect{% concatenate to a table (ie using integer index) with a list of comma separated values (second arg) #
+ __tbl__ = luatbls._get_tbl_name(\luastring{#1})
+ for k, v in ipairs(luatbls._luakeys.parse(string.subpar(\luastring{#2}), {naked_as_value=true})) do
+ table.insert(luatbls[__tbl__], v)
+ end
+}}
+
+
+\NewDocumentCommand{\tblconN}{m m}{\luadirect{% concatenate to a table (ie using integer index) with a list of comma separated values (second arg) #
+ __tbl__ = luatbls._get_tbl_name(\luastring{#1})
+ for k, v in ipairs(luatbls._luakeys.parse(string.subpar(\luastringN{#2}), {naked_as_value=true})) do
+ table.insert(luatbls[__tbl__], v)
+ end
+}}
+
+
+\NewDocumentCommand{\tbladd}{m m}{\luadirect{% add a kv pair to a table
+ __tbl__, __key__ = luatbls._get_tbl_index(\luastring{#1}, true)
+ luatbls[__tbl__][__key__] = \luastring{#2}
+}}
+
+\NewDocumentCommand{\tbladdN}{m m}{\luadirect{% add a kv pair to a table
+ __tbl__, __key__ = luatbls._get_tbl_index(\luastring{#1}, true)
+ luatbls[__tbl__][__key__] = \luastringN{#2}
+}}
+
+
+\NewDocumentCommand{\tblupd}{m m}{\luadirect{% update to a table (ie using integer index) with a list of comma separated values (second arg) #
+ __tbl__ = luatbls._get_tbl_name(\luastring{#1})
+ __tbl_new__ = luatbls._luakeys.parse(string.subpar(\luastring{#2})
+ penlight.tablex.update(luatbls[__tbl__], __tbl_new__)
+}}
+
+
+\NewDocumentCommand{\tblupdN}{m m}{\luadirect{% update to a table (ie using integer index) with a list of comma separated values (second arg) #
+ __tbl__ = luatbls._get_tbl_name(\luastring{#1})
+ __tbl_new__ = luatbls._luakeys.parse(string.subpar(\luastringN{#2})
+ penlight.tablex.update(luatbls[__tbl__], __tbl_new__)
+}}
+
+
+
+
+\NewDocumentCommand{\tblget}{m}{\luadirect{% get an item using i syntax
+ luatbls._get_tbl_item(\luastring{#1}, true)
+}}
+
+\NewDocumentCommand{\tblgetE}{m}{\luadirect{% get an item explicitly
+ tex.sprint(tostring(luatbls.#1))
+}}
+
+\NewDocumentCommand{\tblset}{m m}{\luadirect{% set item with {value}
+ luatbls._set_tbl_item(\luastring{#1}, \luastring{#2})
+}}
+
+\NewDocumentCommand{\tblsetN}{m m}{\luadirect{% set item with {value}
+ luatbls._set_tbl_item(\luastring{#1}, \luastringN{#2})
+}}
+
+
+\NewDocumentCommand{\tblif}{ m m O{}}{\ifluax{luatbls._get_tbl_item(\luastring{#1})}{#2}[#3]}
+
+\NewDocumentCommand{\tblifv}{m m O{}}{\ifluaxv{luatbls._get_tbl_item(\luastring{#1})}{#2}[#3]}
+
+\NewDocumentCommand{\tblifeq}{m m m O{}}{\ifluax{luatbls._get_tbl_item(\luastring{#1}) == #2}{#3}[#4]}
+
+
+\NewDocumentCommand{\tblfor}{ m m }{\luadirect{luatbls._for_tbl(\luastring{#1}, \luastring{#2})}}
+\NewDocumentCommand{\tblforN}{ m m }{\luadirect{luatbls._for_tbl(\luastring{#1}, \luastringN{#2})}}
+
+\NewDocumentCommand{\tblforE}{ m m }{\luadirect{luatbls._for_tbl_e(luatbls.#1, \luastring{#2})}}
+\NewDocumentCommand{\tblforEN}{ m m }{\luadirect{luatbls._for_tbl_e(luatbls.#1, \luastringN{#2})}}
+
+
+\NewDocumentCommand{\tbldef}{ m m }{\luadirect{luatbls._def_tbl(\luastring{#1}, \luastring{#2})}}
+\NewDocumentCommand{\tblgdef}{ m m }{\luadirect{luatbls._def_tbl(\luastring{#1}, \luastring{#2}, 'global')}}
+
+\NewDocumentCommand{\tbldefs}{ m O{} }{\luadirect{luatbls._def_tbl_some(\luastring{#1}, \luastring{#2})}}
+\NewDocumentCommand{\tblgdefs}{ m O{} }{\luadirect{luatbls._def_tbl_some(\luastring{#1}, \luastring{#2}, 'global')}}
+
+\NewDocumentCommand{\tbldefxy}{ m O{} }{\luadirect{luatbls._def_tbl_coords(\luastring{#1}, \luastring{#2})}}% define #2x and #2y from a space delimited x-y pair
+
+\NewDocumentCommand{\tblapply}{m m O{}}{\luadirect{luatbls._apply_tbl(\luastring{#1}, \luastring{#2}, \luastring{#3})}}
+
+\NewDocumentCommand{\tblprt}{s m}{%
+ \luadirect{penlight.wrth(luatbls._get_tbl(\luastring{#2}),'luatbls: '..\luastring{#2})}%
+ \IfBooleanTF{#1}{\PackageError{luatbls}{Program terminated by tblprt*}{}\stop}{}%
+}
+
diff --git a/macros/luatex/latex/luatbls/luatbls.tex b/macros/luatex/latex/luatbls/luatbls.tex
new file mode 100644
index 0000000000..13e9dbf30b
--- /dev/null
+++ b/macros/luatex/latex/luatbls/luatbls.tex
@@ -0,0 +1,410 @@
+% Kale Ewasiuk (kalekje@gmail.com)
+% 2025-01-01
+% Copyright (C) 2025 Kale Ewasiuk
+%
+% Permission is hereby granted, free of charge, to any person obtaining a copy
+% of this software and associated documentation files (the "Software"), to deal
+% in the Software without restriction, including without limitation the rights
+% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+% copies of the Software, and to permit persons to whom the Software is
+% furnished to do so, subject to the following conditions:
+%
+% The above copyright notice and this permission notice shall be included in
+% all copies or substantial portions of the Software.
+%
+% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+% ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+% TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+% PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+% SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+% ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+% ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
+% OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+\documentclass[11pt,parskip=half]{scrartcl}
+\usepackage[left=0.75in,right=0.75in,top=1in,bottom=1in]{geometry}
+\setlength{\parindent}{0ex}
+\newcommand{\llcmd}[1]{\leavevmode\llap{\texttt{\detokenize{#1}}}}
+\newcommand{\cmd}[1]{\texttt{\detokenize{#1}}}
+\newcommand{\qcmd}[1]{``\cmd{#1}''}
+\usepackage{url}
+\usepackage[svgnames]{xcolor}
+\usepackage{showexpl}
+\lstset{explpreset={justification=\raggedright,pos=r,hsep=1cm,preset={\color{Navy}\small}}}
+\setlength\ResultBoxRule{0mm}
+\lstset{
+ language=[LaTeX]TeX,
+ basicstyle=\ttfamily\footnotesize\color{blue},
+ commentstyle=\ttfamily\footnotesize\color{gray},
+ frame=none,
+ numbers=left,
+ numberstyle=\ttfamily\footnotesize\color{gray},
+ prebreak=\raisebox{0ex}[0ex][0ex]{\color{gray}\ensuremath{\hookleftarrow}},
+ extendedchars=true,
+ breaklines=true,
+ tabsize=4,
+}
+\addtokomafont{title}{\raggedright}
+\addtokomafont{author}{\raggedright}
+\addtokomafont{date}{\raggedright}
+\author{Kale Ewasiuk (\url{kalekje@gmail.com})}
+\usepackage[yyyymmdd]{datetime}\renewcommand{\dateseparator}{--}
+\date{\today}
+
+\usepackage{enumitem}
+
+\RequirePackage{luatbls}
+
+\title{luatbls}
+\subtitle{Create, modify, and use Lua tables from within LaTeX}
+
+
+\begin{document}
+%
+
+\maketitle
+
+
+\subsection*{Introduction}
+
+This package provides a Lua-table interface based on the \cmd{luakeys} package:\\
+\url{https://mirror.quantum5.ca/CTAN/macros/luatex/generic/luakeys/luakeys.pdf}\\
+
+A global table called \cmd{luatbls} is created by loading this package. This table contains all user-defined tables
+as well as internal package functions and settings.
+User tables are stored directly under the module's table, so you can access a table within Lua by using:
+\cmd{luatbls['mytable']} or \cmd{luatbls.mytable}. Further, \cmd{luatbls} can be called directly to obtain a table item by \cmd{luatbls'i'},
+where \cmd{i} is a ``flexible'' indexing system discussed in the next paragraphs.
+
+If you want to change the \cmd{luakeys} global parser options, you can adjust them by:\\
+\cmd{\directlua{luatbls._luakeys.opts.OPTION = VALUE}}\\
+For debugging, set \cmd{\directlua{luatbls._debug = true}}
+
+
+In this documentation, arguments are represented as follows:\\
+\llcmd{t }: table name. If none provided, the most recent is used.\\
+\llcmd{k }: a string key.\\
+\llcmd{n }: an integer index.\\
+\llcmd{v }: a value.\\
+\llcmd{i }: the flexible indexer to get a single item.\\
+\llcmd{I }: the flexible indexer to get a single or multiple items.\\
+\llcmd{keyval }: a key-value string for the table. Standalone values are set to boolean.\\
+\llcmd{csv }: a key-value string where standalone values are parsed as array-like.
+
+There are a few ways to use the index (placeholder \cmd{i}.\\
+\cmd{t.k} where \cmd{t} is the table name and \cmd{k} is a string key (i.e. uses \cmd{luatbls.t.k}),\\
+\cmd{t/n} where \cmd{n} is an integer index (i.e. uses \cmd{t.k[n]}); note that negative indexes are allowed where -1 is the last element.
+Alternatively, \cmd{t} and the symbol can be omitted, and simply pass the element without the table name as a prefix,
+where the assumed table is the last one that was created or changed to (i.e. the most 'recent' table).
+In this case, passing a number will assume an integer index.
+
+To use a \cmd{I},
+you can explicitly select tables and groups of keys by \cmd{t|seq}, or \cmd{t.k}, or \cmd{t/n}.
+If no \cmd{|./} is provided, the recent table is used and the argument is assumed to be a sequence of keys.
+\cmd{penlightplus}'s command \cmd{penlight.seq.tbltrain()} syntax is used for \cmd{seq}uences.
+To summarize what \cmd{seq} can be, a comma-separated list of numbers or keys are used to specify which elements are iterated over.
+NumPy-like slicing is possibly with \cmd{:} to choose integer ranges.
+If \cmd{*} is provided,
+all string keys are iterated.
+If \cmd{I} is entirely blank, all elements of the recent table are used, which is equivalent to \cmd{t|*,:}.\\
+
+Note: nested tables are currently not fully supported. Some variations of commands have an \cmd{E} suffix which indicates
+that nested elements can be explicitly indexed.
+The table name must be specified, and the validity of table names and keys are not checked.
+
+The \cmd{tbl} commands fully expand the
+\cmd{t}, \cmd{k}, \cmd{n}, \cmd{i}, and \cmd{I} arguments. However a variation with an \cmd{N}-appended is usually provided
+which will not expand the \cmd{v}, \cmd{keyval}, or \cmd{csv} args.
+
+
+
+%%%
+\subsection*{Creating Tables}
+
+%\tblfrkv{kale}{kale=ewasiuk}
+%\luadirect{penlight.wrth(luatbls'kale.kale')}
+
+\cmd{\tblnew{t}} declares a new table with name \cmd{t}\\
+\cmd{\tblchg{t}} changes the 'recent' table\\
+\\
+\cmd{\tblfrkv{t}{keyval}[opts]} new table from key-vals using \cmd{luakeys} \\
+\cmd{\tblfrkvN{t}{keyval}[opts]} does not expand key-val string \cmd{luakeys} \\
+
+\cmd{\tblkvundefcheck} will throw an error if you use define a table from key-values
+and use a key that was not specified in the luakeys parse options via \cmd{opts.defaults} or \cmd{opts.defs}.\\
+\\
+ \cmd{\tblfrcsv{t}{csv}[opts]} a shorthand \cmd{\tblfrkv{t}{csv}[naked_as_value=true,opts]}, a good way to convert a comma-separated list to an array\\
+ \cmd{\tblfrcsvN{t}{csv}[opts]} same as above, but the csv is not expanded.\\
+
+
+\subsection*{Setting, getting, and modifying}
+
+
+\cmd{\tblset{i}{v}} sets a value of the table/index \cmd{i} to \cmd{v}\\
+\cmd{\tblsetN{i}{v}} same as above, but the value is not expanded.\\
+\\
+\cmd{\tblget{i}} gets the value and \cmd{tex.sprint()}s it\\
+\cmd{\tblgetE{t.k}} An 'explicit' version of tbl get. Use this for nested tables. The tbl name must be specified.
+The validity of table names and keys are not checked.\\
+
+\begin{LTXexample}
+\tblfrkv{ex}{a,b,c,first=john,last=smith}%
+ [defaults={x=0,1=one,n=false,y=yes}]
+\tblget{ex.a}\\
+\tblset{a}{tRuE!!}
+\tblget{a}\\
+\tblget{ex.x}\\
+\tblget{.x}\\
+\tbladd{ex.newkey}{val}\tblget{newkey}\\
+\tbladd{nk}{VAL}\tblget{nk}\\
+\end{LTXexample}
+
+\begin{LTXexample}
+\tblfrcsv{EX}{x={1,2,{1,2,3}},name=me}
+\tblgetE{EX.x[1]}\\
+\tblgetE{EX.x[3][3]}\\
+\tblgetE{EX.name}\\
+\end{LTXexample}
+
+
+
+\cmd{\tbladd{i}{v}} add a new value to a table using index method\\
+\cmd{\tbladdN{i}{v}} above, but don't expand the value argument\\
+\\
+\cmd{\tblapp{t}{v}} append a \cmd{v}alue (integer-wise) to a \cmd{t}able\\
+\cmd{\tblappN{t}{v}}\\
+\\
+\cmd{\tblupd{t}{keyval}} update a \cmd{t}able with more \cmd{keyval}s\\
+\cmd{\tblupdN{t}{keyval}}\\
+\\
+\cmd{\tblcon{t}{csv}} concatenate array-style \cmd{csv} at the end of \cmd{t}\\
+\cmd{\tblconN{t}{csv}}\\
+
+
+
+\subsection*{Conditionals}
+
+\cmd{\tblif{i}{tr}[fa]} runs code \cmd{tr} if the item is true else \cmd{fa}\\
+\cmd{\tblifv{i}{tr}[fa]} runs code \cmd{tr} if the item is truth-y (using \cmd{pl.hasval}) else \cmd{fa}\\
+\cmd{\tblifeq{i}{v}{tr}[fa]} checks the equivalency of to a user-specified value. Quotes must be used to indicate strings.
+
+\begin{LTXexample}
+\tblfrcsv{x}{n=false,y=true,
+ k0="",kv=val,k2=6}
+\tblif{n}{tr}[FA]\\
+\tblif{k0}{TR}[fa]\\
+\tblifv{k0}{tr}[FA]\\
+\tblifeq{kv}{'val'}{TR}[fa]\\
+\tblifeq{k2}{6}{TR}[fa]\\
+\end{LTXexample}
+
+
+\subsection*{Iterating}
+%TODO use PIPE SYMBOLZ \dsadsa
+
+\cmd{\tblfor{I}{template}} and \cmd{\tblforN} By default, iterates over all elements (\cmd{seq = *,:}),
+but arbitrary indices/keys can be iterated over as per \cmd{penlight.seq.tbltrain} syntax.
+\cmd{<k>} and \cmd{<v>} are placeholders in the template that are replaced by the keys and vals and can be changed by:
+\cmd{\luadirect{luatbls._tblv = '<v>'}}
+
+If you want to iterate over a second-level tabel, you must use:\\
+\cmd{\tblforE} and \cmd{\tblforEN}, and explicitly provide the table and element.
+
+\begin{LTXexample}
+\tblfrcsv{x}{n1,k1=v1,n2,n3,n4,
+ k2=v2,k3=v3,n5,n6}
+1> \tblfor{:}{<k> = <v>; }\\
+2> \tblfor{*}{<k> = <v>; }\\
+3> \tblfor{1,*,2::2}{<k> = <v>; }\\
+4> \tblfor{ x | 1,*,2::2}{<k> = <v>; }\\
+\tblfrcsv{x}{a,{a,b,c}}
+5> \tblforE{x[2]}{<k> = <v>; }
+\end{LTXexample}
+
+
+
+
+\subsection*{Definitions}
+\cmd{\tbldef{i}[cs]} pushes the value to macro \cmd{\cs}. If cs is not provided, a default cs name of \cmd{dtbl<t><k>} is used.\\
+\cmd{\tblgdef{i}[cs]} like above but global definition is used.\\
+
+\cmd{\tbldefs{I}[cspfx]} and \cmd{\tblgdefs{I}[cspfx]} defines items in table \cmd{t} (use recent if blank) with format \cmd{<cspfx><key>} where \cmd{cspfx} is a command sequence prefix.
+If \cmd{cspfx} is blank, keys will be defined as \cmd{dtbl<t><k>}.
+The default \cmd{cspfx} is changed by:\\\cmd{\luadirect{luatbls._cspfx = 'dtbl'}}\\
+Numerical keys are converted to capital letters: 1->A, 2->B.
+It is recommended that tables and keys contain letters only for predictable behaviour when using this feature.\\
+If the value of a tbl's key is a table, every element in that table is defined, and the keys of that nested table is
+appended to the cs: \cmd{dtbl<t><k1><k2>} (noting that numbers are converted to letters).\\
+
+
+\begin{LTXexample}
+\tblfrcsv{EX}{n1,kA=v1,n2,n3,n4,
+ kB=v2,kC=v3,n5,n6}
+1>\tbldef{kA}{mycs}\mycs\tbldef{kA}{}\dtblEXkA\\
+2> \tbldef{EX/1}{}\dtblEXA
+\end{LTXexample}
+
+\begin{LTXexample}
+\tblfrcsv{EX}{x={1,2,3}}
+1>\tbldef{x}{mycs}\mycsA, \mycsB \\
+2>\tbldefs{}\dtblEXxA, \dtblEXxB
+\end{LTXexample}
+
+
+
+\cmd{\tbldefxy{i}[cspfx]} splits the value of item by space, and creates two definitions \cmd{<cspfx>x} and \cmd{<cspfx>y}.
+This might be useful for passing and using tikz coordinates, for example \cmd{xy=0 5}. An error is thrown if the values
+are non-numeric.\\
+
+\begin{LTXexample}[width=0.5\linewidth]
+\tblfrkv{EX}{coords=12 34,other}
+\tbldefxy{coords}[d]\dx, \dy \\
+\tbldefxy{coords}\dtblEXcoordsx, \dtblEXcoordsy \\
+\end{LTXexample}
+
+
+
+\subsection*{Utilities}
+
+\cmd{\tblapply{I}{func1(<v>,x,y)|:func2}[newtable]} apply a Lua function(s).\\If \cmd{newtable} is provided, a new table is created (and made the recent table)
+and the original table is preserved.\\ The \cmd{.}, \cmd{/} or \cmd{|} indexer may be used to apply a function
+to a single value or group of keys.
+Multiple functions can be applied sequentially, separated by \cmd{|}.
+
+An arbitrary global function (including additional arguments) can be used,
+but if a function is prefixed with a \cmd{:}, the class method will be called.
+The \cmd{stringx} and \cmd{tablex} methods
+from \cmd{penlight} are used depending on the value's type. See:\\
+\url{https://lunarmodules.github.io/Penlight/}
+
+Arguments can be specified with round brackets, where
+\cmd{<v>} and \cmd{<k>} are used as a placeholder for the values and keys.
+If no arguments are passed, it is assumed that the value is the only argument.
+Note that luakeys parses the args, so quotes are not needed around strings for the args.
+
+
+\begin{LTXexample}[width=0.5\linewidth]
+\tblfrcsv{ex}{{a, b, c}}
+\tblapply{}{:concat(<v>,-) | :upper}[new]
+1> \tblgetE{ex[1][1]}\\
+2> \tblget{new/1}\\
+\tblfrcsv{ex}{HelloWorld}
+\tblapply{}{string.sub(<v>,2,-5)}[new]
+3> \tblget{new/1}
+\end{LTXexample}
+
+%\begin{luacode*}
+% penlight.wrth(penlight.tablex.list2comma({'a','b','c'}),'XYZ')
+%\end{luacode*}
+
+%\tblprt*{ex}
+
+
+
+
+\cmd{\tblprt{t}} pretty-print the table in console. Using \cmd{\tblprt*{}} will terminate the LaTeX program immediately after and issue an error, which could be useful for debugging.
+
+
+
+\luadirect{luatbls._debug = true}
+
+\subsubsection*{An Example}
+%
+\begin{LTXexample}\scriptsize
+\NewDocumentCommand{\Exampletbl}{m}{
+ \tblfrcsv{ex}{#1}[defaults={sal=Hello}]
+ %\tblkvundefcheck
+ \tblapply{ex.auth}{:list2comma}
+ \tblget{sal}, \tblget{auth}! Thank you for writing such a great novel.
+ My favorite parts were:
+ \begin{description}
+ \tblforEN{ex.chaps}{\item[<k>] <v> }
+ \end{description}
+ It was also very cool to learn that
+ \tblgetE{ex.num[1]}*\tblgetE{ex.num[2]}=
+ \luadirect{tex.sprint(tostring(luatbls.ex.num[1]*luatbls.ex.num[2]))}
+}
+\Exampletbl{auth={You,Me,Dupree},
+ chaps={intro=very enticing, climax=thrilling, finale=what a twist!}
+ num={12,13}
+}
+\end{LTXexample}
+
+\tblprt{ex}
+
+
+
+
+
+
+\end{document}
+
+
+
+
+
+
+%\begin{luacode*}
+% function prt_pyth()
+% t = pl.tbls.pyth
+% if not t.a then
+% pl.tex.pkgerror('must pass a= to \\pyth')
+% elseif not t.b then
+% t.b = (tonumber(t.c)^2 -
+% tonumber(t.a)^2)^0.5
+% elseif not t.c then
+% t.c = (tonumber(t.a)^2 +
+% tonumber(t.b)^2)^0.5
+% end
+% local t = pl.tbx.fmt(t,'.'..t.d..'f') -- format table according to d decimals
+% s = 'Right-angle sides a=$a and b=$b form a hypotenuse of c=$c'
+% pl.tex.prt(s:fmt(t))
+% end
+%\end{luacode*}
+%\NewDocumentCommand{\pyth}{m}{%
+% \tblfrkv{pyth}{#1}[defaults={a=false,b=false,c=false,d=0,e=extras}]
+% \luadirect{prt_pyth()}%
+%}
+%
+%\pyth{a=3,c=5}\\
+%\pyth{a=3.2,b=4.2,d=2}\\
+%C: \tblget{c}
+%
+%\end{LTXexample}
+
+%
+
+%
+%\begin{luacode*}
+%function prttol()
+% local dec = penlight.tbls.tol[4] or 1
+% penlight.wrth(dec,'??')
+% penlight.tbls.tol[3] = penlight.tbls.tol[3] or 3
+% penlight.tbls.tol[4] = penlight.tbls.tol[1]*(1.0-penlight.tbls.tol[3]/100.0) + 0.0
+% penlight.tbls.tol[5] = penlight.tbls.tol[1]*(1.0+penlight.tbls.tol[3]/100.0) + 0.0
+% --penlight.tbls.tol['k'] = 'fuckboi'
+% --ttt = pl.tbx.fmt(penlight.tbls.tol, '.3f')
+% penlight.wrth(('$1\\$2 (\\pmpct{$3} tolerance, $4\\ndash$5\\$2)'):fmt(penlight.tbls.tol, '4=.'..dec..'f, 5=.'..dec..'f'), 'XYZ')
+%end
+%\end{luacode*}
+%\NewDocumentCommand{\prttol}{ m }{\tblfrcsv{tol}{#1}\luadirect{prttol()}}% {50.0,kV,3,P} % 50\us (\pmpct{20} tolerance, 40=--60\us), P is optional and precision of the range (number of decimals)
+%
+%\prttol{50,kV,3}
+%
+%\begin{luacode*}
+% pl.wrth(pl.filterfiles('.',true,'.*%.tex'), 'FF')
+%\end{luacode*}
+
+
+
+
+%\begin{luacode*}
+% for t, k, v in luatbls._iter_tbls_vals('my|*,:') do
+% --for t, k, v in luatbls._iter_tbls_vals('my/1') do
+% penlight.wrth({t,k,v})
+% end
+%\end{luacode*}
+
+