% Kale Ewasiuk (kalekje@gmail.com) % 2024-03-14 % 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 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/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} 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}\\ The gloals flags below are taken care of in the package options:\\ \cmd{__PL_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\\ \cmd{__PDFmetadata__} a table used to store PDF meta-data \subsubsection*{global extras} 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}. If you want global \cmd{pl.tex} funcs and vars, call \cmd{pl.make_tex_global()}\\ \subsubsection*{penlight additions} Some functionality is added to penlight and Lua. \llcmd{pl.hasval(x)} Python-like boolean testing\\ \llcmd{COMP'xyz'()} Python-like comprehensions:\\\url{https://lunarmodules.github.io/Penlight/libraries/pl.comprehension.html}\\ \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{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. \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 \\ \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{wrh}\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\\ \subsubsection*{Recording latex input} \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\\ \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. 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 for LaTeX conditionals} \cmd{\ifluax{}{}[]} and\\ \cmd{\ifluax{}{}[]} 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*{Creating and using Lua tables in LaTeX} \cmd{penlightplus} provides a Lua-table interface. Tables are stored in the \cmd{penlight.tbls} table. %%% \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{\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{\tblapp{t}{v}} append a value (integer-wise) to a table\\ \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} where d is your prefix. If d is blank, keys will be defined as \cmd{\dtbl} \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}\\ \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 else \cmd{fr}\\ \cmd{\tblprt{t}} print the table in console There are 3 ways to use the index (placeholder \cmd{{i}} above). \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} Note: for this versions: all latex tbl commands are now prefixed with \cmd{tbl}, eg., \cmd{tblget}, \cmd{tblset}. Old-style commands eg. \cmd{gettbl} will be kept as aliases for a few more releases then removed. \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} \subsubsection*{PDF meta data (for pdfx package)} \cmd{\writePDFmetadatakv*{m}} Take a key-value string (eg. \cmd{title=whatever, author=me}) and writes to the \cmd{jobname.xmpdata} file, to be used by pdfx. \cmd{*} will first clear the data\\ \cmd{\writePDFmetadata} runs the lua function \cmd{penlight.tex.writePDFmetadata()}, which pushes the lua variable \cmd{__PDFmetadata__} (a table) to the xmpdata file. %\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}