diff options
author | Norbert Preining <norbert@preining.info> | 2025-01-02 03:04:34 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2025-01-02 03:04:34 +0000 |
commit | 2eea71934e853f5e45633ccf0f7915b4f133919a (patch) | |
tree | b5d38f640b78d108926cd12a371f857bdecbb202 /macros/luatex | |
parent | a22c35c9797ac178b8552f754a91fa976d850a53 (diff) |
CTAN sync 202501020304
Diffstat (limited to 'macros/luatex')
-rw-r--r-- | macros/luatex/generic/penlightplus/README.md | 2 | ||||
-rw-r--r-- | macros/luatex/generic/penlightplus/penlightplus.lua | 440 | ||||
-rw-r--r-- | macros/luatex/generic/penlightplus/penlightplus.pdf | bin | 87726 -> 78426 bytes | |||
-rw-r--r-- | macros/luatex/generic/penlightplus/penlightplus.sty | 162 | ||||
-rw-r--r-- | macros/luatex/generic/penlightplus/penlightplus.tex | 305 | ||||
-rw-r--r-- | macros/luatex/latex/luatbls/README.md | 27 | ||||
-rw-r--r-- | macros/luatex/latex/luatbls/luatbls.lua | 310 | ||||
-rw-r--r-- | macros/luatex/latex/luatbls/luatbls.pdf | bin | 0 -> 73500 bytes | |||
-rw-r--r-- | macros/luatex/latex/luatbls/luatbls.sty | 163 | ||||
-rw-r--r-- | macros/luatex/latex/luatbls/luatbls.tex | 410 |
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 Binary files differindex bb9b8027ff..4a5d6eece3 100644 --- a/macros/luatex/generic/penlightplus/penlightplus.pdf +++ b/macros/luatex/generic/penlightplus/penlightplus.pdf 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 Binary files differnew file mode 100644 index 0000000000..e8bc8a1694 --- /dev/null +++ b/macros/luatex/latex/luatbls/luatbls.pdf 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*} + + |