summaryrefslogtreecommitdiff
path: root/macros/luatex/generic/penlightplus
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2023-07-24 03:04:00 +0000
committerNorbert Preining <norbert@preining.info>2023-07-24 03:04:00 +0000
commit19444d4440a66200b03e05b567996375d0404a46 (patch)
tree450f6790376fe84313c8b2840fe51307bcac2f9e /macros/luatex/generic/penlightplus
parent8e64fc98a1773bc22a0f010f106d56a028e48597 (diff)
CTAN sync 202307240303
Diffstat (limited to 'macros/luatex/generic/penlightplus')
-rw-r--r--macros/luatex/generic/penlightplus/README.md28
-rw-r--r--macros/luatex/generic/penlightplus/penlightplus.lua927
-rw-r--r--macros/luatex/generic/penlightplus/penlightplus.pdfbin0 -> 55459 bytes
-rw-r--r--macros/luatex/generic/penlightplus/penlightplus.sty119
-rw-r--r--macros/luatex/generic/penlightplus/penlightplus.tex227
5 files changed, 1301 insertions, 0 deletions
diff --git a/macros/luatex/generic/penlightplus/README.md b/macros/luatex/generic/penlightplus/README.md
new file mode 100644
index 0000000000..cc80374347
--- /dev/null
+++ b/macros/luatex/generic/penlightplus/README.md
@@ -0,0 +1,28 @@
+# penlightplus
+
+Additions to the Penlight Lua Libraries.
+
+
+# License
+
+Copyright (C) 2021-2023 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/generic/penlightplus/penlightplus.lua b/macros/luatex/generic/penlightplus/penlightplus.lua
new file mode 100644
index 0000000000..ffdbe89bc5
--- /dev/null
+++ b/macros/luatex/generic/penlightplus/penlightplus.lua
@@ -0,0 +1,927 @@
+--% Kale Ewasiuk (kalekje@gmail.com)
+--% 2023-07-22
+--% Copyright (C) 2021-2023 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.
+
+
+
+
+__PL_PLUS__ = true
+__PL_SKIP_TEX__ = __PL_SKIP_TEX__ or false --if declared true before here, it will use regular print functions
+-- (for troubleshooting with texlua instead of actual use in lua latex)
+__PL_GLOBALS__ = __PL_GLOBALS__ or false
+__PL_NO_HYPERREF__ = __PL_NO_HYPERREF__ or false
+
+
+penlight.luakeys = require'luakeys'()
+
+
+penlight.COMP = require'pl.comprehension'.new() -- for comprehensions
+
+-- http://lua-users.org/wiki/SplitJoin -- todo read me!!
+
+penlight.tex = {} -- adding a sub-module for tex related stuff
+
+local bind = bind or penlight.func.bind
+
+
+function penlight.hasval(x) -- if something has value
+ if (type(x) == 'function') or (type(x) == 'CFunction') or (type(x) == 'userdata') then
+ return true
+ elseif (x == nil) or (x == false) or (x == 0) or (x == '') or (x == {}) then
+ return false
+ elseif (type(x) ~= 'boolean') and (type(x) ~= 'number') and (type(x) ~= 'string') then -- something else? maybe ths not needed
+ if #x == 0 then -- one more check, probably no needed though, I was trying to cover other classes but they all tables
+ return false
+ else
+ return true
+ end
+ end
+ return true
+end
+
+
+-- Some simple and helpful LaTeX functions -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+-- xparse defaults
+penlight.tex._xTrue = '\\BooleanTrue '
+penlight.tex._xFalse = '\\BooleanFalse '
+penlight.tex._xNoValue = '-NoValue-'
+
+--Generic LuaLaTeX utilities for print commands or environments
+
+if not __PL_SKIP_TEX__ then
+ local function check_special_chars(s) -- todo extend to toher special chars?
+ if type(s) == 'string' then
+ if string.find(s, '[\n\r\t\0]') then
+ penlight.tex.pkgwarn('penlight', 'printing string with special (eg. newline) char, possible unexpected behaviour on string: '..s)
+ end
+ end
+ end
+
+ -- NOTE: usage is a bit different than default. If number is first arg, you CANT change catcode.
+ -- We don't need that under normal use, use tex.print or tex.sprint if you need
+ function penlight.tex.prt(s, ...) -- print something, no new line after
+ check_special_chars(s)
+ if type(s) == 'number' then s = tostring(s) end
+ tex.sprint(s, ...) --can print lists as well, but will NOT put new line between them or anything printed
+ end
+
+ function penlight.tex.prtn(s, ...) -- print with new line after, can print lists or nums. C-function not in Lua, apparantly
+ s = s or ''
+ check_special_chars(s)
+ if type(s) == 'number' then s = tostring(s) end
+ tex.print(s, ...)
+ end
+
+ penlight.tex.wrt = texio.write
+ penlight.tex.wrtn = texio.write_nl
+else
+ penlight.tex.prt = io.write
+ penlight.tex.prtn = print --print with new line
+ penlight.tex.wrt = io.write
+ penlight.tex.wrtn = io.write_nl
+end
+
+function penlight.tex.prtl(str) -- prints a literal/lines string in latex, adds new line between them
+ for line in str:gmatch"[^\n]*" do -- gets all characters up to a new line
+ penlight.tex.prtn(line)
+ end
+end
+
+-- todo option to specify between character? one for first table, on for recursives?
+function penlight.tex.prtt(tab, d1, d2) -- prints a table with new line between each item
+ d1 = d1 or ''
+ d2 = d2 or '\\leavevmode\\\\'
+ for _, t in pairs(tab) do --
+ if type(t) ~= 'table' then
+ if d1 == '' then
+ penlight.tex.prtn(t)
+ else
+ penlight.tex.prt(t, d1)
+ end
+ else
+ penlight.tex.prtn(d2)
+ penlight.tex.prtt(t,d1,d2)
+ end
+ end
+end
+
+function penlight.tex.help_wrt(s1, s2) -- helpful printing, makes it easy to debug, s1 is object, s2 is note
+ local wrt2 = wrt or texio.write_nl or print
+ s2 = s2 or ''
+ wrt2('\nvvvvv '..s2..'\n')
+ if type(s1) == 'table' then
+ wrt2(penlight.pretty.write(s1))
+ else
+ wrt2(tostring(s1))
+ end
+ wrt2('\n^^^^^\n')
+end
+penlight.help_wrt = penlight.tex.help_wrt
+
+function penlight.tex.prt_array2d(t)
+ for _, r in ipairs(t) do
+ local s = ''
+ for _, v in ipairs(r) do
+ s = s.. tostring(v)..', '
+ end
+ penlight.tex.prt(s)
+ penlight.tex.prt('\n')
+ end
+end
+
+-- -- -- -- --
+
+function penlight.tex.pkgwarn(pkg, msg1, msg2)
+ pkg = pkg or ''
+ msg1 = msg1 or ''
+ msg2 = msg2 or ''
+ tex.sprint('\\PackageWarning{'..pkg..'}{'..msg1..'}{'..msg2..'}')
+end
+
+function penlight.tex.pkgerror(pkg, msg1, msg2, stop)
+ pkg = pkg or ''
+ msg1 = msg1 or ''
+ msg2 = msg2 or ''
+ stop = penlight.hasval(stop)
+ tex.sprint('\\PackageError{'..pkg..'}{'..msg1..'}{'..msg2..'}')
+ if stop then tex.sprint('\\stop') end -- stop on the spot (say that 10 times)
+end
+
+
+--definition helpers -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+function penlight.tex.defmacro(cs, val) -- , will not work if val contains undefined tokens (so pre-define them if using..)
+ val = val or '' -- however this works for arbitrary command names (\@hello-123 etc allowed)
+ token.set_macro(cs, val, 'global')
+end
+
+
+function penlight.tex.defcmd(cs, val) -- fixes above issue, but only chars allowed in cs (and no @)
+ val = val or ''
+ tex.sprint('\\gdef\\'..cs..'{'..val..'}')
+end
+
+function penlight.tex.defcmdAT(cs, val) -- allows @ in cs,
+ --however should only be used in preamble. I avoid \makeatother because I've ran into issues with cls and sty files using it.
+ val = val or ''
+ tex.sprint('\\makeatletter\\gdef\\'..cs..'{'..val..'}')
+end
+
+
+
+function penlight.tex.prvcmd(cs, val) -- provide command via lua
+ if token.is_defined(cs) then
+ -- do nothing if token is defined already --pkgwarn('penlight', 'Definition '..cs..' is being overwritten')
+ else
+ penlight.tex.defcmd(cs, val)
+ end
+end
+
+function penlight.tex.newcmd(cs, val) -- provide command via lua
+ if token.is_defined(cs) then
+ penlight.tex.pkgerror('penlight: newcmd',cs..' already defined')
+ else
+ penlight.tex.defcmd(cs, val)
+ end
+end
+
+function penlight.tex.renewcmd(cs, val) -- provide command via lua
+ if token.is_defined(cs) then
+ penlight.tex.defcmd(cs, val)
+ else
+ penlight.tex.pkgerror('penlight: renewcmd',cs..' not defined')
+ end
+end
+
+function penlight.tex.deccmd(cs, def, overwrite) -- declare a definition, placeholder throws an error if it used but not set!
+ overwrite = penlight.hasval(overwrite)
+ local decfun
+ if overwrite then decfun = penlight.tex.defcmd else decfun = penlight.tex.newcmd end
+ if def == nil then
+ decfun(cs, pkgerror('penlight', cs..' was declared and used in document, but never set'))
+ else
+ decfun(cs, def)
+ end
+end
+
+
+--
+-- -- todo add and improve this, options for args?
+--local function defcmd_nest(cs) -- for option if you'd like your commands under a parent ex. \csparent{var}
+-- tex.print('\\gdef\\'..cs..'#1{\\csname '..var..'--#1--\\endcsname}')
+--end
+--
+--
+--local function defcmd(cs, val, nargs)
+-- if (nargs == nil) or (args == 0) then
+-- token.set_macro(cs, tostring(val), 'global')
+-- else
+-- local args = '#1'
+-- tex.print('\\gdef\\'..cs..args..'{'..val..'}')
+-- -- todo https://tex.stackexchange.com/questions/57551/create-a-capitalized-macro-token-using-csname
+-- -- \expandafter\gdef\csname Two\endcsname#1#2{1:#1, two:#2} --todo do it like this
+-- end
+--end
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+
+
+-- when nesting commands, this makes it helpful to not worry about brackets
+penlight.tex._NumBkts = 0
+--prt(opencmd('textbf')..opencmd('texttt')..'bold typwriter'..close_bkt_cnt())
+
+function penlight.tex.opencmd(cmd)
+ return '\\'..cmd..add_bkt_cnt()
+end
+
+function penlight.tex.reset_bkt_cnt(n)
+ n = n or 0
+ _NumBkts = n
+end
+
+function penlight.tex.add_bkt_cnt(n)
+ -- add open bracket n times, returns brackets
+ n = n or 1
+ _NumBkts = _NumBkts + n
+ return ('{'):rep(n)
+end
+
+function penlight.tex.close_bkt_cnt(n)
+ n = n or _NumBkts
+ local s = ('}'):rep(n)
+ _NumBkts = _NumBkts - n
+ return s
+end
+
+
+
+function penlight.tex.aliasluastring(s, d)
+ s = s:delspace():upper():tolist()
+ d = d:delspace():upper():tolist()
+ for i, S in penlight.seq.enum(d:slice_assign(1,#s,s)) do
+ if (S == 'E') or (S == 'F') then S = '' end -- E or F is fully expanded
+ penlight.tex.prtn('\\let\\plluastring'..penlight.Char(i)..'\\luastring'..S)
+ end
+end
+
+
+
+
+
+function penlight.tex.get_ref_info(l)
+ local n = 5
+ if __PL_NO_HYPERREF__ then
+ local n = 2
+ end
+ local r = token.get_macro('r@'..l)
+ local t = {}
+ if r == nil then
+ t = penlight.tablex.new(n, 0) -- make all 0s
+ r = '-not found-'
+ else
+ t = {r:match(("(%b{})"):rep(n))}
+ t = penlight.tablex.map(string.trimfl, t)
+ end
+ t[#t+1] = r -- add the og return of label
+ --penlight.help_wrt(t, 'ref info')
+ return t
+end
+
+-- todo add regex pattern for cref info
+--function penlight.tex.get_ref_info_all_cref(l)
+-- local r = token.get_macro('r@'..l..'@cref')
+-- if r == nil then
+-- return r, 0, 0
+-- end
+-- local sec, page = r:match("{([^}]*)}{([^}]*)}")
+-- return r, sec, page
+--end
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
+
+
+
+
+
+
+-- -- -- -- math stuff
+function math.mod(a, b) -- math modulo, return remainder only
+ return a - (math.floor(a/b)*b)
+end
+
+function math.mod2(a) -- math modulo 2
+ return math.mod(a,2)
+end
+
+
+
+-- -- -- -- string stuff
+local lpeg = require"lpeg"
+local P, R, S, V = lpeg.P, lpeg.R, lpeg.S, lpeg.V
+
+local number = P{"number",
+ number = (V"int" * V"frac"^-1 * V"exp"^-1) / tonumber,
+ int = V"sign"^-1 * (R"19" * V"digits" + V"digit"),
+ digits = V"digit" * V"digits" + V"digit",
+ digit = R"09",
+ sign = S"+-",
+ frac = P"." * V"digits",
+ exp = S"eE" * V"sign"^-1 * V"digits",
+ }
+
+
+
+
+function penlight.char(num)
+ return string.char(string.byte("a")+num-1)
+end
+
+function penlight.Char(num)
+ return string.char(string.byte("A")+num-1)
+end
+
+
+local str_mt = getmetatable("") -- register functions with str
+
+function str_mt.__index.gnum(s)
+ return number:match(s)
+end
+
+function str_mt.__index.gextract(s, pat) --extract a pattern from string, returns both
+ local s_extr = ''
+ local s_rem = s
+ for e in s:gmatch(pat) do
+ s_extr = s_extr..e
+ s_rem = s_rem:gsub(e,'')
+ end
+ return s_extr, s_rem
+end
+
+function str_mt.__index.gfirst(s, t) -- get the first pattern found from a table of pattern
+ for _, pat in pairs(t) do
+ if string.find(s, pat) then
+ return pat
+ end
+ end
+end
+
+function str_mt.__index.appif(S, W, B, O) --append W ord to S tring if B oolean true, otherwise O ther
+ --append Word to String
+ if B then --if b is true
+ S = S .. W
+ else --consider Other word
+ O = O or ''
+ S = S .. O
+ end
+ return S
+end
+
+
+ function str_mt.__index.containsany(s, exp)
+ if type(exp) ~= 'table' then exp = {exp} end
+ for _, e in ipairs(exp) do
+ if s:find(e) then return true end
+ end
+ return false
+end
+
+function str_mt.__index.containsanycase(s, exp)
+ if type(exp) ~= 'table' then exp = {exp} end
+ for _, e in ipairs(exp) do
+ if s:lower():find(e:lower()) then return true end
+ end
+ return false
+end
+
+function str_mt.__index.totable(str)
+ local t = {}
+ for i = 1, #str do
+ t[i] = str:sub(i, i)
+ end
+ return t
+end
+
+function str_mt.__index.tolist(str)
+ return penlight.List(str)
+end
+
+
+function str_mt.__index.upfirst(str)
+ return str:gsub('%a', function(x) return x:upper() end, 1)
+end
+
+function str_mt.__index.delspace(str)
+ return str:gsub('%s','')
+end
+
+function str_mt.__index.trimfl(str)
+ return str:sub(2,-2)
+end
+
+
+
+-- -- -- -- function stuff
+
+function penlight.clone_function(fn)
+ local dumped = string.dump(fn)
+ local cloned = loadstring(dumped)
+ local i = 1
+ while true do
+ local name = debug.getupvalue(fn, i)
+ if not name then
+ break
+ end
+ debug.upvaluejoin(cloned, i, fn, i)
+ i = i + 1
+ end
+ return cloned
+end
+
+
+
+
+-- -- -- -- -- -- -- -- -- -- -- -- functions below extend the operator module
+
+function penlight.operator.strgt(a,b) return tostring(a) > tostring(b) end
+function penlight.operator.strlt(a,b) return tostring(a) < tostring(b) end
+
+
+
+-- -- -- -- functions below are helpers for arrays and 2d
+
+local function compare_elements(a, b, op, ele)
+ op = op or penlight.oper.gt
+ ele = ele or 1
+ return op(a[ele], b[ele])
+end
+
+local function comp_2ele_func(op, ele) -- make a 2 element comparison function,
+ --sort with function on element nnum
+ return bind(compare_elements, _1, _2, op, ele)
+end
+
+
+
+
+
+-- table stuff below
+
+
+function penlight.tablex.map_slice(func, T, j1, j2)
+ if type(j1) == 'string' then
+ return penlight.array2d.map_slice(func, {T}, ','..j1)[1]
+ else
+ return penlight.array2d.map_slice(func, {T}, 1, j1, 1, j2)[1]
+ end
+end
+
+penlight.array2d.map_slice1 = penlight.tablex.map_slice
+
+
+-- todo option for multiple filters with AND logic, like the filter files??
+function penlight.tablex.filterstr(t, exp, case)
+ -- case = case sensitive
+ case = penlight.hasval(case)
+ -- apply lua patterns to a table to filter iter
+ -- str or table of str's can be passed, OR logic is used if table is passed
+ if case then
+ return penlight.tablex.filter(t, bind(string.containsany,_1,exp))
+ else
+ return penlight.tablex.filter(t, bind(string.containsanycase,_1,exp))
+ end
+end
+
+
+function penlight.utils.filterfiles(...)
+ -- f1 is a series of filtering patterns, or condition
+ -- f2 is a series of filtering patters, or condition
+ -- (f1_a or f2_...) and (f2 .. ) must match
+ local args = table.pack(...)
+ -- todo -- check where boolean is for recursive or not, set starting argument
+ -- this could allow one to omit dir
+ -- todo if no boolean at all, assume dir = '.' and r = false
+ -- if boolean given, assume dir = '.'
+ local nstart = 3
+ local r = args[2]
+ local dir = args[1]
+ if type(args[1]) == 'boolean' then
+ dir = '.'
+ r = args[1]
+ nstart = 2
+ elseif type(args[2]) ~= 'boolean' then
+ dir = '.'
+ r = false
+ nstart = 1
+ end
+
+ local files
+ if r then files = penlight.dir.getallfiles(dir)
+ else files = penlight.dir.getfiles(dir)
+ end
+ for i=nstart,args.n do
+ files = penlight.tablex.filter(files, penlight.func.compose(bind(string.containsanycase,_1, args[i]), penlight.path.basename))
+ end
+ return files
+end
+
+
+
+
+-- -- -- -- -- -- -- -- functions below extend the array2d module
+
+
+function penlight.array2d.map_slice(func, M, i1, j1, i2, j2) -- map a function to a slice of a Matrix
+ func = penlight.utils.function_arg(1, func)
+ for i,j in penlight.array2d.iter(M, true, i1, j1, i2, j2) do
+ M[i][j] = func(M[i][j])
+ end
+ return M
+end
+
+penlight.array2d.map_slice2 = penlight.array2d.map_slice
+
+function penlight.array2d.map_cols(func, M, j1, j2) -- map function to columns of matrix
+ if type(j1) == 'string' then
+ return penlight.array2d.map_slice(func, M, ','..j1)
+ else
+ j2 = j2 or -1
+ return penlight.array2d.map_slice(func, M, 1, j1, -1, j2)
+ end
+end
+
+penlight.array2d.map_columns = penlight.array2d.map_cols
+
+function penlight.array2d.map_rows(func, M, i1, i2) -- map function to rows of matrix
+ if type(i1) == 'string' then
+ return penlight.array2d.map_slice(func, M, i1)
+ else
+ i2 = i2 or -1
+ return penlight.array2d.map_slice(func, M, i1, 1, i2, -1)
+ end
+end
+
+
+-- -- -- -- -- -- -- --
+
+function penlight.array2d.sortOP(M, op, ele) -- sort a 2d array based on operator criteria, ele is column, ie sort on which element
+ M_new = {}
+ for row in penlight.seq.sort(M, comp_2ele_func(op, ele)) do
+ M_new[#M_new+1] = row
+ end
+ return M_new
+end
+
+function penlight.array2d.like(M1, v)
+ v = v or 0
+ r, c = penlight.array2d.size(M1)
+ return penlight.array2d.new(r,c,v)
+end
+
+function penlight.array2d.from_table(t) -- turns a labelled table to a 2d, label-free array
+ t_new = {}
+ for k, v in pairs(t) do
+ if type(v) == 'table' then
+ t_new_row = {k}
+ for _, v_ in ipairs(v) do
+ t_new_row[#t_new_row+1] = v_
+ end
+ t_new[#t_new+1] = t_new_row
+ else
+ t_new[#t_new+1] = {k, v}
+ end
+ end
+ return t_new
+end
+
+function penlight.array2d.toTeX(M, EL) --puts & between columns, can choose to end line with \\ if EL is true (end-line)
+ EL = EL or false
+ if EL then EL = '\\\\' else EL = '' end
+ return penlight.array2d.reduce2(_1..EL.._2, _1..'&'.._2, M)..EL
+end
+
+
+local function parse_numpy1d(i1, i2, iS)
+ i1 = tonumber(i1)
+ i2 = tonumber(i2)
+ if iS == ':' then
+ if i1 == nil then i1 = 1 end
+ if i2 == nil then i2 = -1 end
+ else
+ if i1 == nil then
+ i1 = 1
+ i2 = -1
+ else
+ i2 = i1
+ end
+ end
+ return i1, i2
+end
+
+function penlight.array2d.parse_numpy2d_str(s)
+ s = s:gsub('%s+', '')
+ _, _, i1, iS, i2, j1, jS, j2 = string.find(s, "(%-?%d*)(:?)(%-?%d*),?(%-?%d*)(:?)(%-?%d*)")
+ i1, i2 = parse_numpy1d(i1, i2, iS)
+ j1, j2 = parse_numpy1d(j1, j2, jS)
+ return i1, j1, i2, j2
+end
+
+
+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
+ penlight.utils.assert_arg(1,s,'string')
+ if not s:find'%a' then
+ return penlight.array2d.parse_numpy2d_str(s)
+ end
+ return _parse_range(s)
+end
+
+
+
+
+
+
+-- https://tex.stackexchange.com/questions/38150/in-lualatex-how-do-i-pass-the-content-of-an-environment-to-lua-verbatim
+penlight.tex.recordedbuf = ""
+function penlight.tex.readbuf(buf)
+ i,j = string.find(buf, '\\end{%w+}')
+ if i==nil then -- if not ending an environment
+ penlight.tex.recordedbuf = penlight.tex.recordedbuf .. buf .. "\n"
+ return ""
+ else
+ return nil
+ end
+end
+
+function penlight.tex.startrecording()
+ penlight.tex.recordedbuf = ""
+ luatexbase.add_to_callback('process_input_buffer', penlight.tex.readbuf, 'readbuf')
+end
+
+function penlight.tex.stoprecording()
+ luatexbase.remove_from_callback('process_input_buffer', 'readbuf')
+ penlight.tex.recordedbuf = penlight.tex.recordedbuf:gsub("\\end{%w+}\n","")
+end
+
+
+
+__PDFmetadata__ = {}
+penlight.tex.add_xspace_intext = true
+
+
+function penlight.tex.updatePDFtable(k, v, o) -- key val overwrite
+ k = k:upfirst()
+ if penlight.hasval(o) or (__PDFmetadata__[k] == nil) then
+ __PDFmetadata__[k] = v
+ end
+end
+
+penlight.tex.writePDFmetadata = function(t) -- write PDF metadata to xmpdata file
+ t = t or __PDFmetadata__
+ local str = ''
+ for k, v in pairs(t) do
+ k = k:upfirst()
+ str = str..'\\'..k..'{'..v..'}'..'\n'
+ end
+ penlight.utils.writefile(tex.jobname..'.xmpdata', str)
+end
+
+
+
+function penlight.tex.clear_cmds_str(s)
+ return s:gsub('%s+', ' '):gsub('\\\\',' '):gsub('\\%a+',''):gsub('{',' '):gsub('}',' '):gsub('%s+',' '):strip()
+end
+
+function penlight.tex.makePDFvarstr(s)
+ s = s:gsub('%s*\\sep%s+','\0'):gsub('%s*\\and%s+','\0') -- turn \and into \sep
+ s = penlight.tex.clear_cmds_str(s)
+ s = s:gsub('\0','\\sep ')
+ --penlight.tex.help_wrt(s,'PDF var string')
+ return s
+end
+
+function penlight.tex.makeInTextstr(s)
+ local s, c_and = s:gsub('%s*\\and%s+','\0')
+ s = penlight.tex.clear_cmds_str(s)
+ if penlight.tex.add_xspace_intext then
+ s = s..'\\xspace'
+ end
+ if c_and == 1 then
+ s = s:gsub('\0',' and ')
+ elseif c_and > 1 then
+ s = s:gsub('\0',', ', c_and - 1)
+ s = s:gsub('\0',', and ')
+ end
+ --penlight.tex.help_wrt(s,'in text var string')
+ 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
+
+
+
+if penlight.hasval(__PL_GLOBALS__) then
+ -- iterators
+ kpairs = penlight.utils.kpairs
+ npairs = penlight.utils.npairs
+ --enum = utils.enum
+
+ for k,v in pairs(penlight.tablex) do -- extend the table table to contain tablex functions
+ if k == 'sort' then
+ table.sortk = v
+ elseif k == 'move' then
+ table.xmove = v
+ else
+ _G['table'][k] = v
+ end
+ end
+ table.join = table.concat -- alias
+ -- todo should tablex have all table functions
+
+ penlight.tablex.concat = table.concat
+ penlight.tablex.insert = table.insert
+ penlight.tablex.maxn = table.maxn
+ penlight.tablex.remove = table.remove
+ penlight.tablex.sort = table.sort
+
+ hasval = penlight.hasval
+ COMP = penlight.COMP
+
+ -- shortcuts
+-- http://stevedonovan.github.io/Penlight/api/libraries/penlight.utils.html
+ penlight.writefile = penlight.utils.writefile
+ penlight.readfile = penlight.utils.readfile
+ penlight.readlines = penlight.utils.readfile
+ penlight.filterfiles = penlight.utils.filterfiles
+
+ penlight.a2 = penlight.array2d
+ penlight.tbl = penlight.tablex
+
+
+ for k,v in pairs(penlight.tex) do -- make tex functions global
+ _G[k] = v
+ end
+
+end
+
+
+
+
+
+ --_xTrue = penlight.tex._xTrue
+ --_xFalse = penlight.tex._xFalse
+ --_xNoValue = penlight.tex._xNoValue
+ --
+ --prt = penlight.tex.prt
+ --prtn = penlight.tex.prtn
+ --wrt = penlight.tex.wrt
+ --wrtn = penlight.tex.wrtn
+ --
+ --prtl = penlight.tex.prtl
+ --prtt = penlight.tex.prtt
+ --
+ --help_wrt = penlight.tex.help_wrt
+ --prt_array2d = penlight.tex.prt_array2d
+ --
+ --pkgwarn = penlight.tex.pkgwarn
+ --pkgerror = penlight.tex.pkgerror
+ --
+ --defcmd = penlight.tex.defcmd
+ --prvcmd = penlight.tex.prvcmd
+ --newcmd = penlight.tex.newcmd
+ --renewcmd = penlight.tex.renewcmd
+ --deccmd = penlight.tex.deccmd
+ --
+ --_NumBkts = penlight.tex._NumBkts
+ --opencmd = penlight.tex.opencmd
+ --reset_bkt_cnt = penlight.tex.reset_bkt_cnt
+ --add_bkt_cnt = penlight.tex.add_bkt_cnt
+ --close_bkt_cnt = penlight.tex.close_bkt_cnt
+
+
+-- graveyard
+
+
+-- luakeys parses individual keys as ipairs, this changes the list to a pure map
+--function penlight.luakeystomap(t)
+-- local t_new = {}
+-- for k, v in pairs(t) do
+-- if type(k) == 'number' then
+-- t_new[v] = true
+-- else
+-- t_new[k] = v
+-- end
+-- end
+-- return t_new
+--end
+--if luakeys then -- if luakeys is already loaded
+-- function luakeys.parseN(s, ...)
+-- local t = luakeys.parse(s,...)
+-- t = penlight.luakeystomap(t)
+-- return t
+-- end
+--end
+-- might not be needed
+
+
+ --local func = check_func(func)
+--local function check_func(func) -- check if a function is a PE, if so, make it a function
+-- if type(func) ~= 'function' then
+-- return I(func)
+-- end
+-- return func
+--end
+
+-- -- -- -- -- -- --
+-- -- -- -- functions below extend the array2d module
+
+
+--function penlight.array2d.map_slice1(func, L, i1, i2) -- map a function to a slice of an array, can use PlcExpr
+-- i2 = i2 or i1
+-- local len = #L
+-- i1 = check_index(i1, len)
+-- i2 = check_index(i2, len)
+-- func = check_func(func)
+-- for i in penlight.seq.range(i1,i2) do
+-- L[i] = func(L[i])
+-- end
+-- return L
+--end
+
+ -- used this below when iter was not working..
+ --i1, j1, i2, j2 = check_slice(M, i1, j1, i2, j2)
+ --for i in penlight.seq.range(i1,i2) do
+ -- for j in penlight.seq.range(j1,j2) do
+ --end
+ -- penlight may have fixed this
+--local function check_index(ij, rc) -- converts array index to positive value if negative
+-- if type(ij) ~= 'number' then
+-- return 1
+-- else
+-- if ij < 0 then
+-- ij = rc + ij + 1
+-- elseif ij > rc then
+-- ij = rc
+-- elseif ij == 0 then
+-- ij = 1
+-- end
+-- return ij
+-- end
+--end
+--local function check_slice(M, i1, j1, i2, j2) -- ensure a slice is valid; i.e. all positive numbers
+-- r, c = penlight.array2d.size(M)
+-- i1 = check_index(i1 or 1, r)
+-- i2 = check_index(i2 or r, r)
+-- j1 = check_index(j1 or 1, c)
+-- j2 = check_index(j2 or c, c)
+-- return i1, j1, i2, j2
+--end
+
diff --git a/macros/luatex/generic/penlightplus/penlightplus.pdf b/macros/luatex/generic/penlightplus/penlightplus.pdf
new file mode 100644
index 0000000000..89b8236d66
--- /dev/null
+++ 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
new file mode 100644
index 0000000000..0d49ff90d0
--- /dev/null
+++ b/macros/luatex/generic/penlightplus/penlightplus.sty
@@ -0,0 +1,119 @@
+% Kale Ewasiuk (kalekje@gmail.com)
+% 2023-07-22
+% Copyright (C) 2021-2023 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.
+
+\ProvidesPackage{penlightplus}[2023-07-22]
+
+\RequirePackage{luacode}
+\RequirePackage{luakeys}
+\RequirePackage[import]{penlight}
+
+\DeclareOption{globals}{\luadirect{__PL_GLOBALS__ = true}}
+\DeclareOption{pl}{\directlua{pl = penlight}}
+\ProcessOptions*\relax
+
+\luadirect{require'penlightplus'}
+
+
+
+
+\global\newcommand{\writePDFmetadata}{\luadirect{penlight.tex.writePDFmetadata()}}
+
+\NewDocumentCommand{\writePDFmetadatakv}{ s m }{
+\IfBooleanTF{#1}{% if *, overwrite everything
+ \luadirect{
+ __PDFmetadata__ = penlight.luakeys.parse(\luastring{#2})
+ penlight.tex.writePDFmetadata()
+ }}{
+ \luadirect{
+ __PDFmetadata__ = __PDFmetadata__ or {}
+ penlight.tablex.update(__PDFmetadata__, penlight.luakeys.parse(\luastring{#2}))
+ penlight.tex.writePDFmetadata()
+ }}
+}
+
+
+\gdef\luastringT#1{\luastring{\unexpanded\expandafter\expandafter\expandafter{#1}}} % expand luastring twice
+\global\let\luastringF\luastring % fully expanded luastring
+
+% 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})}%
+}
+
+
+%
+%
+%\NewDocumentCommand{\tblnew}{m}{\luadirect{
+% _G[\luastring{#1}] = {}
+%}}
+%
+%\NewDocumentCommand{\tblfrkv}{m m O{} }{\luadirect{
+% _G[\luastring{#1}] = penlight.luakeys.parse(\luastring{#2}, penlight.luakeys.parse(\luastring{#3}))
+%}}
+%
+%\NewDocumentCommand{\tblset}{m m m}{\luadirect{
+% _G[\luastring{#1}][\luastring{#2}] = \luastring{#3}
+%}}
+%
+%\NewDocumentCommand{\tblget}{m m}{\luadirect{
+% tex.sprint(tostring(_G[\luastring{#1}][\luastring{#2}]))
+%}}
+%
+%\NewDocumentCommand{\tblidx}{m m}{\luadirect{
+% tex.sprint(tostring(_G[\luastring{#1}][#2]))
+%}}
+%
+%
+%\NewDocumentCommand{\tblupd}{ m m}{\luadirect{
+% penlight.tablex.update(_G[\luastring{#1}], _G[\luastring{#2}])
+%}}
+%
+%\NewDocumentCommand{\tblupdkv}{ m m }{\luadirect{
+% penlight.tablex.update(_G[\luastring{#1}], penlight.luakeys.parse(\luastring{#2}))
+%}}
+%
+%% todo need a get table function. If blank is passed, the last table referenced is used.
+%\NewDocumentCommand{\tblfrkvII}{ m m m }{\luadirect{
+% _G[\luastring{#1}] = penlight.tablex.update(penlight.luakeys.parse(\luastring{#2}), luakeys.parse(\luastring{#3}))
+%}}
+
+
+%\NewDocumentCommand{\tbldo}{m m O{} }{\luadirect{
+% % tex.sprint(tostring(_G[\luastring{#1}][#2]))
+% % todo allow one to use pl tablex function here,
+% % {tbl}{update}[arg1][arg2]
+% penlight.tablex[\luastring[#1]](_G[\luastring{}], _G[\luastring{}])
+%}}
+
+
diff --git a/macros/luatex/generic/penlightplus/penlightplus.tex b/macros/luatex/generic/penlightplus/penlightplus.tex
new file mode 100644
index 0000000000..50f87fa38b
--- /dev/null
+++ b/macros/luatex/generic/penlightplus/penlightplus.tex
@@ -0,0 +1,227 @@
+% Kale Ewasiuk (kalekje@gmail.com)
+% 2023-07-22
+% Copyright (C) 2021-2023 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}
+\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{xcolor}
+\usepackage{showexpl}
+\lstset{explpreset={justification=\raggedright,pos=r,wide=true}}
+\setlength\ResultBoxRule{0mm}
+\lstset{
+ language=[LaTeX]TeX,
+ basicstyle=\ttfamily\small,
+ commentstyle=\ttfamily\small\color{gray},
+ frame=none,
+ numbers=left,
+ numberstyle=\ttfamily\small\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}
+
+
+\RequirePackage{penlightplus}
+
+\title{penlightplus}
+\subtitle{Additions to the Penlight Lua Libraries}
+
+\begin{document}
+%
+
+\maketitle
+
+This package first loads the \cmd{[import]penlight} package.\\
+The \texttt{pl} option may be passed to this package to create an alias for \cmd{penlight}.\\
+\texttt{globals} option may be used to make several of the functions global (as discussed below).
+
+\subsection*{texlua usage}
+If you want to use penlightplus.lua with the \texttt{texlua} interpreter
+(no document is made, but useful for testing your Lua code),
+you can access it by setting \cmd{__SKIP_TEX__ = true} before loading. For example:
+ \begin{verbatim}
+package.path = package.path .. ';'..'path/to/texmf/tex/latex/penlightplus/?.lua'
+package.path = package.path .. ';'..'path/to/texmf/tex/latex/penlight/?.lua'
+penlight = require'penlight'
+
+__SKIP_TEX__ = true --only required if you want to use
+ --penlightplus without a LaTeX run
+__PL_GLOBALS__ = true -- optional, include global definitions
+require'penlightplus'
+\end{verbatim}
+
+
+
+
+
+
+\subsection*{}
+
+The following global variables are defined:
+
+\llcmd{__SKIP_TEX__} If using the \cmd{penlightplus} package with \cmd{texlua} (good for troubleshooting), set this global before loading \cmd{penlight}\\
+The gloals flags below are taken care of in the package options:\\
+\llcmd{__PL_}\cmd{GLOBALS__} If using package with \cmd{texlua} and you don't want to set some globals (described in next sections), set this global before to \cmd{true} loading \cmd{penlight}\\
+\cmd{__PL_NO_HYPERREF__} a flag used to change the behaviour of a function, depending on if you don't use the hyperref package\\
+
+\subsubsection*{penlight additions}
+
+Some functionality is added to penlight/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{math.mod(n,d)}, \cmd{math.mod2(n)} math modulous\\
+\llcmd{string.}\cmd{totable(s)} string a table of characters\\
+\llcmd{string.}\cmd{delspace(s)} clear spaces from string\\
+\llcmd{pl.}\cmd{char(n)} return letter corresponding to 1=a, 2=b, etc.\\
+\llcmd{pl.}\cmd{Char(n)} return letter corresponding to 1=A, 2=B, etc.\\
+
+\llcmd{pl.utils.}\cmd{filterfiles}\cmd{(dir,filt,rec)} Get files from dir and apply glob-like filters. Set rec to \cmd{true} to include sub directories\\
+
+
+\subsubsection*{A \cmd{pl.tex.} module is added}
+\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.\\
+\\
+\llcmd{_xNoValue,}\cmd{_xTrue,_xFalse}: \cmd{xparse} equivalents for commands\\
+\\
+\llcmd{prt(x),prtn(x)} print without or with a newline at end. Tries to help with special characters or numbers printing.\\
+\llcmd{prtl(l),prtt(t)} print a literal string, or table\\
+\llcmd{wrt(x), wrtn(x)} write to log\\
+\llcmd{help_wrt}\cmd{(s1, s2)} pretty-print something to console. S2 is a flag to help you find.\\
+\llcmd{prt_array2d(tt)} pretty print a 2d array\\
+\\
+\llcmd{pkgwarn}\cmd{(pkg, msg1, msg2)} throw a package warning\\
+\llcmd{pkgerror}\cmd{(pkg, msg1, msg2, stop)} throw a package error. If stop is true, immediately ceases compile.\\
+\\
+\llcmd{defcmd}\cmd{(cs, val)} like \cmd{\gdef}\\
+\llcmd{newcmd}\cmd{(cs, val)} like \cmd{\newcommand}\\
+\llcmd{renewcmd}\cmd{(cs, val)} like \cmd{\renewcommand}\\
+\llcmd{prvcmd}\cmd{(cs, val)} like \cmd{\providecommand}\\
+\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\\
+
+
+
+\subsubsection*{global extras}
+If \cmd{extrasglobals} is used and NOT \cmd{extras}, many additional globals are set for shortcuts\\
+All \cmd{pl.tex} modules are made global.\\
+\cmd{pl.hasval}, \cmd{pl.COMP}, \cmd{pl.utils.kpairs}, \cmd{pl.utils.npairs} become globals with the function name.\\
+
+
+
+\subsection*{Macro helpers}
+\cmd{\MakeluastringCommands[def]{spec}} will let \cmd{\plluastring(A|B|C..)} be \cmd{\luastring(N|O|T|F)}
+based on the letters that \cmd{spec} is set to (or \cmd{def} if nothing is provided)
+This is useful if you want to write a command with flexibility on argument expansion.
+The user can specify \cmd{n}, \cmd{o}, \cmd{t}, and \cmd{f} (case insensitve) if they want
+no, once, twice, or full expansion.
+
+% BELOW IS FOR TROUBLESHOOTING ABOVE
+%\def\NOTexp{\ONEexp}
+%\def\ONEexp{\TWOexp}
+%\def\TWOexp{\TREexp}
+%\def\TREexp{Fully expanded}
+%
+%\NewDocumentCommand{\luastringExpTest}{m m}{
+% \MakeluastringCommands{#1}
+% \luadirect{texio.write_nl('VVVVVVVVVVVVVVVVVVVVVVVVVVVVV')}
+% \luadirect{texio.write_nl(\plluastringA{#2}..' | Not')}
+% \luadirect{texio.write_nl(\plluastringB{#2}..' | Once')}
+% \luadirect{texio.write_nl(\plluastringC{#2}..' | Twice')}
+% \luadirect{texio.write_nl(\plluastringD{#2}..' | Full')}
+% \luadirect{texio.write_nl('VVVVVVVVVVVVVVVVVVVVVVVVVVVVV')}
+%}
+%
+%\luastringExpTest{ n o t f }{\NOTexp}
+
+
+
+
+\subsection*{Split stuff}
+Splitting text (or a cmd) into oxford comma format via:
+\cmd{\splitToComma[expansion level]{text}{text to split on}}:
+
+\begin{LTXexample}
+
+-\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 teh expasion of each argument.
+
+\begin{LTXexample}
+spliToItems:
+\begin{itemize}
+ \splitToItems{kale\and john}{\and}
+ \splitToItems{kale -john -someone else}{-}
+\end{itemize}
+\end{LTXexample}
+
+
+
+
+
+
+% \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}%
+%
+% \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}
+% }
+%
+% \THINg[color=blue,size=tiny]{Kale}
+
+\end{document} \ No newline at end of file