summaryrefslogtreecommitdiff
path: root/macros/luatex/latex/lua-placeholders
diff options
context:
space:
mode:
Diffstat (limited to 'macros/luatex/latex/lua-placeholders')
-rw-r--r--macros/luatex/latex/lua-placeholders/README.md11
-rw-r--r--macros/luatex/latex/lua-placeholders/doc/lua-placeholders-deps.bib13
-rw-r--r--macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example-specification.yaml33
-rw-r--r--macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.json19
-rw-r--r--macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.pdfbin0 -> 67505 bytes
-rw-r--r--macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.tex225
-rw-r--r--macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.yaml14
-rw-r--r--macros/luatex/latex/lua-placeholders/doc/lua-placeholders-manual.pdfbin0 -> 256425 bytes
-rw-r--r--macros/luatex/latex/lua-placeholders/doc/lua-placeholders-manual.tex216
-rw-r--r--macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-common.lua35
-rw-r--r--macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-namespace.lua94
-rw-r--r--macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-parser.lua88
-rw-r--r--macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-types.lua273
-rw-r--r--macros/luatex/latex/lua-placeholders/scripts/lua-placeholders.lua217
-rw-r--r--macros/luatex/latex/lua-placeholders/tex/lua-placeholders.sty55
15 files changed, 1293 insertions, 0 deletions
diff --git a/macros/luatex/latex/lua-placeholders/README.md b/macros/luatex/latex/lua-placeholders/README.md
new file mode 100644
index 0000000000..9d2c9ef966
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/README.md
@@ -0,0 +1,11 @@
+# Lua(TeX) Placeholders
+
+A LaTeX package for specifying and inserting document placeholders with JSON or YAML formats.
+
+## Installation
+In order to install this package the right way, one should create the release tarball first with `make package`.
+Afterward be sure to unpack the contents of the release tarball anywhere where TeX will find it, i.e. `~/texmf`.
+
+## License
+This project is licensed under the LPPL version 1.3c and maintained by Erik Nijenhuis.
+See [LICENSE.txt](LICENSE.txt) for more information.
diff --git a/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-deps.bib b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-deps.bib
new file mode 100644
index 0000000000..2e80c1b269
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-deps.bib
@@ -0,0 +1,13 @@
+
+@misc{lyaml,
+ title = {lyaml},
+ author = {Andrew Danforth},
+ note = {Accessed: 6 January, 2024},
+ howpublished = {\url{https://github.com/gvvaughan/lyaml} and \url{https://luarocks.org/modules/gvvaughan/lyaml}},
+}
+
+@misc{libYAML,
+ title = {libYAML},
+ note = {Accessed: 6 January, 2024},
+ howpublished = {\url{https://pyyaml.org/wiki/LibYAML} and \url{https://packages.msys2.org/package/mingw-w64-x86_64-libyaml}},
+}
diff --git a/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example-specification.yaml b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example-specification.yaml
new file mode 100644
index 0000000000..2d00cecaf3
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example-specification.yaml
@@ -0,0 +1,33 @@
+bool example:
+ type: bool
+ default: false
+string example:
+ type: string
+ placeholder: A string
+number example:
+ type: number
+ default: -1
+list example:
+ type: list
+ item type: string
+ default:
+ - A string
+ - A second string
+object example:
+ type: object
+ fields:
+ name:
+ type: string
+ placeholder: Your name
+ email:
+ type: string
+ placeholder: Your email
+table example:
+ type: table
+ columns:
+ description:
+ type: string
+ placeholder: The description
+ price:
+ type: number
+ placeholder: The price
diff --git a/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.json b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.json
new file mode 100644
index 0000000000..e65389b1aa
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.json
@@ -0,0 +1,19 @@
+{
+ "bool example": true,
+ "string example": "PeelInc.",
+ "number example": 1,
+ "list example": ["Tomatoes", "Potatoes"],
+ "object example": {
+ "name": "John Doe",
+ "email": "j.doe@example.com"
+ },
+ "table example": [
+ {
+ "description": "Peeling tomatoes",
+ "price": 50.00
+ }, {
+ "description": "Peeling potatoes",
+ "price": 25.00
+ }
+ ]
+}
diff --git a/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.pdf b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.pdf
new file mode 100644
index 0000000000..fcdf27afab
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.pdf
Binary files differ
diff --git a/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.tex b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.tex
new file mode 100644
index 0000000000..805dd2469c
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.tex
@@ -0,0 +1,225 @@
+%% example.tex
+%% Copyright 2024 E. Nijenhuis
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3c
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+% http://www.latex-project.org/lppl.txt
+% and version 1.3c or later is part of all distributions of LaTeX
+% version 2005/12/01 or later.
+%
+% This work has the LPPL maintenance status ‘maintained’.
+%
+% The Current Maintainer of this work is E. Nijenhuis.
+%
+% This work consists of the files lua-placeholders.sty lua-placeholders-manual.pdf
+% lua-placeholders.lua lua-placeholders-common.lua lua-placeholders-namespace.lua lua-placeholders-parser.lua
+% and lua-placeholders-types.lua
+
+\documentclass{article}
+\usepackage{gitinfo-lua}
+\usepackage{lua-placeholders}
+\usepackage{listings}
+\usepackage{amsmath}
+\usepackage{calc}
+
+\loadrecipe[\jobname]{example-specification.yaml}
+
+\setlength{\parindent}{0pt}
+
+\begin{document}
+ \title{Lua \paramplaceholder{placeholders} Example\thanks{This example corresponds to \texttt{lua-placeholders} version \gitversion{} written on \gitdate.}}
+ \author{\dogitauthors[\\]}
+ \maketitle
+
+ \section*{Basics}
+ Wrong parameter:\\
+
+ \lstinline[style=TeX,morekeywords={param}]|\param{non existing}|
+ $\implies$
+ \param{non existing}\\
+
+ Conditional Parameter:\\
+
+ \lstinline[style=TeX,morekeywords={hasparam}]|\hasparam{list example}{is set}{is not set}|
+ $\implies$
+ \hasparam{list example}{is set}{is not set}
+
+ \section*{Before values loaded}
+
+ Boolean example:\\
+
+ \lstinline[style=TeX,morekeywords={param}]|\param{bool example}|
+ $\implies$
+ \param{bool example}\\
+
+ \lstinline[style=TeX,morekeywords={ifparam}]|\ifparam{bool example}{TRUE}{FALSE}|
+ $\implies$
+ \ifparam{bool example}{TRUE}{FALSE}\\
+
+ String example:\\
+
+ \lstinline[style=TeX,morekeywords={param}]|\param{string example}|
+ $\implies$
+ ``\param{string example}''\\
+
+ Number example:\\
+
+ \lstinline[style=TeX,morekeywords={param}]|\param{number example}|
+ $\implies$
+ \param{number example}\\
+
+ List example:\\
+
+ \lstinline[style=TeX,morekeywords={param}]|\param{list example}|
+ $\implies$
+ \param{list example}\\
+
+ \begin{lstlisting}[language={[LaTeX]TeX},morekeywords={formatitem,forlistitem}]
+\begin{enumerate}
+ \newcommand\formatitem[1]{\item #1}
+ \forlistitem{list example}{formatitem}
+\end{enumerate}
+ \end{lstlisting}
+ $\implies$
+ \begin{enumerate}
+ \newcommand\formatitem[1]{\item #1}
+ \forlistitem{list example}{formatitem}
+ \end{enumerate}
+
+ Object example:\\
+
+ \lstinline[style=TeX,morekeywords={paramfield}]|\paramfield{object example}{name}|\\
+ \lstinline[style=TeX,morekeywords={paramfield}]|\paramfield{object example}{email}|\\
+ $\implies$
+ \paramfield{object example}{name}
+ \paramfield{object example}{email}\\
+
+ \begin{lstlisting}[style=TeX,morekeywords={name,email}]
+\newcommand\name{...}
+\begin{paramobject}{object example}
+ \name \email
+\end{paramobject}
+% And here it works again
+\name
+ \end{lstlisting}
+ $\implies$
+ \newcommand\name{...}%
+ \parbox{\linewidth}{
+ \begin{paramobject}{object example}
+ \name \email
+ \end{paramobject}
+ \name
+ }\\
+
+ Table example:\\
+
+ \begin{lstlisting}[style=TeX,morekeywords={formatrow,fortablerow,description,price}]
+\newcommand\formatrow{\description & \price \\}%
+\begin{tabular}{l | l}
+ \textbf{Description} & \textbf{Price} \\ \hline
+ \fortablerow{table example}{formatrow}
+\end{tabular}
+ \end{lstlisting}
+ $\implies$
+ \newcommand\formatrow{\description & \price \\}%
+ \begin{tabular}{l | l}
+ \textbf{Description} & \textbf{Price} \\ \hline
+ \fortablerow{table example}{formatrow}
+ \end{tabular}
+
+
+ \section*{After values loaded}
+ \loadpayload[\jobname]{example.yaml}
+
+ Boolean example:\\
+
+ \lstinline[style=TeX,morekeywords={param}]|\param{bool example}|
+ $\implies$
+ \param{bool example}\\
+
+ \lstinline[style=TeX,morekeywords={ifparam}]|\ifparam{bool example}{TRUE}{FALSE}|
+ $\implies$
+ \ifparam{bool example}{TRUE}{FALSE}\\
+
+ String example:\\
+
+ \lstinline[style=TeX,morekeywords={param}]|\param{string example}|
+ $\implies$
+ ``\param{string example}''\\
+
+ Number example:\\
+
+ \lstinline[style=TeX,morekeywords={param}]|\param{number example}|
+ $\implies$
+ \param{number example}\\
+
+ List example:\\
+
+ \lstinline[style=TeX,morekeywords={param}]|\param{list example}|
+ $\implies$
+ \param{list example}\\
+
+ \begin{lstlisting}[language={[LaTeX]TeX},morekeywords={formatitem,forlistitem}]
+\begin{enumerate}
+ \newcommand\formatitem[1]{\item #1}
+ \forlistitem{list example}{formatitem}
+\end{enumerate}
+ \end{lstlisting}
+ $\implies$
+ \begin{enumerate}
+ \newcommand\formatitem[1]{\item #1}
+ \forlistitem{list example}{formatitem}
+ \end{enumerate}
+
+ Object example:\\
+
+ \lstinline[style=TeX,morekeywords={paramfield}]|\paramfield{object example}{name}|\\
+ \lstinline[style=TeX,morekeywords={paramfield}]|\paramfield{object example}{email}|\\
+ $\implies$
+ \paramfield{object example}{name}
+ \paramfield{object example}{email}\\
+
+ \begin{lstlisting}[style=TeX,morekeywords={name,email}]
+\newcommand\name{...}
+\begin{paramobject}{object example}
+ \name \email
+\end{paramobject}
+% And here it works again
+\name
+ \end{lstlisting}
+ $\implies$
+ \parbox{\linewidth}{
+ \begin{paramobject}{object example}
+ \name \email
+ \end{paramobject}
+ \name
+ }\\
+
+ Table example:\\
+
+ \begin{lstlisting}[style=TeX,morekeywords={formatrow,fortablerow,description,price}]
+\newcommand\formatrow{\description & \price \\}%
+\begin{tabular}{l | l}
+ \textbf{Description} & \textbf{Price} \\ \hline
+ \fortablerow{table example}{formatrow}
+\end{tabular}
+ \end{lstlisting}
+ $\implies$
+ \begin{tabular}{l | l}
+ \textbf{Description} & \textbf{Price} \\ \hline
+ \fortablerow{table example}{formatrow}
+ \end{tabular}
+
+ \section*{Specification File}
+ \lstinputlisting[language=YAML,numbers=left,xleftmargin=15pt,caption={example-specification.yaml},columns=fullflexible]{example-specification.yaml}
+
+ \clearpage
+
+ \section*{Payload File}
+ \lstinputlisting[language=YAML,numbers=left,xleftmargin={15pt},caption={example.yaml},columns=fullflexible]{example.yaml}
+
+ \section*{Fallback Payload}
+ \lstinputlisting[language=JSON,numbers=left,xleftmargin={15pt},caption={example.json},columns=fullflexible]{example.json}
+\end{document}
diff --git a/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.yaml b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.yaml
new file mode 100644
index 0000000000..1806fad8da
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-example/example.yaml
@@ -0,0 +1,14 @@
+bool example: true
+string example: PeelInc.
+number example: 1
+list example:
+ - Tomatoes
+ - Potatoes
+object example:
+ name: John Doe
+ email: j.doe@example.com
+table example:
+ - description: Peeling tomatoes
+ price: 50.00
+ - description: Peeling potatoes
+ price: 25.00
diff --git a/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-manual.pdf b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-manual.pdf
new file mode 100644
index 0000000000..499978dbf0
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-manual.pdf
Binary files differ
diff --git a/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-manual.tex b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-manual.tex
new file mode 100644
index 0000000000..776ad3af5d
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/doc/lua-placeholders-manual.tex
@@ -0,0 +1,216 @@
+%% lua-placeholders-manual.tex
+%% Copyright 2024 E. Nijenhuis
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3c
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+% http://www.latex-project.org/lppl.txt
+% and version 1.3c or later is part of all distributions of LaTeX
+% version 2005/12/01 or later.
+%
+% This work has the LPPL maintenance status ‘maintained’.
+%
+% The Current Maintainer of this work is E. Nijenhuis.
+%
+% This work consists of the files lua-placeholders.sty lua-placeholders-manual.pdf
+% lua-placeholders.lua lua-placeholders-common.lua lua-placeholders-namespace.lua lua-placeholders-parser.lua
+% and lua-placeholders-types.lua
+
+\documentclass{ltxdoc}
+\usepackage[english]{babel}
+\usepackage[titlepage,authors]{gitinfo-lua}
+\usepackage{listings}
+\usepackage{lua-placeholders}
+\usepackage{multicol}
+\usepackage{calc}
+\usepackage{fmtcount}
+\usepackage[nodayofweek]{datetime}
+\usepackage{hyperref,embedfile}
+\usepackage{textcomp}
+\usepackage{attachfile2}
+\usepackage{enumitem}
+\usepackage{biblatex}
+
+\DefineBibliographyStrings{english}{%
+ bibliography = {References},
+}
+
+%! suppress = MissingBibliographystyle
+\bibliography{lua-placeholders-deps}
+
+\embedfile[desc={Parameter Specification Example},filespec=example.pdf]{lua-placeholders-example/example.pdf}
+\embedfile[desc={Parameter Specification Example (YAML)},filespec=example-specification.yaml]{lua-placeholders-example/example-specification.yaml}
+\embedfile[desc={Values Specification Example},filespec=example.yaml]{lua-placeholders-example/example.yaml}
+\embedfile[desc={Values Fallback Specification Example},filespec=example.json]{lua-placeholders-example/example.json}
+
+\def\projecturl{https://github.com/Xerdi/lua-placeholders}
+
+\newcommand\showexample[5][15pt]{%
+\begin{minipage}[t]{.5\linewidth - .5 \columnsep}%
+\lstinputlisting[firstnumber={#2},linerange={#3},style=YAML,frame=single,numbers=left,xleftmargin=0pt,numbersep=10pt,columns=fullflexible]{lua-placeholders-example/example-specification.yaml}
+\end{minipage}\hspace*{\columnsep}%
+\begin{minipage}[t]{.5\linewidth - .5 \columnsep}%
+\lstinputlisting[firstnumber={#4},linerange={#5},style=YAML,frame=single,numbers=left,xleftmargin={#1},numbersep=10pt,columns=fullflexible]{lua-placeholders-example/example.yaml}
+\end{minipage}\\%
+}
+
+\begin{document}
+ \title{Lua \paramplaceholder{placeholders}\thanks{This document corresponds to \texttt{lua-placeholders} version \gitversion, written on \gitdate}}
+ \maketitle
+ \begin{abstract}
+ A package for creating `example' documents, which show parameters as placeholders and `actual copy' documents, which show parameters with the real data, written in Lua\TeX{}.
+ \end{abstract}
+
+ \tableofcontents
+
+ \clearpage
+
+ \section{Introduction}
+ This package is meant for setting parameters in a Lua\LaTeX{} document in a more programmatically way with YAML\@.
+ Parameters can be specified by adding a `recipe' file.
+ These recipe files describe the parameter's type, placeholders and/or default values.
+ From thereon, the placeholders can be displayed in the document and an `\textit{example}' document can be created.
+ An `\textit{actual copy}' document can be created by loading additional payload files, which all must correspond to a recipe file.
+
+ \subsection{Pros}
+ \begin{enumerate}[align=left]
+ \item Create an `\textit{example}' or `\textit{actual copy}' document with the same \LaTeX{} source and YAML recipe.
+ \item Integration within systems is as easy as compiling a normal \LaTeX{} document, especially thanks to the fallback support to JSON, which is quite renown in programming languages.
+ \item Supports multiple data types and formatting macros which work in most \TeX{} environments, like \texttt{enumerate} or \texttt{tabular}.
+ \end{enumerate}
+
+ \subsection{Cons}
+ \begin{enumerate}[align=left]
+ \item The package only works with Lua\LaTeX{}.
+ \item In order for the files to be loaded, commandline option `\texttt{--shell-escape}' is required.
+ \item For YAML support, there needs to be a Lua setup with certain dependencies, which can be quite hard to setup on some systems.
+ \end{enumerate}
+
+ \subsection{Prerequisites}
+ If you're using JSON as \meta{recipe} and \meta{payload} format, the following requirements are no longer needed, since Lua\TeX{} already supports JSON formats out of the box.
+
+ For YAML support, however, this package requires the \texttt{lyaml}\cite{lyaml} Lua module for parsing the YAML files.
+ This also includes the \texttt{libYAML}\cite{libYAML} platform dependent library and optionally LuaRocks for installing \texttt{lyaml}.
+ Another requirement is Lua, which version meets the Lua version used by Lua\TeX{}.
+ If no \texttt{LUA\_PATH} is set, and you use LuaRocks, this package tries to call the LuaRocks executable to find the \texttt{LUA\_PATH}.
+ If \texttt{lyaml} can't be loaded, this package will fall back on accepting JSON files only.
+
+ \clearpage
+ \section{Usage}
+ This section describes the basic commands of \texttt{lua-placeholders}.
+ For more detail about type specific commands or the behavior of types with commands described here, see section~\ref{sec:spec}.\\
+
+ \subsection{Configuration}
+ \DescribeMacro{\strictparams} In order to give an error when values are missing, the \cmd{\strictparams}\footnote{The \cmd{\strictparams} command is still under development.} command can be used.
+ Make sure to do it before loading any \meta{recipe} and \meta{payload} files.
+ \DescribeMacro{\loadrecipe}
+ In order to load a recipe the macro \cmd{\loadrecipe}\oarg{namespace}\marg{filename} can be used.
+ Where the \meta{filename} is a YAML file with its corresponding extension.
+ The optional \meta{namespace} is only a placeholder in order to prevent any conflicts between duplicate \meta{key}s.
+ If left out, the \meta{namespace} defaults to the base name of the filename.
+ \DescribeMacro{\loadpayload} The same behaviour counts for \cmd{\loadpayload}\oarg{namespace}\marg{filename}.
+ The order of loading \meta{recipe} and \meta{payload} files doesn't matter.
+ If the \meta{payload} file got loaded first, it will be yielded until the corresponding \meta{recipe} file is loaded.
+
+ All other macros of this package also take the optional \meta{namespace}, which by default is equal to \cmd{\jobname}.
+ \DescribeMacro{\setnamespace} This default \meta{namespace} can be changed with \cmd{\setnamespace}\marg{new default namespace}.\\
+
+ \subsection{Displaying Parameters}
+ For displaying variables, the commands \cmd{\param} and \cmd{\PARAM} share the same interface.
+ \DescribeMacro{\param} The most trivial, displaying the variable as-is, is \cmd{\param}\oarg{namespace}\marg{key}.
+ \DescribeMacro{\PARAM} The \cmd{\PARAM} however, shows the value as upper case.
+
+ In some cases, it's required to output the text without any \TeX{} related functionality.
+ Another case is that some environments don't take macros with optional arguments well.
+ \DescribeMacro{\rawparam} For these cases there is \cmd{\rawparam}\marg{namespace}\marg{key}, which takes the namespace as mandatory argument, instead of optional, and doesn't output fancy \TeX{} placeholders.\\
+
+ \DescribeMacro{\hasparam} To check whether a parameter is set, the \cmd{\hasparam}\oarg{namespace}\marg{key}\marg{true case}\marg{false case} command is used.
+ However, a more robust way is using \LaTeX{} hooks.
+ For recipes being loaded, the hook \texttt{namespace/}\meta{name} is triggered once.
+ For payloads being loaded, the hook \texttt{namespace/}\meta{name}\texttt{loaded} is triggered once.
+ For more information on \LaTeX{} hooks, read the \texttt{lthooks} manual.
+
+ \clearpage
+
+ \section{Parameter Specification}\label{sec:spec}
+ Every parameter specified has a \meta{type} set.
+ Optionally there is a choice between setting a \meta{default} or a \meta{placeholder} for the parameter.
+ \begin{description}
+ \item[bool] Next to the textual representation of \textit{true} and \textit{false}, it provides a \LaTeX{} command using the \texttt{ifthen} package.
+ Therefore, only the \meta{default} setting makes sense.\\[5pt]
+ \hspace*{5pt}\parbox{\linewidth-5pt}{%
+ \hfill\texttt{Recipe}\hfill\hspace*{\columnsep}%
+ \hfill\texttt{Payload}\hfill\hspace*{\columnsep}}\\%
+ \showexample{1}{1-3}{1}{1-1}
+ \DescribeMacro{\param}
+ With a boolean type the \cmd{\param}\oarg{namespace}\marg{name} returns either \textit{true} or \textit{false}.
+ \DescribeMacro{\ifparam}
+ Additionally, it provides the \cmd{\ifparam}\oarg{namespace}\marg{name}\marg{true code}\marg{false code} command for top level boolean types.
+ The macro is just a wrapper for the boolean package \texttt{ifthen}, which supports spaces in names.
+ \item[string] representing a piece of text.
+ All \TeX{} related symbols in the text, like \textbackslash, \% and \#, are escaped.\\
+ \showexample{4}{4-6}{2}{2-2}
+ \DescribeMacro{\param} A string type can easily be placed in \LaTeX{} using the \cmd{\param} command.
+ \item[number] representing a number, like the number type of Lua.
+ In most cases it's necessary to use \meta{default} instead of \meta{placeholder}, especially when the number is used in calculations, since a placeholder will cause errors in \LaTeX{} calculations.\\
+ \showexample{7}{7-9}{3}{3-3}
+ A number type can be used with \cmd{\param}, just like the string type.
+ \DescribeMacro{\numparam} However, the \cmd{\numparam}\oarg{namespace}\marg{name} command uses \cmd{\numprint} to properly format the number according to the selected language.
+ Read the documentation of package \texttt{numprint} for more information.
+ \item[list] representing a list of values.
+ The value type is specified by \meta{value type}.
+ A \meta{default} setting can be set.
+ Due to its structure, a \meta{placeholder} would be somewhat incompatible with the corresponding macros.
+ However, a placeholder can be simulated by setting the placeholders as children of the \meta{default} list, as demonstrated in the example.\\
+ \showexample{10}{10-15}{4}{4-6}
+ \DescribeMacro{\param}
+ Command \cmd{\param} concatenates every item with command \cmd{\paramlistconjunction}.
+ \DescribeMacro{\paramlistconjunction}
+ By default, the conjunction is set to `\texttt{,\textasciitilde}'.
+
+ \DescribeMacro{\forlistitem}
+ There's also the \cmd{\forlistitem}\oarg{namespace}\marg{name}\marg{csname} command, which takes an additional \meta{csname} and will execute it for every item in the list.
+ This command doesn't handle advanced features like altering the conjunction.
+ Though, some utility commands will be set, which are only available in the \meta{csname}s implementation, in order to achieve the same goal.
+ \item[object] representing a list of key value pairs.
+ This parameter type requires a \meta{fields} specification to be set.
+ Any field must be of type \texttt{bool}, \texttt{number} or \texttt{string}.\\
+ \showexample{16}{16-24}{7}{7-9}
+ There is no support for the \cmd{\param} command.
+ \DescribeMacro{\paramfield}
+ In order to show to contents there is the \cmd{\paramfield}\oarg{namespace}\marg{name}\marg{field} command.
+ However, unlike the common command \cmd{\param}, the command \cmd{\hasparam} does work with object types.
+
+ \DescribeEnv{paramobject} There's also the \texttt{paramobject} environment, which takes an optional \meta{namespace} and takes the \meta{name} of the object as arguments and then defines for every field name a corresponding command.
+ Every command is appended with the \cmd{\xspace} command to prevent gobbling a space.
+ In other words, the author doesn't have to end the command with accolades `\{\}' to get the expected output.
+
+ \item[table] representing a table.
+ This parameter type requires a \meta{columns} specification to be set.
+ The \meta{columns} describes each column by name with its own type specification.
+ Like the object field, only the types \texttt{bool}, \texttt{number} and \texttt{string} are supported column types.\\
+ \showexample[20pt]{25}{25}{10}{10}
+ \DescribeMacro{\fortablerow}
+ Like the object, the table has no support for \cmd{\param}, but comes with a table specific command \cmd{\fortablerow}\oarg{namespace}\marg{name}\marg{csname}.
+ The control sequence name \meta{csname} is a user-defined command with no arguments, containing any of the column names in a command form.
+ For example, the name \texttt{example} would be accessible as \cmd{\example} in the user-defined command body.
+
+ Like the object field, a table cell doesn't require accolades, though, this is due to the Lua implementation behind it.
+ Technically every command in the user-defined command body is replaced with the variable in Lua, instead of redefining the command itself for every row, preventing issues with macro expansion between table rows and also column separators in \TeX{}.
+
+ \end{description}
+
+ \clearpage
+
+ \printbibliography[heading=bibnumbered]
+
+ \clearpage
+ \section{Example}
+
+ The source file \texttt{example.tex} is a perfect demonstration of all macros in action.
+ It shows perfectly what happens when there's a \meta{payload} file loaded and when not.
+ The result of this example \attachfile[icon=Paperclip,description={ELPI Example v\gitversion}]{lua-placeholders-example/example.pdf} is attached in the digital version of this document.
+
+ \lstinputlisting[language={[LaTeX]TeX},frame=single,caption={\ttfamily example.tex},captionpos=t,numbers=left,keywordsprefix={\\},firstnumber=20,firstline=20,columns=fullflexible]{lua-placeholders-example/example.tex}
+\end{document}
diff --git a/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-common.lua b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-common.lua
new file mode 100644
index 0000000000..e68a5484e8
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-common.lua
@@ -0,0 +1,35 @@
+-- lua-placeholders-common.lua
+-- Copyright 2024 E. Nijenhuis
+--
+-- This work may be distributed and/or modified under the
+-- conditions of the LaTeX Project Public License, either version 1.3c
+-- of this license or (at your option) any later version.
+-- The latest version of this license is in
+-- http://www.latex-project.org/lppl.txt
+-- and version 1.3c or later is part of all distributions of LaTeX
+-- version 2005/12/01 or later.
+--
+-- This work has the LPPL maintenance status ‘maintained’.
+--
+-- The Current Maintainer of this work is E. Nijenhuis.
+--
+-- This work consists of the files lua-placeholders.sty
+-- lua-placeholders-manual.pdf lua-placeholders.lua
+-- lua-placeholders-common.lua lua-placeholders-namespace.lua
+-- lua-placeholders-parser.lua and lua-placeholders-types.lua
+
+function table.copy(t)
+ local u = { }
+ for k, v in pairs(t) do
+ u[k] = v
+ end
+ return setmetatable(u, getmetatable(t))
+end
+
+lua_placeholders_toks = {
+ new_bool = token.create('provideboolean'),
+ set_bool = token.create('setboolean'),
+ list_conj = token.create('paramlistconjunction'),
+ placeholder_format = token.create('paramplaceholder'),
+ unknown_format = token.create('paramnotfound')
+}
diff --git a/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-namespace.lua b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-namespace.lua
new file mode 100644
index 0000000000..fb84447a25
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-namespace.lua
@@ -0,0 +1,94 @@
+-- lua-placeholders-namespace.lua
+-- Copyright 2024 E. Nijenhuis
+--
+-- This work may be distributed and/or modified under the
+-- conditions of the LaTeX Project Public License, either version 1.3c
+-- of this license or (at your option) any later version.
+-- The latest version of this license is in
+-- http://www.latex-project.org/lppl.txt
+-- and version 1.3c or later is part of all distributions of LaTeX
+-- version 2005/12/01 or later.
+--
+-- This work has the LPPL maintenance status ‘maintained’.
+--
+-- The Current Maintainer of this work is E. Nijenhuis.
+--
+-- This work consists of the files lua-placeholders.sty
+-- lua-placeholders-manual.pdf lua-placeholders.lua
+-- lua-placeholders-common.lua lua-placeholders-namespace.lua
+-- lua-placeholders-parser.lua and lua-placeholders-types.lua
+
+require('lua-placeholders-types')
+
+local namespace = {
+ strict = false,
+ recipe_file = nil,
+ recipe_loaded = false,
+ payload_file = nil,
+ payload_loaded = false
+}
+
+function namespace.parse_filename(path)
+ local abs_path = kpse.find_file(path)
+ local _, _, name = abs_path:find('/?%w*/*(%w+)%.%w+')
+ return name, abs_path
+end
+
+function namespace:new(_o)
+ local o = {
+ recipe_file = _o.recipe_file,
+ payload_file = _o.payload_file,
+ strict = _o.strict,
+ values = {}
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function namespace:load_recipe(params)
+ for key, opts in pairs(params) do
+ local param = base_param.define(key, opts)
+ if param then
+ self.values[key] = param
+ end
+ end
+ self.recipe_loaded = true
+end
+
+function namespace:load_payload(values)
+ if self.recipe_loaded then
+ if values then
+ for key, value in pairs(values) do
+ if self.values[key] then
+ local param = self.values[key]
+ param:load(key, value)
+ else
+ texio.write_nl('Warning: passed an unknown key ' .. key)
+ end
+ texio.write_nl('Key' .. key)
+ end
+ else
+ texio.write_nl('Warning: Payload file was empty')
+ end
+ self.payload_loaded = true
+ end
+end
+
+function namespace:param(key)
+ if not self.recipe_loaded then
+ tex.error('Error: Recipe was not loaded yet...')
+ return nil
+ end
+ if not self.payload_loaded then
+ if self.strict then
+ tex.error('Error: Payload was not loaded yet...')
+ return nil
+ else
+ texio.write_nl('Warning: Payload was not loaded yet...')
+ end
+ end
+ return self.values[key]
+end
+
+return namespace
diff --git a/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-parser.lua b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-parser.lua
new file mode 100644
index 0000000000..1fa43227a4
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-parser.lua
@@ -0,0 +1,88 @@
+-- lua-placeholders-parser.lua
+-- Copyright 2024 E. Nijenhuis
+--
+-- This work may be distributed and/or modified under the
+-- conditions of the LaTeX Project Public License, either version 1.3c
+-- of this license or (at your option) any later version.
+-- The latest version of this license is in
+-- http://www.latex-project.org/lppl.txt
+-- and version 1.3c or later is part of all distributions of LaTeX
+-- version 2005/12/01 or later.
+--
+-- This work has the LPPL maintenance status ‘maintained’.
+--
+-- The Current Maintainer of this work is E. Nijenhuis.
+--
+-- This work consists of the files lua-placeholders.sty
+-- lua-placeholders-manual.pdf lua-placeholders.lua
+-- lua-placeholders-common.lua lua-placeholders-namespace.lua
+-- lua-placeholders-parser.lua and lua-placeholders-types.lua
+
+local LUA_VERSION = string.sub(_VERSION, 5, -1)
+
+yaml_supported = false
+
+-- Check if LUA_PATH is set
+local current_path = os.getenv('LUA_PATH')
+if current_path then
+ texio.write_nl('Info: LUA path setup up correctly. Great job!')
+else
+ -- Set the LUA_PATH and LUA_CPATH using 'luarocks -lua-version <LuaLaTeX version> path'
+ texio.write_nl('Warning: No LUA_PATH set. Looking for LuaRocks installation...')
+ local handle = io.popen('luarocks --lua-version ' .. LUA_VERSION .. ' path')
+ if handle then
+ local buffer = handle:read('*a')
+ if handle:close() then
+ texio.write_nl('Info: luarocks command executed successfully')
+ local lua_path, lua_search_count = string.gsub(buffer, ".*LUA_PATH='([^']*)'.*", "%1")
+ local lua_cpath, clua_search_count = string.gsub(buffer, ".*LUA_CPATH='([^']*)'.*", "%1")
+ if lua_search_count > 0 then
+ texio.write_nl('Info: Setting LUA_PATH from LuaRocks', lua_path)
+ package.path = lua_path
+ end
+ if clua_search_count > 0 then
+ texio.write_nl('Info: Setting LUA_CPATH from LuaRocks', lua_cpath)
+ package.cpath = lua_cpath
+ end
+ else
+ texio.write_nl('Error: couldn\'t find LuaRocks installation')
+ texio.write_nl("Info: LUA PATH:\n\t" .. string.gsub(package.path, ';', '\n\t') .. '\n\n')
+ end
+ else
+ tex.error('Error: could not open a shell. Is shell-escape turned on?')
+ end
+end
+texio.write_nl('\n')
+
+-- For falling back to JSON
+require('lualibs')
+
+-- Require YAML configuration files
+-- Make sure to have the apt package lua-yaml installed
+local status, yaml = pcall(require, 'lyaml')
+if status then
+ yaml_supported = true
+else
+ texio.write_nl('Warning: No YAML support.')
+ texio.write_nl(yaml)
+ texio.write_nl('Info: Falling back to JSON.')
+end
+
+return function(filename)
+ local _, _, ext = string.match(filename, "(.-)([^\\]-([^\\%.]+))$")
+ local file = io.open(filename, "rb")
+ if not file then
+ error('File ' .. filename .. ' doesn\'t exist...')
+ end
+ local raw = file:read "*a"
+ file:close()
+ if ext == 'json' then
+ return utilities.json.tolua(raw)
+ else
+ if yaml_supported then
+ return yaml.load(raw)
+ else
+ tex.error('Error: no YAML support!')
+ end
+ end
+end
diff --git a/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-types.lua b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-types.lua
new file mode 100644
index 0000000000..faa637c8f7
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders-types.lua
@@ -0,0 +1,273 @@
+-- lua-placeholders-types.lua
+-- Copyright 2024 E. Nijenhuis
+--
+-- This work may be distributed and/or modified under the
+-- conditions of the LaTeX Project Public License, either version 1.3c
+-- of this license or (at your option) any later version.
+-- The latest version of this license is in
+-- http://www.latex-project.org/lppl.txt
+-- and version 1.3c or later is part of all distributions of LaTeX
+-- version 2005/12/01 or later.
+--
+-- This work has the LPPL maintenance status ‘maintained’.
+--
+-- The Current Maintainer of this work is E. Nijenhuis.
+--
+-- This work consists of the files lua-placeholders.sty
+-- lua-placeholders-manual.pdf lua-placeholders.lua
+-- lua-placeholders-common.lua lua-placeholders-namespace.lua
+-- lua-placeholders-parser.lua and lua-placeholders-types.lua
+
+require('lua-placeholders-common')
+
+function table.copy(t)
+ local u = { }
+ for k, v in pairs(t) do
+ u[k] = v
+ end
+ return setmetatable(u, getmetatable(t))
+end
+
+base_param = {}
+function base_param:new(o)
+ o = o or {}
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function base_param:is_set()
+ return self and ((self.values or self.fields or self.value) ~= nil)
+end
+
+function base_param:val()
+ return self.value or self.values or self.default
+end
+
+function base_param:to_upper()
+ local val = self:val()
+ if type(val) == 'string' then
+ return val:upper()
+ elseif type(self.placeholder) == 'string' then
+ return '[' .. self.placeholder:upper() .. ']'
+ end
+end
+
+function base_param:print_val()
+ local value = self:val()
+ if value ~= nil then
+ tex.write(value)
+ else
+ tex.sprint(lua_placeholders_toks.placeholder_format, '{', self.placeholder or self.key, '}')
+ end
+end
+
+bool_param = base_param:new{
+ type = 'bool'
+}
+
+function bool_param:new(key, _o)
+ local o = {
+ key = key,
+ default = _o.default
+ }
+ setmetatable(o, self)
+ self.__index = self
+ tex.sprint(lua_placeholders_toks.new_bool, '{', o.key, '}')
+ return o
+end
+
+function bool_param:val()
+ local value
+ if self.value ~= nil then
+ value = tostring(self.value)
+ elseif self.default ~= nil then
+ value = tostring(self.default)
+ else
+ value = 'false'
+ end
+ return value
+end
+
+function bool_param:set_bool(key)
+ tex.sprint(lua_placeholders_toks.set_bool, '{', key, '}{', self:val(), '}')
+end
+
+str_param = base_param:new{
+ type = 'string'
+}
+
+function str_param:new(key, _o)
+ local o = {
+ key = key,
+ placeholder = _o.placeholder
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+number_param = base_param:new{
+ type = 'number'
+}
+
+function number_param:new(key, _o)
+ local o = {
+ key = key,
+ placeholder = _o.placeholder,
+ default = _o.default
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function number_param:val()
+ if self.value or self.default then
+ return tex.number(self.value or self.default)
+ end
+end
+
+function number_param:print_num()
+ local val = self:val()
+ if val then
+ tex.print('\\numprint{' .. val .. '}')
+ else
+ tex.sprint(lua_placeholders_toks.placeholder_format, '{', self.placeholder or self.key, '}')
+ end
+end
+
+list_param = base_param:new{
+ type = 'list'
+}
+
+function list_param:new(key, _o)
+ local o = {
+ key = key,
+ item_type = base_param.define('list_item', { type = (_o["item type"] or 'string') }),
+ default = {}
+ }
+ if _o.default then
+ for _, default_val in ipairs(_o.default) do
+ local v = table.copy(o.item_type)
+ v:load('list_item', default_val)
+ table.insert(o.default, v)
+ end
+ end
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function list_param:val()
+ return self.values or self.default or {}
+end
+
+function list_param:print_val()
+ local list = self:val()
+ if #list > 0 then
+ if not self.values then
+ tex.sprint(lua_placeholders_toks.placeholder_format, '{')
+ end
+ tex.sprint(list[1]:val())
+ for i = 2, #list do
+ tex.sprint(lua_placeholders_toks.list_conj, list[i]:val())
+ end
+ if not self.values then
+ tex.sprint('}')
+ end
+ end
+end
+
+object_param = base_param:new{
+ type = 'object'
+}
+
+function object_param:new(key, _o)
+ local o = {
+ key = key,
+ fields = {},
+ default = _o.default
+ }
+ for _key, field in pairs(_o.fields) do
+ o.fields[_key] = base_param.define(_key, field)
+ end
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+table_param = base_param:new{
+ type = 'table'
+}
+
+function table_param:new(key, _o)
+ local o = {
+ key = key,
+ columns = {}
+ }
+ for col_key, col in pairs(_o.columns) do
+ o.columns[col_key] = base_param.define(col_key, col)
+ end
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function base_param.define(key, o)
+ if o.type then
+ if o.type == 'bool' then
+ return bool_param:new(key, o)
+ elseif o.type == 'string' then
+ return str_param:new(key, o)
+ elseif o.type == 'number' then
+ return number_param:new(key, o)
+ elseif o.type == 'list' then
+ return list_param:new(key, o)
+ elseif o.type == 'object' then
+ return object_param:new(key, o)
+ elseif o.type == 'table' then
+ return table_param:new(key, o)
+ else
+ texio.write_nl('Warning: no such parameter type ' .. o.type)
+ end
+ else
+ error('ERROR: parameter must have a "type" field')
+ end
+end
+
+function base_param:load(key, value)
+ if self.type == 'list' then
+ self.values = {}
+ for _, val in ipairs(value) do
+ local param = table.copy(self.item_type)
+ param:load('list-item', val)
+ table.insert(self.values, param)
+ end
+ elseif self.type == 'table' then
+ self.values = {}
+ for _, row_vals in ipairs(value) do
+ local row = {}
+ for col_key, col in pairs(self.columns) do
+ local cell = table.copy(col)
+ cell:load(col.key, row_vals[col_key])
+ row[col_key] = cell
+ end
+ table.insert(self.values, row)
+ end
+ elseif self.type == 'object' then
+ for field_key, field in pairs(self.fields) do
+ local field_val = value[field_key]
+ if field_val then
+ field:load(field_key, field_val)
+ else
+ texio.write_nl('Warning: Passed unknown field to object', field_key)
+ end
+ end
+ else
+ self.value = value
+ end
+ if self.type == 'bool' then
+ self:set_bool(key)
+ end
+end
diff --git a/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders.lua b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders.lua
new file mode 100644
index 0000000000..255b4d1f26
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/scripts/lua-placeholders.lua
@@ -0,0 +1,217 @@
+-- lua-placeholders.lua
+-- Copyright 2024 E. Nijenhuis
+--
+-- This work may be distributed and/or modified under the
+-- conditions of the LaTeX Project Public License, either version 1.3c
+-- of this license or (at your option) any later version.
+-- The latest version of this license is in
+-- http://www.latex-project.org/lppl.txt
+-- and version 1.3c or later is part of all distributions of LaTeX
+-- version 2005/12/01 or later.
+--
+-- This work has the LPPL maintenance status ‘maintained’.
+--
+-- The Current Maintainer of this work is E. Nijenhuis.
+--
+-- This work consists of the files lua-placeholders.sty
+-- lua-placeholders-manual.pdf lua-placeholders.lua
+-- lua-placeholders-common.lua lua-placeholders-namespace.lua
+-- lua-placeholders-parser.lua and lua-placeholders-types.lua
+
+if not modules then
+ modules = {}
+end
+
+modules.lua_placeholders = {
+ version = "0.1.0",
+ date = "2024/01/12",
+ comment = 'Extended LaTeX Parameter Interface — for specifying and inserting document parameters',
+ author = 'Erik Nijenhuis',
+ license = 'free'
+}
+
+local api = {
+ namespaces = {},
+ parameters = {},
+ strict = false,
+ toks = {
+ is_set_true = token.create('has@param@true'),
+ is_set_false = token.create('has@param@false'),
+ }
+}
+local lua_placeholders = {}
+local lua_placeholders_mt = {
+ __index = api,
+ __newindex = function()
+ tex.error('Cannot override or set actions for this module...')
+ end
+}
+
+setmetatable(lua_placeholders, lua_placeholders_mt)
+
+local lua_placeholders_namespace = require('lua-placeholders-namespace')
+local load_resource = require('lua-placeholders-parser')
+
+local function get_param(key, namespace)
+ namespace = namespace or tex.jobname
+ local _namespace = api.namespaces[namespace]
+ return _namespace and _namespace:param(key)
+end
+
+function api.set_strict()
+ api.strict = true
+end
+
+function api.recipe(path, namespace_name)
+ if namespace_name == '' then
+ namespace_name = nil
+ end
+ local filename, abs_path = lua_placeholders_namespace.parse_filename(path)
+ local raw_recipe = load_resource(abs_path)
+ local name = namespace_name or raw_recipe.namespace or filename
+ local namespace = api.namespaces[name] or lua_placeholders_namespace:new { recipe_file = abs_path, strict = api.strict }
+ if not api.namespaces[name] then
+ api.namespaces[name] = namespace
+ end
+ if raw_recipe.namespace then
+ namespace:load_recipe(raw_recipe.parameters)
+ else
+ namespace:load_recipe(raw_recipe)
+ end
+ tex.print('\\UseOneTimeHook{namespace/' .. name .. '}')
+ if namespace.payload_file and not namespace.payload_loaded then
+ local raw_payload = load_resource(namespace.payload_file)
+ if raw_payload.namespace then
+ namespace:load_payload(raw_payload.parameters)
+ else
+ namespace:load_payload(raw_payload)
+ end
+ tex.print('\\UseOneTimeHook{namespace/' .. name .. '/loaded}')
+ end
+end
+
+function api.payload(path, namespace_name)
+ if namespace_name == '' then
+ namespace_name = nil
+ end
+ local filename, abs_path = lua_placeholders_namespace.parse_filename(path)
+ local raw_payload = load_resource(abs_path)
+ local name = namespace_name or raw_payload.namespace or filename
+ local namespace = api.namespaces[name] or lua_placeholders_namespace:new { payload_file = abs_path, strict = api.strict }
+ if not api.namespaces[name] then
+ api.namespaces[name] = namespace
+ end
+ if namespace.recipe_loaded then
+ if raw_payload.namespace then
+ namespace:load_payload(raw_payload.parameters)
+ else
+ namespace:load_payload(raw_payload)
+ end
+ tex.print('\\UseOneTimeHook{namespace/' .. name .. '/loaded}')
+ end
+end
+
+function api.param_object(key, namespace)
+ return get_param(key, namespace)
+end
+
+function api.param(key, namespace)
+ local param = get_param(key, namespace)
+ if param then
+ param:print_val()
+ elseif api.strict then
+ tex.error('Error: Parameter not set "' .. key .. '" in namespace "' .. namespace .. '".')
+ else
+ tex.sprint(lua_placeholders_toks.unknown_format, '{', key, '}')
+ end
+end
+
+function api.handle_param_is_set(key, namespace)
+ local param = get_param(key, namespace)
+ if param and param:is_set() then
+ tex.sprint(token.create('has@param@true'))
+ else
+ tex.sprint(token.create('has@param@false'))
+ end
+end
+
+function api.field(object_key, field, namespace)
+ local param = get_param(object_key, namespace)
+ if param then
+ local object = param.fields or param.default or {}
+ local f = object[field]
+ if f then
+ f:print_val()
+ else
+ tex.sprint(lua_placeholders_toks.unknown_format, '{', field, '}')
+ end
+ else
+ tex.error('No such object', object_key)
+ end
+end
+
+function api.with_object(object_key, namespace)
+ local object = get_param(object_key, namespace)
+ for key, param in pairs(object.fields) do
+ local val = param:val()
+ if val then
+ token.set_macro(key, param:val() .. '\\xspace')
+ else
+ token.set_macro(key, '\\paramplaceholder{' .. (param.placeholder or key) .. '}\\xspace')
+ end
+ end
+end
+
+function api.for_item(list_key, namespace, csname)
+ local param = get_param(list_key, namespace)
+ local list = param:val()
+ if #list > 0 then
+ if token.is_defined(csname) then
+ local tok = token.create(csname)
+ for _, item in ipairs(list) do
+ if param.values then
+ tex.sprint(tok, '{', item:val(), '}')
+ else
+ tex.sprint(tok, '{', lua_placeholders_toks.placeholder_format, '{', item:val(), '}}')
+ end
+ end
+ else
+ tex.error('No such command ', csname or 'nil')
+ end
+ end
+end
+
+function api.with_rows(key, namespace, csname)
+ local param = get_param(key, namespace)
+ if token.is_defined(csname) then
+ local row_content = token.get_macro(csname)
+ if param then
+ if param.values or api.strict then
+ if #param.values > 0 then
+ for _, row in ipairs(param.values) do
+ local format = row_content
+ for col_key, cell in pairs(row) do
+ format = format:gsub('\\' .. col_key, cell:val())
+ end
+ tex.print(format)
+ end
+ end
+ elseif param.columns then
+ texio.write_nl("Warning: no values set for " .. param.key)
+ local format = row_content
+ for col_key, col in pairs(param.columns) do
+ format = format:gsub('\\' .. col_key, '{\\paramplaceholder{' .. (col.placeholder or col_key) .. '}}')
+ end
+ tex.print(format)
+ else
+ tex.error('No values either columns available')
+ end
+ else
+ tex.error('Error: no such parameter')
+ end
+ else
+ tex.error('Error: no such command: ', csname or 'nil')
+ end
+end
+
+return lua_placeholders
diff --git a/macros/luatex/latex/lua-placeholders/tex/lua-placeholders.sty b/macros/luatex/latex/lua-placeholders/tex/lua-placeholders.sty
new file mode 100644
index 0000000000..9a2c247962
--- /dev/null
+++ b/macros/luatex/latex/lua-placeholders/tex/lua-placeholders.sty
@@ -0,0 +1,55 @@
+%% lua-placeholders.sty
+%% Copyright 2024 E. Nijenhuis
+%
+% This work may be distributed and/or modified under the
+% conditions of the LaTeX Project Public License, either version 1.3c
+% of this license or (at your option) any later version.
+% The latest version of this license is in
+% http://www.latex-project.org/lppl.txt
+% and version 1.3c or later is part of all distributions of LaTeX
+% version 2005/12/01 or later.
+%
+% This work has the LPPL maintenance status ‘maintained’.
+%
+% The Current Maintainer of this work is E. Nijenhuis.
+%
+% This work consists of the files lua-placeholders.sty
+% lua-placeholders-manual.pdf lua-placeholders.lua
+% lua-placeholders-common.lua lua-placeholders-namespace.lua
+% lua-placeholders-parser.lua and lua-placeholders-types.lua
+
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{lua-placeholders}[2024/01/12 Extended LaTeX Paramter Interface Package]
+
+\RequirePackage{ifthen}
+\RequirePackage{luapackageloader}
+\RequirePackage{textcomp}
+\RequirePackage{xspace}
+
+\newcommand\curnamespace{\jobname}
+\newcommand\paramplaceholder[1]{\texttt{\textbf{[}#1\textbf{]}}}
+\newcommand\paramnotfound[1]{\paramplaceholder{{\textnormal{\textlangle unknown\textrangle}} #1}}
+\newcommand\paramlistconjunction{,~}
+
+\directlua{lua_placeholders = require('lua-placeholders')}
+
+\newcommand\setnamespace[1]{\renewcommand\curnamespace{#1}}
+\newcommand\strictparams{\directlua{lua_placeholders.set_strict()}}
+\newcommand%! suppress = NonMatchingIf
+\ifparam[4][\curnamespace]{%
+ \directlua{local p = lua_placeholders.param_object('#2','#1') if p then p:set_bool('#2') end}%
+ \ifthenelse{\boolean{#2}}{#3}{#4}}
+\newcommand\param[2][\curnamespace]{\directlua{lua_placeholders.param('#2', '#1')}}
+\newcommand\PARAM[2][\curnamespace]{\directlua{local p = lua_placeholders.param_object('#2', '#1') if p then tex.print(p:to_upper()) end}}
+\def\rawparam#1#2{\directlua{local p = lua_placeholders.param_object('#2', '#1') if p then tex.print(p:val() or p.default or p.placeholder) end}}
+\newcommand\numparam[2][\curnamespace]{\directlua{local n = lua_placeholders.param_object('#2', '#1') if n then n:print_num() end}}
+\newcommand\hasparam[4][\curnamespace]{%
+ \def\has@param@true{#3}%
+ \def\has@param@false{#4}%
+ \directlua{lua_placeholders.handle_param_is_set('#2', '#1')}}
+\newcommand\paramfield[3][\curnamespace]{\directlua{lua_placeholders.field('#2','#3', '#1')}}
+\newenvironment{paramobject}[2][\curnamespace]{\directlua{lua_placeholders.with_object('#2', '#1')}}{}
+\newcommand\forlistitem[3][\curnamespace]{\directlua{lua_placeholders.for_item('#2', '#1', '#3')}}
+\newcommand\fortablerow[3][\curnamespace]{\directlua{lua_placeholders.with_rows('#2', '#1', '#3')}}
+\newcommand\loadrecipe[2][]{\directlua{lua_placeholders.recipe('#2', '#1')}}
+\newcommand\loadpayload[2][]{\directlua{lua_placeholders.payload('#2', '#1')}}