% 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{}{}[]} and\\ \cmd{\ifluaxv{}{}[]} 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}