% Kale Ewasiuk (kalekje@gmail.com)
% 2025-01-06
% 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
% 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 LaTeX \cmd{penlight[import]} package:\\
\url{https://ctan.org/pkg/penlight?lang=en}.\\
Documentation for the Lua penlight 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}.\\
A portion of this package to facilitate the creation, modification, and usage of the Lua table data structure
through a LaTeX interface has been moved to a separate package called \cmd{luatbls}:\\
\url{https://ctan.org/pkg/luatbls}.


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}
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{tbx} (which also includes all native Lua table functions), and
\cmd{pl.array2d} is aliased as \cmd{a2d}.

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

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

\llcmd{math.mod(n,d)} math modulus\\
\cmd{math.mod2(n)} mod with base 2\\

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

\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\\
\llcmd{string.}\cmd{gextract(s,pat)} extract a pattern from a string (returns capture and new string with capture removed)\\
\llcmd{string.}\cmd{gextrct(s,pat,num,join)} extract a pattern from a string (returns capture and new string with capture removed),
can specify a number of extractions. if join is specified, captures will be joined, otherwise a list is returned\\
\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{istexdim(s)} checks if a string is a valid tex dimension (eg. mm, pt, sp)\\
\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 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*}
%  local s1, s2 = ('12,13'):gxtrct('%d',nil)
%  pl.wrth({s1,s2}, 'err rat')
%--%  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)  \\
\llcmd{tablex.}\cmd{train(t,seq,reind)} return a sable based on \cmd{pl.seq.tbltrain}, \cmd{reind} will make numerical keys ordered from 1  \\

%\begin{luacode*}
%  local t = {1,2,3,a='A',b='B'}
%  penlight.wrth(penlight.tablex.train(t,'2:3,*',true),'kew')
%\end{luacode*}

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



\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.\\
\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\\
\\
\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{\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]\\
\ifluax{abc123 == nil}{Var is nil}[WRONG]\\
\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. 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}}
\def\mycase{dog} \caseswitchexample \\
\def\mycase{human} \caseswitchexample
\end{LTXexample}
%\caseswitch*{\mycase}{dog=DOG, cat=CAT, __=INVALID}









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






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


%

%\pyth{} % erro




\end{document}