% Kale Ewasiuk (kalekje@gmail.com)
% 2024-09-30
% Copyright (C) 2021-2024 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\footnotesize,
	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}


\RequirePackage[pl,globals]{penlightplus}

\title{penlightplus}
\subtitle{Additions to the Penlight Lua Libraries}


\begin{document}
%

\maketitle

\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}.\\
The \texttt{pl} option may be passed to this package to create an alias for \cmd{penlight}.\\

The following global Lua variables are defined:

\cmd{__SKIP_TEX__} If using the \cmd{penlightplus} package with \cmd{texlua} (good for troubleshooting), set this global before loading \cmd{penlight}\\
\cmd{__PL_GLOBALS__} If using this package with \cmd{texlua} and you want to set some functions as globals (described in next sections), set this variable to \cmd{true} before loading \cmd{penlight}\\
\cmd{__PL_NO_HYPERREF__} a flag used to change the behaviour of some functions, depending on if you don't use the hyperref package\\
\cmd{__PDFmetadata__} a table used to store PDF meta-data for pdfx package.


\subsubsection*{globals option}
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()}.

If you want global \cmd{pl.tex} functions and variables, call \cmd{pl.make_tex_global()}.\\


\subsection*{texlua usage}
If you want to use \cmd{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/lualatex/penlightplus/?.lua'
package.path = package.path .. ';'..'path/to/texmf/tex/lualatex/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}



\section*{penlight additions}

Some functionality is added to penlight and Lua.

\subsection*{General Additions}

\llcmd{pl.hasval(x)} Python-like boolean testing\\
\llcmd{COMP'xyz'()} Python-like comprehensions:\\\url{https://lunarmodules.github.io/Penlight/libraries/pl.comprehension.html}\\

\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)\\

\llcmd{math.mod(n,d)}, \cmd{math.mod2(n)} math modulous\\

\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\\


\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.\\



\subsection*{string additions}

\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{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\\
\llcmd{string.}\cmd{gextract(s)} extract a pattern from a string (returns capture and new string with capture removed)\\
\llcmd{string.}\cmd{totable(s)} string a table of characters\\
\llcmd{string.}\cmd{tolist(s)} string a table of characters\\
\llcmd{string.}\cmd{containsany(s,t)} checks if any of the array of strings \cmd{t} are in \cmd{s} using \cmd{string.find}\\
\llcmd{string.}\cmd{containsanycase(s,t)} case-insensitive version\\
\llcmd{string.}\cmd{delspace(s)} clear spaces from string\\
\llcmd{string.}\cmd{subpar(s, c)} replaces \cmd{\\par} with a character of your choice default is space\\
\llcmd{string.}\cmd{fmt(s, t, fmt)} format a string like \cmd{format_operator}, but with
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.

\subsection*{tablex additions}
\llcmd{tablex.}\cmd{fmt(t, f)} format a table with table or key-value string f\\
\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 \\


\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.

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.

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]
\begin{luacode*}
  for i in
    pl.seq.itrain('1, :, 6, 0::2, -3 ',
                    5) do
      tex.print(i..',')
    end
\end{luacode*}
\end{LTXexample}



\subsection*{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{wrth}\cmd{(s1, s2)} pretty-print something to console. S2 is a flag to help you find., alias is \cmd{help_wrt}, also in \cmd{pl.wrth}\\
\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}, but note that no special chars allowed in \cmd{cs}(eg. \cmd{@})\\
\llcmd{defmacro}\cmd{(cs, val)} like \cmd{\gdef}, allows special characters, but any tokens in val must be pre-defined (this uses \cmd{token.set_macro} internally)\\
\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\\




\subsection*{Recording LaTeX input as a lua variable}
\cmd{penlight.tex.startrecording()} start recording input buffer without printing to latex\\
\cmd{penlight.tex.stoprecording()} stop recording input buffer\\
\cmd{penlight.tex.readbuf()} internal-use function that interprets the buffer. This will ignore an environment ending (eg. \cmd{end{envir}})\\\\
\cmd{penlight.tex.recordedbuf} the string variable where the recorded buffer is stored\\


\section*{penlightplus LaTeX Macros}


\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}(ault) 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
none, once, twice, or full expansion.

%\cmd{e} expansion expands every token in the argument only once.
%\cmd{\luastringE{m}}, expands each token once (note that \cmd{\luastringO} only expands the first token once)



Variants of luastring are added:\\
\cmd{\luastringF{m}} = \cmd{\luastring{m}} \\
\cmd{\luastringT{m}}, expand the first token of m twice\\

For example, we can control the expansion of args 2 and 3 with arg 1:

\begin{verbatim}
	\NewDocumentCommand{\splittocomma}{ O{nn} m m }{%
    \MakeluastringCommands[nn]{#1}%
    \luadirect{penlight.tex.split2comma(\plluastringA{#2},\plluastringB{#3})}%
   }
\end{verbatim}

%   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*{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})

\begin{LTXexample}[width=0.3\linewidth]
\ifluax{3^3 == 27}{3*3*3 is 27}[WRONG]\\
\ifluax{abc123 == nil}{Var is nil}[WRONG]\\
\ifluax{not true}{tRuE}[fAlSe]\\
\ifluax{''}{TRUE}[FALSE]\\
\ifluaxv{''}{true}[false]\\
\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.

\begin{LTXexample}[width=0.3\linewidth]
\def\caseswitchexample{\caseswitch{\mycase}{dog=DOG, cat=CAT, __=INVALID}}
\def\mycase{dog} \caseswitchexample \\
\def\mycase{human} \caseswitchexample
\end{LTXexample}
%\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}}:

\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}



\subsection*{PDF meta data (for pdfx package)}
\cmd{\writePDFmetadatakv*[x]{kv}} Take a key-value string (eg. \cmd{title=whatever, author=me}) and then writes to the \cmd{jobname.xmpdata} file, which is used by pdfx. \cmd{*} will first clear \cmd{__PDFmetadata__} which contains the metadata. The un-starred version updates that table.
You can control the expansion of the key-val argument with \cmd{[x]}, which is fully expanded by default.
Command sequences are ultimately stripped from the values, except for \cmd{\and} is converted to \cmd{\sep} for pdfx usage (\url{https://texdoc.org/serve/pdfx/0}).
\\

\cmd{\writePDFmetadata} runs the lua function \cmd{penlight.tex.writePDFmetadata()},
which pushes the lua variable \cmd{__PDFmetadata__} (a table) to the xmpdata file.
This might be useful if you're updating \cmd{__PDFmetadata__} by some other means.

\def\thekeywords{A\and B\and C}
\def\thekeywords{ABC}
\begin{LTXexample}
\writePDFmetadatakv{author=Some One} %
\writePDFmetadatakv*[n]{author=Kale \and You\xspace} % Overwrites above. Does not expant kv
\writePDFmetadatakv{date=2024-02-01}
\end{LTXexample}



%\writePDFmetadatakv{datxe=2024-02-01} % would throw error, invalid key
%\writePDFmetadatakv[E]{keywords=\thekeywords} %

%\luadirect{texio.write_nl(\luastringE{keywords=\thekeywords})}




%%%%% SAND BOX
%
%\xtokcycleenvironment\luastringAenv
%  {\addcytoks{##1}}
%  {\processtoks{##1}}
%  {\addcytoks[1]{##1}}
%  {\addcytoks{##1}}
%  {}
%  {\cytoks\expandafter{\expandafter\luastringO\expandafter{\the\cytoks}}}
%
%\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.}








% todo why isnt this working???



%

%\pyth{} % erro


%\begin{luacode*}
%  pl.tex.wrth(('a,b,c=3'):parsekv('!naked_as_value'), 'dumy')
%\end{luacode*}

%  \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}
%

%
%\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*}


\end{document}