summaryrefslogtreecommitdiff
path: root/macros/luatex
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2022-06-10 03:02:43 +0000
committerNorbert Preining <norbert@preining.info>2022-06-10 03:02:43 +0000
commitfb4909f4112d8b778570cf1c2e25ca18c63bc1ca (patch)
tree92ffbc5b652338ec52811278f6f700718b359c93 /macros/luatex
parent16559ce13a74c7a182875559b379cac00b07efda (diff)
CTAN sync 202206100302
Diffstat (limited to 'macros/luatex')
-rw-r--r--macros/luatex/generic/luakeys/README.md114
-rw-r--r--macros/luatex/generic/luakeys/luakeys-debug.sty2
-rw-r--r--macros/luatex/generic/luakeys/luakeys-debug.tex7
-rw-r--r--macros/luatex/generic/luakeys/luakeys-doc.pdfbin169502 -> 254353 bytes
-rw-r--r--macros/luatex/generic/luakeys/luakeys-doc.tex1328
-rw-r--r--macros/luatex/generic/luakeys/luakeys.lua1269
-rw-r--r--macros/luatex/generic/luakeys/luakeys.sty2
7 files changed, 2009 insertions, 713 deletions
diff --git a/macros/luatex/generic/luakeys/README.md b/macros/luatex/generic/luakeys/README.md
index 10793d353e..ca73100b64 100644
--- a/macros/luatex/generic/luakeys/README.md
+++ b/macros/luatex/generic/luakeys/README.md
@@ -30,6 +30,120 @@ This work has the LPPL maintenance status `maintained`.
The Current Maintainer of this work is Josef Friedrich.
+## Documentation
+
+### Key-value pair definitions
+
+```lua
+local defs = {
+ key = {
+ -- Allow different key names.
+ -- or a single string: alias = 'k'
+ alias = { 'k', 'ke' },
+
+ -- The key is always included in the result. If no default value is
+ -- definied, true is taken as the value.
+ always_present = false,
+
+ -- Only values listed in the array table are allowed.
+ choices = { 'one', 'two', 'three' },
+
+ -- Possible data types: boolean, dimension, integer, number, string
+ data_type = 'string',
+
+ default = true,
+
+ -- The key belongs to a mutually exclusive group of keys.
+ exclusive_group = 'name',
+
+ -- > \MacroName
+ macro = 'MacroName', -- > \MacroName
+
+ -- See http://www.lua.org/manual/5.3/manual.html#6.4.1
+ match = '^%d%d%d%d%-%d%d%-%d%d$',
+
+ -- The name of the key, can be omitted
+ name = 'key',
+ opposite_keys = { [true] = 'show', [false] = 'hide' },
+ process = function(value, input, result, unknown)
+ return value
+ end,
+ required = true,
+ sub_keys = { key_level_2 = { } },
+ }
+}
+```
+
+### Parser options (opts)
+
+```lua
+local opts = {
+ -- Automatically convert dimensions into scaled points (1cm -> 1864679).
+ convert_dimensions = false,
+
+ -- Print the result table to the console.
+ debug = false,
+
+ -- The default value for naked keys (keys without a value).
+ default = true,
+
+ -- A table with some default values. The result table is merged with
+ -- this table.
+ defaults = { key = 'value' },
+
+ -- Key-value pair definitions.
+ defs = { key = { default = 'value' } },
+
+ -- lower, snake, upper
+ format_keys = { 'snake' },
+
+ -- Listed in the order of execution
+ hooks = {
+ kv_string = function(kv_string)
+ return kv_string
+ end,
+
+ -- Visit all key-value pairs recursively.
+ keys_before_opts = function(key, value, depth, current, result)
+ return key, value
+ end,
+
+ -- Visit the result table.
+ result_before_opts = function(result)
+ end,
+
+ -- Visit all key-value pairs recursively.
+ keys_before_def = function(key, value, depth, current, result)
+ return key, value
+ end,
+
+ -- Visit the result table.
+ result_before_def = function(result)
+ end,
+
+ -- Visit all key-value pairs recursively.
+ keys = function(key, value, depth, current, result)
+ return key, value
+ end,
+
+ -- Visit the result table.
+ result = function(result)
+ end,
+ },
+
+ -- If true, naked keys are converted to values:
+ -- { one = true, two = true, three = true } -> { 'one', 'two', 'three' }
+ naked_as_value = false,
+
+ -- Throw no error if there are unknown keys.
+ no_error = false,
+
+ -- { key = { 'value' } } -> { key = 'value' }
+ unpack = false,
+}
+local result = luakeys.parse('one,two,three', opts)
+```
+
## Tasks
### Installing
diff --git a/macros/luatex/generic/luakeys/luakeys-debug.sty b/macros/luatex/generic/luakeys/luakeys-debug.sty
index 83ae86b08b..7fcb483741 100644
--- a/macros/luatex/generic/luakeys/luakeys-debug.sty
+++ b/macros/luatex/generic/luakeys/luakeys-debug.sty
@@ -17,6 +17,6 @@
% luakeys-debug.sty and luakeys-debug.tex.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{luakeys-debug}[2022/04/04 v0.5 Debug package for luakeys.]
+\ProvidesPackage{luakeys-debug}[2022/06/09 v0.6 Debug package for luakeys.]
\input luakeys-debug.tex
diff --git a/macros/luatex/generic/luakeys/luakeys-debug.tex b/macros/luatex/generic/luakeys/luakeys-debug.tex
index ffc449ac9b..e7758cce0a 100644
--- a/macros/luatex/generic/luakeys/luakeys-debug.tex
+++ b/macros/luatex/generic/luakeys/luakeys-debug.tex
@@ -37,7 +37,10 @@
}%
\def\luakeysdebug@parse@options#1{
\directlua{
- luakeys.save('debug_options', luakeys.parse('#1'))
+ luakeys.save(
+ 'debug_options',
+ luakeys.parse('#1', { format_keys = { 'snake', 'lower' } })
+ )
}
}%
\def\luakeysdebug@output#1{
@@ -47,7 +50,7 @@
\directlua{
local result = luakeys.parse('\luaescapestring{\unexpanded{#1}}', luakeys.get('debug_options'))
tex.print(luakeys.stringify(result, true))
- luakeys.print(result)
+ luakeys.debug(result)
}
}
}%
diff --git a/macros/luatex/generic/luakeys/luakeys-doc.pdf b/macros/luatex/generic/luakeys/luakeys-doc.pdf
index 07f14d206b..6fe42295c4 100644
--- a/macros/luatex/generic/luakeys/luakeys-doc.pdf
+++ b/macros/luatex/generic/luakeys/luakeys-doc.pdf
Binary files differ
diff --git a/macros/luatex/generic/luakeys/luakeys-doc.tex b/macros/luatex/generic/luakeys/luakeys-doc.tex
index 3f7d1b0ea1..bc9a55f632 100644
--- a/macros/luatex/generic/luakeys/luakeys-doc.tex
+++ b/macros/luatex/generic/luakeys/luakeys-doc.tex
@@ -1,6 +1,6 @@
\documentclass{ltxdoc}
-\usepackage{hyperref}
+\usepackage[hidelinks]{hyperref}
\EnableCrossrefs
\CodelineIndex
\RecordChanges
@@ -17,8 +17,24 @@
\setminted{
breaklines=true,
fontsize=\footnotesize,
+ style=manni,
}
\def\lua#1{\mintinline{lua}|#1|}
+\def\latex#1{\mintinline{latex}|#1|}
+
+\NewDocumentCommand { \InputLatexExample } { O{} m } {
+ \begin{mdframed}
+ \inputminted[linenos=false,#1]{latex}{examples/#2}
+ \end{mdframed}
+}
+
+\NewDocumentCommand { \InputLuaExample } { O{} m } {
+ \begin{mdframed}
+ \inputminted[linenos=false,#1]{lua}{examples/#2}
+ \end{mdframed}
+}
+
+\catcode`_=12
\begin{document}
@@ -30,19 +46,13 @@
\url{josef@friedrich.rocks}\\%
\href{https://github.com/Josef-Friedrich/luakeys}{github.com/Josef-Friedrich/luakeys}%
}
-\date{v0.5 from 2022/04/04}
+\date{v0.6 from 2022/06/09}
\maketitle
\vfill
-%level1={level2={level3={dim=1cm,bool=true,num=-0.001,str=lua}}}
-
-\begin{minted}{lua}
-local luakeys = require('luakeys')
-local kv = luakeys.parse('level1={level2={level3={dim=1cm,bool=true,num=-0.001,str=lua}}}')
-luakeys.print(kv)
-\end{minted}
+\InputLuaExample[firstline=4,lastline=7]{first-page.lua}
\noindent
Result:
@@ -52,12 +62,11 @@ Result:
{
['level1'] = {
['level2'] = {
- ['level3'] = {
- ['dim'] = 1864679,
- ['bool'] = true,
- ['num'] = -0.001
- ['str'] = 'lua',
- }
+ ['naked'] = true,
+ ['dim'] = 1864679,
+ ['bool'] = false,
+ ['num'] = -0.001,
+ ['str'] = 'lua,{}',
}
}
}
@@ -77,35 +86,47 @@ Result:
\section{Introduction}
\noindent
-|luakeys| is a Lua module that can parse key-value options like the
-\TeX{} packages \href{https://www.ctan.org/pkg/keyval}{keyval},
+% |luakeys| ist ein Lua-Modul / Lua\TeX package, das wie die Pakete ...
+% Schlüssel-Wert-Optionen analysieren kann.
+|luakeys| is a Lua module / Lua\TeX package that can parse key-value
+options like the \TeX{} packages
+\href{https://www.ctan.org/pkg/keyval}{keyval},
\href{https://www.ctan.org/pkg/kvsetkeys}{kvsetkeys},
\href{https://www.ctan.org/pkg/kvoptions}{kvoptions},
\href{https://www.ctan.org/pkg/xkeyval}{xkeyval},
-\href{https://www.ctan.org/pkg/pgfkeys}{pgfkeys} etc. do. |luakeys|,
-however, accompilshes this task entirely, by using the Lua language and
-doesn’t rely on \TeX{}. Therefore this package can only be used with the
-\TeX{} engine Lua\TeX{}. Since |luakeys| uses
+\href{https://www.ctan.org/pkg/pgfkeys}{pgfkeys} etc.
+%
+% |luakeys|, erfüllt diese Aufgabe jedoch, indem es die Sprache Lua
+% verwendet und nicht auf \TeX{} angewiesen ist.
+|luakeys|, however, accomplishes this task by using the Lua language and
+doesn’t rely on \TeX{}.
+%
+% Deshalb kann dieses Paket nur mit der \TeX{}-Engine Lua\TeX{}
+% verwendet werden.
+Therefore this package can only be used with the
+\TeX{} engine Lua\TeX{}.
+%
+% Da |luakeys| \href{http://www.inf.puc-rio.br/~roberto/lpeg/}{LPeg}
+% verwendet, sollte der Parsing-Mechanismus ziemlich robust sein.
+Since |luakeys| uses
\href{http://www.inf.puc-rio.br/~roberto/lpeg/}{LPeg}, the parsing
mechanism should be pretty robust.
The TUGboat article
\href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf}
{“Implementing key–value input: An introduction” (Volume 30 (2009), No. 1)}
-by Joseph Wright and Christian Feuersänger gives a good overview of the
-available key-value packages.
+by \emph{Joseph Wright} and \emph{Christian Feuersänger} gives a good
+overview of the available key-value packages.
This package would not be possible without the article
\href{https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf}
-{Parsing complex data formats in LuaTEX with LPEG (Volume 40 (2019), No. 2)}.
+{“Parsing complex data formats in LuaTEX with LPEG” (Volume 40 (2019), No. 2)}.
%-----------------------------------------------------------------------
%
%-----------------------------------------------------------------------
-\clearpage
-
-\section{Usage}
+\section{How the package is loaded}
%%
%
@@ -113,28 +134,15 @@ This package would not be possible without the article
\subsection{Using the Lua module \texttt{luakeys.lua}}
-The core functionality of this package is realized in Lua. So you can
-use \texttt{luakeys} without using the wrapper \TeX{} files
-\texttt{luakeys.sty} and \texttt{luakeys.tex}.
+% Die Kernfunktionalität dieses Pakets ist in Lua realisiert.
+The core functionality of this package is realized in Lua.
+%
+% Sie können also |luakeys| auch ohne die Wrapper-Dateien
+% \texttt{luakeys.sty} und \texttt{luakeys.tex} verwenden.
+So you can use |luakeys| even without using the wrapper files
+|luakeys.sty| and |luakeys.tex|.
-\begin{minted}{latex}
-\documentclass{article}
-\directlua{
- luakeys = require('luakeys')
-}
-
-\newcommand{\helloworld}[2][]{
- \directlua{
- local keys = luakeys.parse('\luaescapestring{\unexpanded{#1}}')
- luakeys.print(keys)
- local marg = '#2'
- tex.print(keys.greeting .. ', ' .. marg .. keys.punctuation)
- }
-}
-\begin{document}
-\helloworld[greeting=hello,punctuation=!]{world}
-\end{document}
-\end{minted}
+\InputLatexExample{loading/lua.tex}
%%
%
@@ -142,6 +150,22 @@ use \texttt{luakeys} without using the wrapper \TeX{} files
\subsection{Using the Lua\LaTeX{} wrapper \texttt{luakeys.sty}}
+% Der Paketmanager MikTeX lädt beispielsweise Pakete erst bei Bedarf
+% herunter.
+For example, the MiK\TeX{} package manager downloads packages only when
+needed.
+%
+% Es wurde berichtet, dass dieser automatische Download nur mit Hilfe der
+% Hüll-Dateien funktioniert.
+It has been reported that this automatic download only works with this
+wrapper files.
+%
+% Wahrscheinlich hält MiK\TeX nach einem Auftreten des LaTeX macros
+% “\latex{\usepackage{luakeys}} ”Ausschau.
+Probably MiK\TeX{} is searching for an occurrence of the \LaTeX{} macro
+“\latex{\usepackage{luakeys}}”.
+
+% Die mitgelieferte Lua\LaTeX{}-Datei ist recht klein:
The supplied Lua\LaTeX{} file is quite small:
\begin{minted}{latex}
@@ -153,19 +177,7 @@ The supplied Lua\LaTeX{} file is quite small:
\noindent
It loads the Lua module into the global variable \texttt{luakeys}.
-\begin{minted}{latex}
-\documentclass{article}
-\usepackage{luakeys}
-
-\begin{document}
- \directlua{
- local keys = luakeys.parse('one,two,three')
- tex.print(keys[1])
- tex.print(keys[2])
- tex.print(keys[3])
- }
-\end{document}
-\end{minted}
+\InputLatexExample{loading/latex.tex}
%%
%
@@ -184,31 +196,896 @@ line:
It does the same as the Lua\LaTeX{} wrapper and loads the Lua module
\texttt{luakeys.lua} into the global variable \texttt{luakeys}.
+\InputLatexExample{loading/tex.tex}
+
+\section{Lua interface / API}
+
+To learn more about the individual functions (local functions), please
+read the \href{https://josef-friedrich.github.io/luakeys/}{source code
+documentation}, which was created with
+\href{http://stevedonovan.github.io/ldoc/}{LDoc}. The Lua module exports
+this functions and tables:
+
+\InputLuaExample[firstline=3,lastline=12]{export.lua}
+
+\subsection{Lua indentifier names}
+
+% Das Projekt verwendet einige wenige Abkürzungen für Variablennamen,
+% die hoffentlich für externe Leser eindeutig und bekannt sind.
+The project uses a few abbreviations for variable names that are
+hopefully unambiguous and familiar to external readers.
+
+\begin{center}
+\begin{tabular}{lll}
+Abbreviation & spelled out & Example \\\hline
+\lua{kv_string} & Key-value string & \lua{'key=value'} \\
+\lua{opts} & Options (for the parse function) & \lua{ { no_error = false } } \\
+\lua{defs} & Definitions \\
+\lua{def} & Definition \\
+\lua{attr} & Attributes (of a definition) \\
+\end{tabular}
+\end{center}
+
+\noindent
+% Diese nicht abgekürzten Variablennamen werden häufig verwendet.
+These unabbreviated variable names are commonly used.
+\begin{center}
+\begin{tabular}{ll}
+
+\lua{result} &
+% Das Endergebnis aller einzelnen Übersetzungs- und Normalisierungsschritte
+The final result of all individual parsing and normalization steps. \\
+
+\lua{unknown} &
+% Ein Tabelle mit unbekannten, nicht definierten Schlüssel-Wert-Paaren
+A table with unknown, undefinied key-value pairs. \\
+
+\lua{raw} &
+% Das unbearbeitete, rohe Ergebnis der LPeg-Syntaxanalyse.
+The raw result of the Lpeg grammar parser. \\
+\end{tabular}
+\end{center}
+
+%%
+%
+%%
+
+\subsection{Function \texttt{parse(kv_string, opts): result, unknown, raw}}
+\label{parse}
+
+% Die Function parse ist die wichtigste Funktion des Pakets.
+The function \lua{parse(kv_string, opts)} is the most important function
+of the package.
+% Sie konvertiert eine Schlüssel-Wert-Zeichenkette in eine Lua Tabelle.
+It converts a key-value string into a Lua table.
+
\begin{minted}{latex}
-\input luakeys.tex
+\newcommand{\mykeyvalcmd}[2][]{
+ \directlua{
+ result = luakeys.parse('#1')
+ luakeys.debug(result)
+ }
+ #2
+}
+\mykeyvalcmd[one=1]{test}
+\end{minted}
+\noindent
+In plain \TeX:
+
+\begin{minted}{latex}
+\def\mykeyvalcommand#1{
+ \directlua{
+ result = luakeys.parse('#1')
+ luakeys.debug(result)
+ }
+}
+\mykeyvalcmd{one=1}
+\end{minted}
+
+\subsection{Options to configure the \texttt{parse} function}
+
+\noindent
+The \lua{parse} function can be called with an options table. This
+options are supported:
+
+\InputLuaExample[firstline=5,lastline=69]{opts/all-opts.lua}
+
+\noindent
+The options can also be set globally using the exported table
+|opts|:
+
+\InputLuaExample[firstline=4,lastline=4]{opts/exported-default-opts.lua}
+
+\InputLuaExample[firstline=10,lastline=11]{opts/exported-default-opts.lua}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{convert_dimensions}”}
+
+If you set the option \lua{convert_dimensions} to \lua{true}, |luakeys|
+detects the \TeX{} dimensions and converts them into scaled points using
+the function \lua{tex.sp(dim)}.
+
+\InputLuaExample[firstline=4,lastline=7]{opts/convert-dimensions.lua}
+
+\noindent
+By default the dimensions are not converted into scaled points.
+
+\InputLuaExample[firstline=13,lastline=18]{opts/convert-dimensions.lua}
+
+\noindent
+If you want to convert a scale point into a unit string you can use the module
+\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua}{lualibs-util-dim.lua}.
+
+\begin{minted}{lua}
+require('lualibs')
+tex.print(number.todimen(tex.sp('1cm'), 'cm', '%0.0F%s'))
+\end{minted}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{debug}”}
+
+If the option “debug” is set to ture, the result table is printed to the
+console.
+
+\begin{minted}{latex}
+\documentclass{article}
+\usepackage{luakeys}
+\begin{document}
\directlua{
- local keys = luakeys.parse('one,two,three')
- tex.print(keys[1])
- tex.print(keys[2])
- tex.print(keys[3])
+ luakeys.parse('one,two,three', { debug = true })
}
-\bye
+debug
+\end{document}
\end{minted}
+\begin{verbatim}
+This is LuaHBTeX, Version 1.15.0 (TeX Live 2022)
+...
+(./debug.aux) (/usr/local/texlive/texmf-dist/tex/latex/base/ts1cmr.fd)
+{
+ ['three'] = true,
+ ['two'] = true,
+ ['one'] = true,
+}
+ [1{/usr/
+local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./debug.aux)
+)
+...
+Transcript written on debug.log.
+\end{verbatim}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{default}”}
+\label{option-default}
+
+% Mit der Option \lua{default} kann angegeben werden, welchen Wert
+% nackte Schlüssel (Schlüssel ohne Wert) erhalten. Diese Option hat
+% keinen Einfluss auf Schlüssel mit Werten.
+The option \lua{default} can be used to specify which value naked keys
+(keys without a value) get. This option has no influence on keys with
+values.
+
+\InputLuaExample[firstline=4,lastline=5]{opts/default.lua}
+
+\noindent
+% Standardmäßig erhalten nackte Schlüssel den Wert \lua{true}.
+By default, naked keys get the value \lua{true}.
+
+\InputLuaExample[firstline=11,lastline=12]{opts/default.lua}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{defaults}”}
+\label{options-defaults}
+
+% Mit der Attribut „defaults“ kann nicht nur ein einiger Standardwert
+% angegeben werden, sondern eine ganze Tabelle mit Standardwerten.
+The option “defaults” can be used to specify not only one default value,
+but a whole table of default values.
+% Die Ergebnistabelle wird mit der Tabelle bestehend aus Standardwerten
+% vereinigt.
+The result table is merged into the defaults table.
+% Werte aus der Tabelle mit Standardwerten werden von Werten der
+% Ergebnistabelle überschrieben.
+Values in the defaults table are
+overwritten by values in the result table.
+
+\InputLuaExample[firstline=4,lastline=7]{opts/defaults.lua}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{defs}”}
+
+% Für mehr Information wie Schlüssel definiert werden, lesen sie das
+% kapitel 3.2
+For more informations on how keys are defined, see section \ref{define}.
+% Wenn sie die Option \lua{defs} verwenden, können sie auf den
+% Aufruf der Funktion \lua{define} verzichten.
+If you use the \lua{defs} option, you don't need to call the
+\lua{define} function.
+%
+% Anstatt
+Instead of ...
+
+\InputLuaExample[firstline=4,lastline=5]{opts/defs.lua}
+
+\noindent
+% können wir schreiben ..
+we can write ...
+
+\InputLuaExample[firstline=11,lastline=13]{opts/defs.lua}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{format_keys}”}
+
+\begin{description}
+\item[lower] \strut
+
+\InputLuaExample[firstline=4,lastline=5]{opts/format-keys.lua}
+
+\item[snake] \strut
+
+\InputLuaExample[firstline=11,lastline=12]{opts/format-keys.lua}
+
+\item[upper] \strut
+
+\InputLuaExample[firstline=18,lastline=19]{opts/format-keys.lua}
+\end{description}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{hooks}”}
+
+% Die folgenden Hooks bzw. Callback-Funktionen ermöglichen es in den
+% Verarbeitungsprozess der \lua{parse}-Function einzugreifen.
+The following hooks or callback functions allow to intervene in the
+processing of the \lua{parse} function.
+%
+% Die Funktionen sind in der Verarbeitungsreihenfolge aufgelistet.
+The functions are listed in processing order.
+%
+% \lua{*_before_opts} bedeutet, dass die Hooks nach der LPeg Syntaxanalyse
+% und vor dem Anwenden der Optionen ausgeführt
+\lua{*_before_opts} means that the hooks are executed after the LPeg
+syntax analysis and before the options are applied.
+%
+% Die Hooks \lua{*_before_defs} werden vor dem Anwenden der
+% Schlüssel-Wert-Definitionen ausgeführt
+The \lua{*_before_defs} hooks are executed before applying the key value
+definitions.
+
+\def\TmpSignature#1{
+ {
+ \scriptsize\texttt{ = #1}
+ }
+}
+
+\def\TmpKeySignature{
+ \TmpSignature{function(key, value, depth, current, result): key, value}
+}
+\def\TmpResultSignature{
+ \TmpSignature{function(result): void}
+}
+
+\begin{enumerate}
+\item \lua{kv_string} \TmpSignature{function(kv_string): kv_string}
+\item \lua{keys_before_opts} \TmpKeySignature
+\item \lua{result_before_opts} \TmpResultSignature
+\item \lua{keys_before_def} \TmpKeySignature
+\item \lua{result_before_def} \TmpResultSignature
+\item (\lua{process}) (has to be definied using defs, see \ref{attr-process})
+\item \lua{keys} \TmpKeySignature
+\item \lua{result} \TmpResultSignature
+\end{enumerate}
+
+\paragraph{\texttt{kv_string}}
+
+% Der Hook \lua{kv_string} wird als erste
+% der Hook-Funktionen noch vor der LPeg-Syntax-Analyse aufgerufen.
+The \lua{kv_string} hook is called as the first of the hook functions
+before the LPeg syntax parser is executed.
+
+\InputLuaExample[firstline=4,lastline=11]{hooks/kv-string.lua}
+
+\paragraph{\texttt{keys_*}}
+
+% Die Hooks \lua{keys_*} werden rekursiv auf jeden Schlüssel in der
+% aktuellen Ergebnistabelle aufgerufen.
+The hooks \lua{keys_*} are called recursively on each key in the current
+result table.
+% Die Hook-Funktion muss zwei Werte zurückgeben: \lua{key, value}
+The hook function must return two values: \lua{key, value}.
+%
+% Das folgende Beispiel gibt \lua{key} und \lua{value} unverändert zurück,
+% sodass die Ergebnistabelle nicht verändert wird.
+The following example returns \lua{key} and \lua{value} unchanged, so
+the result table is not changed.
+
+\InputLuaExample[firstline=4,lastline=11]{hooks/keys-unchanged.lua}
+
+\noindent
+% Das nächste Beispiel demonstriert den dritten Parameter \lua{depth}
+% der Hook-Funktion.
+The next example demonstrates the third parameter \lua{depth} of the
+hook function.
+
+\InputLuaExample[firstline=4,lastline=16]{hooks/keys-depth.lua}
+
+\paragraph{\texttt{result_*}}
+
+% Die Hooks \lua{result_*} werden einmal mit der aktuellen
+% Ergebnistabelle als Parameter aufgerufen.
+The hooks \lua{result_*} are called once with the current result table
+as a parameter.
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{naked_as_value}”}
+
+% Mit Hilfe der Option \lua{naked_as_value} werden nackte Schlüssel
+% nicht mit einem Standardwert versehen, sondern als Werte in die
+% Lua-Tabelle abgelegt.
+With the help of the option \lua{naked_as_value}, naked keys are not
+given a default value, but are stored as values in a Lua table.
+
+\InputLuaExample[firstline=4,lastline=5]{opts/naked-as-value.lua}
+
+\noindent
+If we set the option \lua{naked_as_value} to \lua{true}:
+
+\InputLuaExample[firstline=11,lastline=14]{opts/naked-as-value.lua}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{no_error}”}
+
+% Standardmaßig wirft parse-Funktion einen Fehler, wenn es unbekannte
+% Schlüssel gibt.
+By default the parse function throws an error if there are unknown keys.
+% Mit Hilfe der Option \lua{no_error} kann dies unterbunden werden.
+This can be prevented with the help of the \lua{no_error} option.
+
+\InputLuaExample[firstline=5,lastline=6]{opts/no-error.lua}
+
+\noindent
+If we set the option \lua{no_error} to \lua{true}:
+
+\InputLuaExample[firstline=9,lastline=10]{opts/no-error.lua}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{unpack}”}
+
+% Mit Hilfe der Option \lua{unpack} werden alle Tabellen, die nur aus
+% einem einzigen nackten Schlüssel bzw. einen einzigen alleinstehenden
+% Wert bestehen, aufgelöst.
+With the help of the option \lua{unpack}, all tables that consist of
+only one a single naked key or a single standalone value are unpacked.
+
+\InputLuaExample[firstline=4,lastline=5]{opts/unpack.lua}
+
+\InputLuaExample[firstline=11,lastline=12]{opts/unpack.lua}
+
+%%
+%
+%%
+
+\subsection{Function \texttt{define(defs, opts): parse}}
+\label{define}
+
+The \lua{define} function returns a \lua{parse} function (see
+\ref{parse}).
+The name of a key can be specified in three ways:
+
+\begin{enumerate}
+\item as a string.
+\item as a key in a Lua table. The definition of the corresponding
+key-value pair is then stored under this key.
+\item by the “name” attribute.
+\end{enumerate}
+
+\InputLuaExample[firstline=4,lastline=16]{functions/define.lua}
+
+\noindent
+For nested definitions, only the last two ways of specifying the key
+names can be used.
+
+\InputLuaExample[firstline=26,lastline=33]{functions/define.lua}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\subsection{Attributes to define a key-value pair}
+
+% Die Definition eines Schlüssel-Wert-Paares kann mit Hilfe von
+% verschiedenen Attributen vorgenommen werden.
+The definition of a key-value pair can be made with the help of various
+attributes.
+%
+% Der Name „Attribut“ für eine Option, einen Schlüssel, eine Eigenschaft
+% (um nur einige Benennungsmöglichkeiten aufzuzählen) zur
+% Schlüssel-Definition, wurde bewusst gewählt, um sie von den Optionen
+% der Funktion \lua{parse} zu unterscheiden.
+The name \emph{“attribute”} for an option, a key, a property ... (to
+list just a few naming possibilities) to define keys, was deliberately
+chosen to distinguish them from the options of the \lua{parse} function.
+%
+% Das folgende Codebeispiel listet alle Attribute auf, die verwendet
+% werden können, um Schlüssel-Wert-Paare zu definieren.
+The code example below lists all the attributes that can be used to
+define key-value pairs.
+
+\InputLuaExample[firstline=5,lastline=41]{defs/all-attrs.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{alias}”}
+
+With the help of the \lua{alias} attribute, other key names can be used.
+The value is always stored under the original key name. A single alias
+name can be specified by a string ...
+
+\InputLuaExample[firstline=4,lastline=7]{defs/attrs/alias.lua}
+
+\noindent
+multiple aliases by a list of strings.
+
+\InputLuaExample[firstline=13,lastline=16]{defs/attrs/alias.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{always_present}”}
+
+% Die Option \lua{default} wird nur bei nackten Schlüsseln verwendet.
+The \lua{default} attribute is used only for naked keys.
+
+\InputLuaExample[firstline=4,lastline=5]{defs/attrs/always-present.lua}
+
+\noindent
+% Wird die Option \lua{always_present} auf wahr gesetzt, wird der
+% Schlüssel immer ins Ergebnis mit übernommen.
+If the attribute \lua{always_present} is set to true, the key is always
+included in the result. If no default value is definied, true is taken
+as the value.
+
+\InputLuaExample[firstline=11,lastline=12]{defs/attrs/always-present.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{choices}”}
+
+% source: Python argparse documentation.
+Some key values should be selected from a restricted set of choices.
+These can be handled by passing an array table containing choices.
+
+\InputLuaExample[firstline=4,lastline=5]{defs/attrs/choices.lua}
+
+\noindent
+When the key-value pair is parsed, values will be checked, and an error
+message will be displayed if the value was not one of the acceptable
+choices:
+
+\InputLuaExample[firstline=13,lastline=15]{defs/attrs/choices.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{data_type}”}
+
+% source: Python argparse documentation.
+The \lua{data_type} attribute allows type-checking and type conversions to
+be performed.
+%
+% Folgende Datentypen werden unterstützt:
+The following data types are supported:
+\lua{'boolean'},
+\lua{'dimension'},
+\lua{'integer'},
+\lua{'number'},
+\lua{'string'}.
+%
+% Bei den drei Datentypen integer, number, dimension kann eine
+% Typenumwandlung scheitern.
+A type conversion can fail with the three data types
+\lua{'dimension'},
+\lua{'integer'},
+\lua{'number'}.
+%
+% Dann wird eine Fehlermeldung ausgegeben.
+Then an error message is displayed.
+
+\InputLuaExample[firstline=4,lastline=8]{defs/attrs/data-type.lua}
+\InputLuaExample[firstline=11,lastline=15]{defs/attrs/data-type.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{default}”}
+
+% Verwenden Sie das Attribut „\lua{default}“, um für jeden nackten Schlüssel
+% einzeln einen Standardwert bereit zu stellen.
+Use the \lua{default} attribute to provide a default value for each naked
+key individually.
+%
+% Mit der globalen \lua{default} Option kann für alle nackten Schlüssel ein
+% Standardwert vorgegeben werden.
+With the global \lua{default} attribute (\ref{option-default}) a default
+value can be specified for all naked keys.
+
+\InputLuaExample[firstline=4,lastline=9]{defs/attrs/default.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{exclusive_group}”}
+
+% Alle Schlüssel, die der gleichen ausschließenden Gruppe angehören,
+% dürfen nicht gemeinsam angegeben werden.
+All keys belonging to the same exclusive group must not be specified
+together.
+%
+% Nur ein Schlüssel aus dieser Gruppe ist erlaubt.
+Only one key from this group is allowed.
+%
+% Als Name für diese ausschließende Gruppe kann irgend ein beliebiger
+% Wert verwendet werden.
+Any value can be used as a name for this exclusive group.
+
+\InputLuaExample[firstline=4,lastline=9]{defs/attrs/exclusive-group.lua}
+
+% Werden mehrer Schlüssel der Gruppe angegeben, so wird eine
+% Fehlermeldung geworfen.
+If more than one key of the group is specified, an error message is
+thrown.
+
+\InputLuaExample[firstline=21,lastline=23]{defs/attrs/exclusive-group.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{opposite_keys}”}
+
+% Die Option \lua{opposite_keys} ermöglicht es, gegensätzliche (nackte)
+% Schlüssel in Wahrheitswerte umzuwandeln und diesen Wahrheitswert unter
+% einem Zielschlüssel zu speichern.
+The \lua{opposite_keys} attribute allows to convert opposite (naked) keys
+into a boolean value and store this boolean under a target key.
+%
+% Lua erlaubt es in Tabellen Wahrheitswerte als Schlüssel zu verwenden.
+% Es müssen jedoch eckige Klammern verwendet werden.
+Lua allows boolean values to be used as keys in tables.
+%
+% Die Wahrheitswerte müssen jedoch in eckige Klammern geschrieben werden.
+However, the boolean values must be written in square brackets, e. g.
+\lua{{ opposite_keys = { [true] = 'show', [false] = 'hide' } }}.
+%
+% Beispiele für gegensätzliche Schlüssel sind:
+Examples of opposing keys are: \lua{show} and \lua{hide}, \lua{dark} and
+\lua{light}, \lua{question} and \lua{solution}.
+%
+% Das untenstehende Beispiel verwendet als gegensätzliches Schlüsselpaar
+% die Schlüssel \lua{show} und \lua{hide}.
+The example below uses the \lua{show} and \lua{hide} keys as the
+opposite key pair.
+%
+% Wird der Schlüssel \lua{show} von der Funktion \lua{parse} gelesen,
+% dann erhält der Zielschlüssel \lua{visibility} den Wert \lua{true}.
+If the key \lua{show} is parsed by the \lua{parse} function, then the
+target key \lua{visibility} receives the value \lua{true}.
+
+\InputLuaExample[firstline=4,lastline=7]{defs/attrs/opposite-keys.lua}
+
+% Wird der Schlüssel \lua{hide} gelesen, dann \lua{falsch}.
+\noindent
+If the key \lua{hide} is parsed, then \lua{false}.
+
+\InputLuaExample[firstline=13,lastline=13]{defs/attrs/opposite-keys.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{macro}”}
+
+The attribute \texttt{macro} stores the value in a \TeX{} macro.
+
+\begin{minted}{lua}
+local parse = luakeys.define({
+ key = {
+ macro = 'MyMacro'
+ }
+})
+parse('key=value')
+\end{minted}
+
+\begin{minted}{latex}
+\MyMacro % expands to “value”
+\end{minted}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{match}”}
+
+% Der Wert des Schlüssel wird der Lua Funktion übergeben
+The value of the key is passed to the Lua function
+\lua{string.match(value, match)} ((\url{http://www.lua.org/manual/5.3/manual.html#pdf-string.match})).
+% Werfe einen Blick in das Lua-Handbuch, wie man Patterns schreibt.
+Take a look at the Lua manual on how to write patterns
+(\url{http://www.lua.org/manual/5.3/manual.html#6.4.1})
+
+\InputLuaExample[firstline=4,lastline=6]{defs/attrs/match.lua}
+
+\noindent
+% Kann das Pattern im Wert nicht gefunden werden, wird eine
+% Fehlermeldung ausgegeben.
+If the pattern cannot be found in the value, an error message is issued.
+
+\InputLuaExample[firstline=14,lastline=17]{defs/attrs/match.lua}
+
+\noindent
+% Der Schlüssel erhält das Ergebnis der Funktion \lua{string.match(value,
+% match)}, dass bedeutet, dass der ursprüngliche Wert unter
+% Umständen nicht vollständig in den Schlüssel gespeichert wird.
+The key receives the result of the function \lua{string.match(value,
+match)}, which means that the original value may not be stored
+completely in the key.
+
+\InputLuaExample[firstline=22,lastline=23]{defs/attrs/match.lua}
+
+\noindent
+% Das Präfix “waste ” und das Suffix “ rubbisch” der Zeichenketten wird
+% verworfen.
+The prefix “waste ” and the suffix “ rubbisch” of the string are
+discarded.
+
+\InputLuaExample[firstline=29,lastline=29]{defs/attrs/match.lua}
+
+\noindent
+% Da Funktion \lua{string.match(value, match)} immer eine Zeichenkette
+% zurückgibt, ist der Wert des Schlüssel auch immer eine Zeichenkette.
+Since function \lua{string.match(value, match)} always returns a string,
+the value of the key is also always a string.
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{name}”}
+
+% Die Option \lua{name} ermöglicht eine alternative Notation von
+% Schlüsselnamen.
+The \lua{name} attribute allows an alternative notation of key names.
+%
+% Anstatt ...
+Instead of ...
+
+\InputLuaExample[firstline=4,lastline=5]{defs/attrs/name.lua}
+
+\noindent
+% ... können wir schreiben:
+... we can write:
+
+\InputLuaExample[firstline=11,lastline=15]{defs/attrs/name.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{process}”}
+\label{attr-process}
+
+The \lua{process} attribute can be used to define a function whose return
+value is passed to the key. Four parameters are passed when the
+function is called:
+
+\begin{enumerate}
+\item \lua{value}:
+% Der zum schlüssel gehörende aktuelle Wert.
+The current value asssociated with the key.
+
+\item \lua{input}:
+% Die Ergebnis-Tabelle, die vor dem Zeitpunkt geklont wurde, als mit dem
+% Anwenden der Definitionen begonnen wurde.
+The result table cloned before the time the definitions started to be applied.
+
+\item \lua{result}: The table in which the final result will be saved.
+
+\item \lua{unknown}: The table in which the unknown key-value pairs
+are stored.
+\end{enumerate}
+
+% Das folgende Beispiel demonstriert den Parameter \lua{value}.
+\noindent
+The following example demonstrates the \lua{value} parameter:
+\InputLuaExample[firstline=4,lastline=14]{defs/attrs/process.lua}
+
+\noindent
+The following example demonstrates the \lua{input} parameter:
+
+\InputLuaExample[firstline=22,lastline=34]{defs/attrs/process.lua}
+
+\noindent
+The following example demonstrates the \lua{result} parameter:
+
+\InputLuaExample[firstline=42,lastline=50]{defs/attrs/process.lua}
+
+\noindent
+The following example demonstrates the \lua{unknown} parameter:
+
+\InputLuaExample[firstline=58,lastline=65]{defs/attrs/process.lua}
+\InputLuaExample[firstline=69,lastline=69]{defs/attrs/process.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{required}”}
+
+\InputLuaExample[firstline=4,lastline=5]{defs/attrs/required.lua}
+
+\InputLuaExample[firstline=13,lastline=14]{defs/attrs/required.lua}
+
+\noindent
+A recursive example:
+
+\InputLuaExample[firstline=18,lastline=23]{defs/attrs/required.lua}
+\InputLuaExample[firstline=29,lastline=30]{defs/attrs/required.lua}
+\InputLuaExample[firstline=38,lastline=39]{defs/attrs/required.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{sub_keys}”}
+
+% Mit dem Attribut \lua{sub_keys} können ineinander verschachtelte
+% Schlüssel-Wert-Paar-Definitionen aufgebaut werden.
+The \lua{sub_keys} attribute can be used to build nested key-value pair
+definitions.
+
+\InputLuaExample[firstline=4,lastline=16]{defs/attrs/sub-keys.lua}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\subsection{Function \texttt{render(result): string}}
+
+The function \lua{render(result)} reverses the function
+\lua{parse(kv_string)}. It takes a Lua table and converts this table
+into a key-value string. The resulting string usually has a different
+order as the input table.
+
+\InputLuaExample[firstline=4,lastline=10]{functions/render.lua}
+
+\noindent
+In Lua only tables with 1-based consecutive integer keys (a.k.a. array
+tables) can be parsed in order.
+
+\InputLuaExample[firstline=16,lastline=17]{functions/render.lua}
+
+%%
+%
+%%
+
+\subsection{Function \texttt{debug(result): void}}
+
+The function \lua{debug(result)} pretty prints a Lua table to standard
+output (stdout). It is a utility function that can be used to debug and
+inspect the resulting Lua table of the function \lua{parse}. You have to
+compile your \TeX{} document in a console to see the terminal output.
+
+\InputLuaExample[firstline=4,lastline=5]{functions/debug.lua}
+
+\noindent
+The output should look like this:
+
+\begin{minted}{md}
+{
+ ['level1'] = {
+ ['level2'] = {
+ ['key'] = 'value',
+ },
+ }
+}
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{Function \texttt{save(identifier, result): void}}
+
+The function \lua{save(identifier, result)} saves a result (a
+table from a previous run of \lua{parse}) under an identifier.
+Therefore, it is not necessary to pollute the global namespace to
+store results for the later usage.
+
+%%
+%
+%%
+
+\subsection{Function \texttt{get(identifier): result}}
+
+The function \lua{get(identifier)} retrieves a saved result from the
+result store.
+
+%%
+%
+%%
+
+\subsection{Table \texttt{is}}
+
+\subsubsection{Function \texttt{is.boolean(value): boolean}}
+\InputLuaExample[firstline=7,lastline=23]{is-table.lua}
+
+\subsubsection{Function \texttt{is.dimension(value): boolean}}
+\InputLuaExample[firstline=27,lastline=37]{is-table.lua}
+
+\subsubsection{Function \texttt{is.integer(value): boolean}}
+\InputLuaExample[firstline=41,lastline=46]{is-table.lua}
+
+\subsubsection{Function \texttt{is.number(value): boolean}}
+\InputLuaExample[firstline=50,lastline=57]{is-table.lua}
+
+\subsubsection{Function \texttt{is.string(value): boolean}}
+\InputLuaExample[firstline=61,lastline=67]{is-table.lua}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
\section{Syntax of the recognized key-value format}
%%
%
%%
-\subsection{A attempt to put the syntax into words}
+\subsection{An attempt to put the syntax into words}
A key-value pair is definied by an equal sign (\texttt{key=value}).
-Several key-value pairs or values without keys are lined up with commas
-(\texttt{key=value,value}) and build a key-value list. Curly brackets
-can be used to create a recursive data structure of nested key-value
-lists (\texttt{level1=\{level2=\{key=value,value\}\}}).
+Several key-value pairs or keys without values (naked keys) are lined up
+with commas (\texttt{key=value,naked}) and build a key-value list. Curly
+brackets can be used to create a recursive data structure of nested
+key-value lists (\texttt{level1=\{level2=\{key=value,naked\}\}}).
%%
%
@@ -231,9 +1108,17 @@ lists (\texttt{level1=\{level2=\{key=value,value\}\}}).
\alt <string-quoted>
\alt <string-unquoted>
+<dimension> ::= <number> <unit>
+
+<number> ::= <sign> ( <integer> [ <fractional> ] | <fractional> )
+
+<fractional> ::= `.' <integer>
+
<sign> ::= `-' | `+'
-<integer> ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9'
+<integer> ::= <digit> \{ <digit> \}
+
+<digit> ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9'
<unit> ::= `bp' | `BP'
\alt `cc' | `CC'
@@ -297,8 +1182,6 @@ into Lua’s boolean type \lua{true}, the strings \texttt{false},
%
%%
-\clearpage
-
\subsubsection{number}
\begin{multicols}{2}
@@ -322,35 +1205,14 @@ into Lua’s boolean type \lua{true}, the strings \texttt{false},
%
%%
-\clearpage
-
\subsubsection{dimension}
-|luakeys| detects \TeX{} dimensions and automatically converts the
-dimensions into scaled points using the function \lua{tex.sp(dim)}. Use
-the option \lua{convert_dimensions} of the function
-\lua{parse(kv_string, options)} to disalbe the automatic conversion.
-
-\begin{minted}{lua}
-local result = parse('dim=1cm', {
- convert_dimensions = false,
-})
-\end{minted}
-
-\noindent
-If you want to convert a scale point into a unit string you can use the module
-\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua}{lualibs-util-dim.lua}.
-
-\begin{minted}{latex}
-\begin{luacode}
-require('lualibs')
-tex.print(number.todimen(tex.sp('1cm'), 'cm', '%0.0F%s'))
-\end{luacode}
-\end{minted}
-
+\begin{multicols}{3}
+\tiny
\begin{center}
\begin{tabular}{rl}
-\textbf{Unit name} & \textbf{Description} \\\hline
+% \textbf{Unit name}
+& \textbf{Description} \\\hline
bp & big point \\
cc & cicero \\
cm & centimeter \\
@@ -367,7 +1229,8 @@ sp & scaledpoint \\
\end{tabular}
\end{center}
-\begin{multicols}{2}
+\columnbreak
+
\begin{minted}{latex}
\luakeysdebug{
bp = 1bp,
@@ -385,6 +1248,9 @@ sp & scaledpoint \\
sp = 1sp,
}
\end{minted}
+
+\columnbreak
+
\begin{minted}{lua}
{
['bp'] = 65781,
@@ -410,64 +1276,41 @@ sp & scaledpoint \\
\subsubsection{string}
-There are two ways to specify strings: With or without quotes. If the
-text have to contain commas or equal signs, then double quotation
-marks must be used.
-
-\begin{multicols}{2}
-\begin{minted}{latex}
-\luakeysdebug{
- without quotes = no commas and equal signs are allowed,
- with double quotes = ", and = are allowed",
-}
-\end{minted}
-\begin{minted}{lua}
-{
- ['without quotes'] = 'no commas and equal signs are allowed',
- ['with double quotes'] = ', and = are allowed',
-}
-\end{minted}
-\end{multicols}
+% Es gibt zwei Möglichkeiten Zeichenketten anzugeben:
+There are two ways to specify strings:
+%
+% Mit oder ohne doppelte Anführungszeichen.
+With or without double quotes.
+%
+% Wenn der Text Kommas, geschweifte Klammern oder Gleichheitszeichen
+% enthalten soll, müssen doppelte Anführungszeichen verwendet werden.
+If the text have to contain commas, curly braces or equal signs, then
+double quotes must be used.
-\subsubsection{Standalone values}
+\InputLuaExample[firstline=4,lastline=17]{data-types/string.lua}
-Standalone values are values without a key. They are converted into an
-array. In Lua an array is a table with numeric indexes (The first index
-is 1).
+\subsubsection{Naked keys}
-\begin{multicols}{2}
-\begin{minted}{latex}
-\luakeysdebug{one,two,three}
-\end{minted}
-\columnbreak
-\begin{minted}{lua}
-{ 'one', 'two', 'three' }
-\end{minted}
+% Nackte Schlüssel sind Schlüssel ohne Wert.
+Naked keys are keys without a value.
+%
+% Mit der Option \lua{naked_as_value} können sie als Werte in ein Feld
+% übernommen werden.
+Using the option \lua{naked_as_value} they can be converted into values
+and stored into an array.
+%
+% In Lua ist ein Feld eine Tabelle mit numerischen Indizes (der erste
+% Index ist 1).
+In Lua an array is a table with numeric indexes (The first index is 1).
-\noindent
-is equivalent to
-
-\begin{minted}{lua}
-{
- [1] = 'one',
- [2] = 'two',
- [3] = 'three',
-}
-\end{minted}
-\end{multicols}
+\InputLatexExample[firstline=5,lastline=12]{luakeysdebug/naked-keys.tex}
\noindent
+% VAlle erkannten Datentypen können als eigenständige Werte verwendet
+% werden.
All recognized data types can be used as standalone values.
-\begin{multicols}{2}
-\begin{minted}{latex}
-\luakeysdebug{one,2,3cm}
-\end{minted}
-\columnbreak
-\begin{minted}{lua}
-{ 'one', 2, 5594039 }
-\end{minted}
-\end{multicols}
+\InputLatexExample[firstline=14,lastline=19]{luakeysdebug/naked-keys.tex}
%-----------------------------------------------------------------------
%
@@ -475,166 +1318,34 @@ All recognized data types can be used as standalone values.
\clearpage
-\section{Exported functions of the Lua module \texttt{luakeys.lua}}
-
-To learn more about the individual functions (local functions), please
-read the \href{https://josef-friedrich.github.io/luakeys/}{source code
-documentation}, which was created with
-\href{http://stevedonovan.github.io/ldoc/}{LDoc}. The Lua module exports
-this functions:
-
-\begin{minted}{lua}
-local luakeys = require('luakeys')
-local parse = luakeys.parse
-local render = luakeys.render
---local print = luakeys.print -- That would overwrite the built-in Lua function
-local save = luakeys.save
-local get = luakeys.get
-\end{minted}
+\section{Examples}
-%%
-%
-%%
+\subsection{Extend and modify keys of existing macros}
-\subsection{\texttt{parse(kv\_string, options)}: table}
+Extend the includegraphics macro with a new key named \latex{caption}
+and change the accepted values of the \latex{width} key. A number
+between 0 and 1 is allowed and converted into
+\latex{width=0.5\linewidth}
-The function \lua{parse(input_string, options)} is the main method of
-this module. It parses a key-value string into a Lua table.
-
-\begin{minted}{latex}
-\newcommand{\mykeyvalcmd}[1][]{
- \directlua{
- result = luakeys.parse('#1')
- luakeys.print(result)
- }
- #2
-}
-\mykeyvalcmd[one=1]{test}
-\end{minted}
+\InputLuaExample{extend-includegraphics/extend-keys.lua}
+\InputLatexExample{extend-includegraphics/extend-keys.tex}
-\noindent
-In plain \TeX:
+\subsection{Process document class options}
\begin{minted}{latex}
-\def\mykeyvalcommand#1{
- \directlua{
- result = luakeys.parse('#1')
- luakeys.print(result)
- }
-}
-\mykeyvalcmd{one=1}
-\end{minted}
-
-\noindent
-The function can be called with an options table. This options are
-supported:
-
-\begin{minted}{lua}
-local result = parse('one,two,three', {
- convert_dimensions = false,
- unpack_single_array_value = false,
- standalone_as_true = false,
- converter = function(key, value, depth, current_table, root_table)
- return key, value
- end,
- case_insensitive_keys = false,
-})
+\directlua{luakeys.parse('\@classoptionslist')}
\end{minted}
-\noindent
-The options can also be set globally using the exported table
-|default_options|:
+\InputLatexExample{class-options/test-class.cls}
+\InputLatexExample{class-options/use-test-class.tex}
\begin{minted}{lua}
-luakeys.parse('dim=1cm') -- {dim = 1864679}
-luakeys.default_options.convert_dimensions = false
--- or:
--- local defaults = luakeys.default_options
--- defaults.convert_dimensions = false
-luakeys.parse('dim=1cm') -- {dim = '1cm'}
-\end{minted}
-
-%%
-%
-%%
-
-\subsection{\texttt{render(tbl)}: string}
-
-The function \lua{render(tbl)} reverses the function
-\lua{parse(kv_string)}. It takes a Lua table and converts this table
-into a key-value string. The resulting string usually has a different
-order as the input table.
-
-\begin{minted}{lua}
-result = luakeys.parse('one=1,two=2,tree=3,')
-print(luakeys.render(result))
---- one=1,two=2,tree=3,
---- or:
---- two=2,one=1,tree=3,
---- or:
---- ...
-\end{minted}
-
-\noindent
-In Lua only tables with 1-based consecutive integer keys (a.k.a. array
-tables) can be parsed in order.
-
-\begin{minted}{lua}
-result = luakeys.parse('one,two,three')
-print(luakeys.render(result))
---- one,two,three, (always)
-\end{minted}
-
-%%
-%
-%%
-
-\subsection{\texttt{print(tbl): void}}
-
-The function \lua{print(tbl)} pretty prints a Lua table to standard
-output (stdout). It is a utility function that can be used to debug and
-inspect the resulting Lua table of the function \lua{parse}. You have to
-compile your \TeX{} document in a console to see the terminal output.
-
-%\luakeysdebug{level1={level2={key=value}}}
-
-\begin{minted}{lua}
-result = luakeys.parse('level1={level2={key=value}}')
-luakeys.print(result)
-\end{minted}
-
-\noindent
-The output should look like this:
-
-\begin{minted}{md}
{
- ['level1'] = {
- ['level2'] = {
- ['key'] = 'value',
- },
+ [1] = '12pt',
+ [2] = 'landscape',
}
\end{minted}
-%%
-%
-%%
-
-\subsection{\texttt{save(identifier, result): void}}
-
-The function \lua{save(identifier, result)} saves a result (a
-table from a previous run of \lua{parse}) under an identifier.
-Therefore, it is not necessary to pollute the global namespace to
-store results for the later usage.
-
-%%
-%
-%%
-
-\subsection{\texttt{get(identifier): table}}
-
-The function \lua{get(identifier)} retrieves a saved result from the
-result store.
-
%-----------------------------------------------------------------------
%
%-----------------------------------------------------------------------
@@ -685,7 +1396,7 @@ An example of how to use the command in \LaTeX:
\usepackage{luakeys-debug}
\begin{document}
\luakeysdebug[
- unpack single array values=false,
+ unpack=false,
convert dimensions=false
]{one,two,three}
\end{document}
@@ -723,9 +1434,9 @@ An example of how to use the command in \LaTeX:
\clearpage
-\subsection{luakeys.tex}
+\subsection{luakeys.sty}
-\inputminted[linenos=true]{latex}{luakeys.tex}
+\inputminted[linenos=true]{latex}{luakeys.sty}
%%
%
@@ -766,9 +1477,22 @@ An example of how to use the command in \LaTeX:
}
\changes{v0.5}{2022/04/04}{
* Add possibility to change options globally
-* New option: standalone\_as\_true
+* New option: standalone_as_true
* Add a recursive converter callback / hook to process the parse tree
-* New option: case\_insensitive\_keys
+* New option: case_insensitive_keys
+}
+\changes{v0.6}{2022/06/09}{
+* New feature: keys now can be defined using the function
+ “define(defs, opts)” or “define(kv_string, { defs = { key = { ... } } })”
+* Rename the global options table from “default_options” to “opts”
+* New option “format_keys”
+* Remove option “case_insensitive_keys”. Use
+ “format_keys = \{ lower \}” to achieve the same effect.
+* The default value of the option “convert_dimension” is now false.
+* The option “standalone_as_true” is renamed to “naked_as_value”.
+ The boolean value of the option must be changed to the opposite to
+ produce the previous effect.
+* The function “print()” is now called “debug()”
}
\pagebreak
\PrintChanges
diff --git a/macros/luatex/generic/luakeys/luakeys.lua b/macros/luatex/generic/luakeys/luakeys.lua
index c5ccf6b01d..ae689d02c1 100644
--- a/macros/luatex/generic/luakeys/luakeys.lua
+++ b/macros/luatex/generic/luakeys/luakeys.lua
@@ -15,59 +15,120 @@
--
-- This work consists of the files luakeys.lua, luakeys.sty, luakeys.tex
-- luakeys-debug.sty and luakeys-debug.tex.
-
--- A key-value parser written with Lpeg.
--
--- Explanations of some LPeg notation forms:
---
--- * `patt ^ 0` = `expression *`
--- * `patt ^ 1` = `expression +`
--- * `patt ^ -1` = `expression ?`
--- * `patt1 * patt2` = `expression1 expression2`: Sequence
--- * `patt1 + patt2` = `expression1 / expression2`: Ordered choice
---
--- * [TUGboat article: Parsing complex data formats in LuaTEX with LPEG](https://tug.org/TUGboat/tb40-2/tb125menke-Patterndf)
---
-- @module luakeys
-
local lpeg = require('lpeg')
-local Variable = lpeg.V
-local Pattern = lpeg.P
-local Set = lpeg.S
-local Range = lpeg.R
-local CaptureGroup = lpeg.Cg
-local CaptureFolding = lpeg.Cf
-local CaptureTable = lpeg.Ct
-local CaptureConstant = lpeg.Cc
-local CaptureSimple = lpeg.C
if not tex then
- tex = {}
+ tex = {
+ -- Dummy function for the tests.
+ sp = function(input)
+ return 1234567
+ end,
+ }
+end
+
+if not token then
+ token = {
+ set_macro = function(csname, content, global)
+ end,
+ }
+end
+
+local utils = {
+ --- Get the size of an array like table `{ 'one', 'two', 'three' }` = 3.
+ --
+ -- @tparam table value A table or any input.
+ --
+ -- @treturn number The size of the array like table. 0 if the input is
+ -- no table or the table is empty.
+ get_array_size = function(value)
+ local count = 0
+ if type(value) == 'table' then
+ for _ in ipairs(value) do
+ count = count + 1
+ end
+ end
+ return count
+ end,
+
+ --- Get the size of a table `{ one = 'one', 'two', 'three' }` = 3.
+ --
+ -- @tparam table value A table or any input.
+ --
+ -- @treturn number The size of the array like table. 0 if the input is
+ -- no table or the table is empty.
+ get_table_size = function(value)
+ local count = 0
+ if type(value) == 'table' then
+ for _ in pairs(value) do
+ count = count + 1
+ end
+ end
+ return count
+ end,
+
+ remove_from_array = function(array, element)
+ for index, value in pairs(array) do
+ if element == value then
+ array[index] = nil
+ return value
+ end
+ end
+ end,
+}
- -- Dummy function for the tests.
- tex['sp'] = function (input)
- return 1234567
+--- https://stackoverflow.com/a/1283608/10193818
+local function merge_tables(target, t2)
+ for k, v in pairs(t2) do
+ if type(v) == 'table' then
+ if type(target[k] or false) == 'table' then
+ merge_tables(target[k] or {}, t2[k] or {})
+ elseif target[k] == nil then
+ target[k] = v
+ end
+ elseif target[k] == nil then
+ target[k] = v
+ end
end
+ return target
end
---- Option handling
--- @section
+--- http://lua-users.org/wiki/CopyTable
+local function clone_table(orig)
+ local orig_type = type(orig)
+ local copy
+ if orig_type == 'table' then
+ copy = {}
+ for orig_key, orig_value in next, orig, nil do
+ copy[clone_table(orig_key)] = clone_table(orig_value)
+ end
+ setmetatable(copy, clone_table(getmetatable(orig)))
+ else -- number, string, boolean, etc
+ copy = orig
+ end
+ return copy
+end
--- This table stores all allowed option keys.
-local option_keys = {
- 'convert_dimensions',
- 'unpack_single_array_values',
- 'standalone_as_true',
- 'converter',
- 'case_insensitive_keys'
+local all_options = {
+ convert_dimensions = false,
+ debug = false,
+ default = true,
+ defaults = false,
+ defs = false,
+ format_keys = false,
+ hooks = {},
+ naked_as_value = false,
+ no_error = false,
+ postprocess = false,
+ preprocess = false,
+ unpack = true,
}
--- The default options.
-local default_options = {
- convert_dimensions = true,
- unpack_single_array_values = true,
- standalone_as_true = false,
-}
+local default_options = clone_table(all_options)
local function throw_error(message)
if type(tex.error) == 'function' then
@@ -77,58 +138,142 @@ local function throw_error(message)
end
end
---- Convert a key so that it can be written as a table field without
--- quotes and square brackets (for example `one 2` becomes `one_2`).
--- The key can then reference values from a table using dot notation.
--- (`table["one 2"]` becomes `table.one_2`).
---
--- @tparam string key The key to be converted.
---
--- @treturn string The converted key.
-local function luafy_key(key)
- return key:gsub('[^%w]+', '_')
-end
+local l3_code_cctab = 10
---- Convert all keys in a table to strings containig only alphanumeric
--- characters and underscores.
+--- Convert back to strings
+-- @section
+
+--- The function `render(tbl)` reverses the function
+-- `parse(kv_string)`. It takes a Lua table and converts this table
+-- into a key-value string. The resulting string usually has a
+-- different order as the input table. In Lua only tables with
+-- 1-based consecutive integer keys (a.k.a. array tables) can be
+-- parsed in order.
--
--- @param raw_options Some raw options.
+-- @tparam table result A table to be converted into a key-value string.
--
--- @treturn table Returns always a table. If the input value is not a
--- an empty table is returned.
-local function luafy_options(raw_options)
- if type(raw_options) ~= 'table' then
- raw_options = {}
- end
- local options = {}
- for key, value in pairs(raw_options) do
- options[luafy_key(key)] = value
+-- @treturn string A key-value string that can be passed to a TeX
+-- macro.
+local function render(result)
+ local function render_inner(result)
+ local output = {}
+ local function add(text)
+ table.insert(output, text)
+ end
+ for key, value in pairs(result) do
+ if (key and type(key) == 'string') then
+ if (type(value) == 'table') then
+ if (next(value)) then
+ add(key .. '={')
+ add(render_inner(value))
+ add('},')
+ else
+ add(key .. '={},')
+ end
+ else
+ add(key .. '=' .. tostring(value) .. ',')
+ end
+ else
+ add(tostring(value) .. ',')
+ end
+ end
+ return table.concat(output)
end
- return options
+ return render_inner(result)
end
---- All option keys can be written with underscores or with spaces as
--- separators.
--- For the LaTeX version of the macro
--- `\luakeysdebug[options]{kv-string}`.
+--- The function `stringify(tbl, for_tex)` converts a Lua table into a
+-- printable string. Stringify a table means to convert the table into
+-- a string. This function is used to realize the `debug` function.
+-- `stringify(tbl, true)` (`for_tex = true`) generates a string which
+-- can be embeded into TeX documents. The macro `\luakeysdebug{}` uses
+-- this option. `stringify(tbl, false)` or `stringify(tbl)` generate a
+-- string suitable for the terminal.
--
--- @tparam table options_raw Options in a raw format. The table may be
--- empty or some keys are not set.
+-- @tparam table result A table to stringify.
--
--- @treturn table
-local function normalize_parse_options (options_raw)
- options_raw = luafy_options(options_raw)
- local options = {}
-
- for _, option_name in ipairs(option_keys) do
- if options_raw[option_name] ~= nil then
- options[option_name] = options_raw[option_name]
- else
- options[option_name] = default_options[option_name]
+-- @tparam boolean for_tex Stringify the table into a text string that
+-- can be embeded inside a TeX document via tex.print(). Curly braces
+-- and whites spaces are escaped.
+--
+-- https://stackoverflow.com/a/54593224/10193818
+local function stringify(result, for_tex)
+ local line_break, start_bracket, end_bracket, indent
+
+ if for_tex then
+ line_break = '\\par'
+ start_bracket = '$\\{$'
+ end_bracket = '$\\}$'
+ indent = '\\ \\ '
+ else
+ line_break = '\n'
+ start_bracket = '{'
+ end_bracket = '}'
+ indent = ' '
+ end
+
+ local function stringify_inner(input, depth)
+ local output = {}
+ depth = depth or 0
+
+ local function add(depth, text)
+ table.insert(output, string.rep(indent, depth) .. text)
+ end
+
+ local function format_key(key)
+ if (type(key) == 'number') then
+ return string.format('[%s]', key)
+ else
+ return string.format('[\'%s\']', key)
+ end
end
+
+ if type(input) ~= 'table' then
+ return tostring(input)
+ end
+
+ for key, value in pairs(input) do
+ if (key and type(key) == 'number' or type(key) == 'string') then
+ key = format_key(key)
+
+ if (type(value) == 'table') then
+ if (next(value)) then
+ add(depth, key .. ' = ' .. start_bracket)
+ add(0, stringify_inner(value, depth + 1))
+ add(depth, end_bracket .. ',');
+ else
+ add(depth, key .. ' = ' .. start_bracket .. end_bracket .. ',')
+ end
+ else
+ if (type(value) == 'string') then
+ value = string.format('\'%s\'', value)
+ else
+ value = tostring(value)
+ end
+
+ add(depth, key .. ' = ' .. value .. ',')
+ end
+ end
+ end
+
+ return table.concat(output, line_break)
end
- return options
+ return
+ start_bracket .. line_break .. stringify_inner(result, 1) .. line_break ..
+ end_bracket
+end
+
+--- The function `debug(tbl)` pretty prints a Lua table to standard
+-- output (stdout). It is a utility function that can be used to
+-- debug and inspect the resulting Lua table of the function
+-- `parse`. You have to compile your TeX document in a console to
+-- see the terminal output.
+--
+-- @tparam table result A table to be printed to standard output for
+-- debugging purposes.
+local function debug(result)
+ print('\n' .. stringify(result, false))
end
--- Parser / Lpeg related
@@ -136,18 +281,42 @@ end
--- Generate the PEG parser using Lpeg.
--
+-- Explanations of some LPeg notation forms:
+--
+-- * `patt ^ 0` = `expression *`
+-- * `patt ^ 1` = `expression +`
+-- * `patt ^ -1` = `expression ?`
+-- * `patt1 * patt2` = `expression1 expression2`: Sequence
+-- * `patt1 + patt2` = `expression1 / expression2`: Ordered choice
+--
+-- * [TUGboat article: Parsing complex data formats in LuaTEX with LPEG](https://tug.org/TUGboat/tb40-2/tb125menke-Patterndf)
+--
-- @treturn userdata The parser.
-local function generate_parser(options)
+local function generate_parser(initial_rule, convert_dimensions)
+ if convert_dimensions == nil then
+ convert_dimensions = false
+ end
+
+ local Variable = lpeg.V
+ local Pattern = lpeg.P
+ local Set = lpeg.S
+ local Range = lpeg.R
+ local CaptureGroup = lpeg.Cg
+ local CaptureFolding = lpeg.Cf
+ local CaptureTable = lpeg.Ct
+ local CaptureConstant = lpeg.Cc
+ local CaptureSimple = lpeg.C
+
-- Optional whitespace
local white_space = Set(' \t\n\r')
--- Match literal string surrounded by whitespace
local ws = function(match)
- return white_space^0 * Pattern(match) * white_space^0
+ return white_space ^ 0 * Pattern(match) * white_space ^ 0
end
- local capture_dimension = function (input)
- if options.convert_dimensions then
+ local capture_dimension = function(input)
+ if convert_dimensions then
return tex.sp(input)
else
return input
@@ -180,8 +349,9 @@ local function generate_parser(options)
end
end
+ -- LuaFormatter off
return Pattern({
- 'list',
+ [1] = initial_rule,
-- list_item*
list = CaptureFolding(
@@ -220,6 +390,9 @@ local function generate_parser(options)
Variable('string_quoted') * -Variable('value') +
Variable('string_unquoted'),
+ -- for is.boolean()
+ boolean_only = Variable('boolean') * -1,
+
-- boolean_true / boolean_false
boolean =
(
@@ -237,18 +410,31 @@ local function generate_parser(options)
Pattern('FALSE') +
Pattern('False'),
+ -- for is.dimension()
+ dimension_only = Variable('dimension') * -1,
+
dimension = (
- Variable('sign')^0 * white_space^0 *
Variable('tex_number') * white_space^0 *
Variable('unit')
) / capture_dimension,
- number =
- (white_space^0 * (Variable('lua_number') / tonumber) * white_space^0) ,
+ -- for is.number()
+ number_only = Variable('number') * -1,
+
+ -- capture number
+ number = Variable('tex_number') / tonumber,
+ -- sign? white_space? (integer+ fractional? / fractional)
tex_number =
- (Variable('integer')^1 * (Pattern('.') * Variable('integer')^1)^0) +
- (Pattern('.') * Variable('integer')^1),
+ Variable('sign')^0 * white_space^0 *
+ (Variable('integer')^1 * Variable('fractional')^0) +
+ Variable('fractional'),
+
+ sign = Set('-+'),
+
+ fractional = Pattern('.') * Variable('integer')^1,
+
+ integer = Range('09')^1,
-- 'bp' / 'BP' / 'cc' / etc.
-- https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua
@@ -267,18 +453,6 @@ local function generate_parser(options)
Pattern('pt') + Pattern('PT') +
Pattern('sp') + Pattern('SP'),
- lua_number =
- Variable('int') *
- Variable('frac')^-1,
-
- int = Variable('sign')^-1 * (
- Range('19') * Variable('integer') + Variable('integer')
- ),
-
- frac = Pattern('.') * Variable('integer'),
- sign = Set('-+'),
- integer = Range('09')^1,
-
-- '"' ('\"' / !'"')* '"'
string_quoted =
white_space^0 * Pattern('"') *
@@ -294,68 +468,25 @@ local function generate_parser(options)
word_unquoted = (1 - white_space - Set('{},='))^1
})
+-- LuaFormatter on
end
---- Get the size of an array like table `{ 'one', 'two', 'three' }` = 3.
---
--- @tparam table value A table or any input.
---
--- @treturn number The size of the array like table. 0 if the input is
--- no table or the table is empty.
-local function get_array_size(value)
- local count = 0
- if type(value) == 'table' then
- for _ in ipairs(value) do count = count + 1 end
- end
- return count
-end
-
---- Get the size of a table `{ one = 'one', 'two', 'three' }` = 3.
---
--- @tparam table value A table or any input.
---
--- @treturn number The size of the array like table. 0 if the input is
--- no table or the table is empty.
-local function get_table_size(value)
- local count = 0
- if type(value) == 'table' then
- for _ in pairs(value) do count = count + 1 end
- end
- return count
-end
-
---- Unpack a single valued array table like `{ 'one' }` into `one` or
--- `{ 1 }` into `1`.
---
--- @treturn If the value is a array like table with one non table typed
--- value in it, the unpacked value, else the unchanged input.
-local function unpack_single_valued_array_table(value, options)
- if
- type(value) == 'table' and
- get_array_size(value) == 1 and
- get_table_size(value) == 1 and
- type(value[1]) ~= 'table'
- then
- if type(value[1]) == 'string' and options.standalone_as_true then
- return value
- else
- return value[1]
- end
- end
- return value
-end
-
-local function visit_parse_tree(parse_tree, callback_func)
- if type(parse_tree) ~= 'table' then
- throw_error('Parse tree has to be a table')
+local function visit_tree(tree, callback_func)
+ if type(tree) ~= 'table' then
+ throw_error('Parameter “tree” has to be a table, got: ' ..
+ tostring(tree))
end
- local function visit_parse_tree_recursive(root_table, current_table, result, depth, callback_func)
- for key, value in pairs(current_table) do
+ local function visit_tree_recursive(tree,
+ current,
+ result,
+ depth,
+ callback_func)
+ for key, value in pairs(current) do
if type(value) == 'table' then
- value = visit_parse_tree_recursive(root_table, value, {}, depth + 1, callback_func)
+ value = visit_tree_recursive(tree, value, {}, depth + 1, callback_func)
end
- key, value = callback_func(key, value, depth, current_table, root_table)
+ key, value = callback_func(key, value, depth, current, tree)
if key ~= nil and value ~= nil then
result[key] = value
@@ -366,255 +497,574 @@ local function visit_parse_tree(parse_tree, callback_func)
end
end
- return visit_parse_tree_recursive(parse_tree, parse_tree, {}, 1, callback_func)
+ local result = visit_tree_recursive(tree, tree, {}, 1, callback_func)
+
+ if result == nil then
+ return {}
+ end
+ return result
end
---- Normalize the result tables of the LPeg parser. This normalization
--- tasks are performed on the raw input table coming directly from the
--- PEG parser:
---
--- * Unpack all single valued array like tables: `{ 'text' }` into
--- `text`
---
--- @tparam table raw The raw input table coming directly from the PEG
--- parser
---
--- @tparam table options Some options. A table with the key
--- `unpack_single_array_values`
---
--- @treturn table A normalized table ready for the outside world.
-local function normalize(raw, options)
- local function normalize_recursive(raw, result, options)
- for key, value in pairs(raw) do
- if options.unpack_single_array_values then
- value = unpack_single_valued_array_table(value, options)
- end
- if type(value) == 'table' then
- result[key] = normalize_recursive(value, {}, options)
- else
- result[key] = value
- end
+local is = {
+ boolean = function(value)
+ if value == nil then
+ return false
end
- return result
- end
- raw = normalize_recursive(raw, {}, options)
+ if type(value) == 'boolean' then
+ return true
+ end
+ local parser = generate_parser('boolean_only', false)
+ local result = parser:match(value)
+ return result ~= nil
+ end,
+
+ dimension = function(value)
+ if value == nil then
+ return false
+ end
+ local parser = generate_parser('dimension_only', false)
+ local result = parser:match(value)
+ return result ~= nil
+ end,
+
+ integer = function(value)
+ local n = tonumber(value)
+ if n == nil then
+ return false
+ end
+ return n == math.floor(n)
+ end,
- if options.standalone_as_true then
- raw = visit_parse_tree(raw, function (key, value)
- if type(key) == 'number' and type(value) == 'string' then
- return value, true
- end
- return key, value
- end)
- end
+ number = function(value)
+ if value == nil then
+ return false
+ end
+ if type(value) == 'number' then
+ return true
+ end
+ local parser = generate_parser('number_only', false)
+ local result = parser:match(value)
+ return result ~= nil
+ end,
+
+ string = function(value)
+ return type(value) == 'string'
+ end,
+}
- if options.case_insensitive_keys then
- raw = visit_parse_tree(raw, function (key, value)
- if type(key) == 'string' then
- return key:lower(), value
- end
- return key, value
- end)
+--- Apply the key-value-pair definitions (defs) on an input table in a
+--- recursive fashion.
+---
+---@param defs table A table containing all definitions.
+---@param opts table The parse options table.
+---@param input table The current input table.
+---@param output table The current output table.
+---@param unknown table Always the root unknown table.
+---@param key_path table An array of key names leading to the current
+---@param input_root table The root input table
+--- input and output table.
+local function apply_definitions(defs,
+ opts,
+ input,
+ output,
+ unknown,
+ key_path,
+ input_root)
+ local exclusive_groups = {}
+
+ local function add_to_key_path(key_path, key)
+ local new_key_path = {}
+
+ for index, value in ipairs(key_path) do
+ new_key_path[index] = value
+ end
+
+ table.insert(new_key_path, key)
+ return new_key_path
end
- return raw
-end
+ local function set_default_value(def)
+ if def.default ~= nil then
+ return def.default
+ elseif opts ~= nil and opts.default ~= nil then
+ return opts.default
+ end
+ return true
+ end
---- Parse a LaTeX/TeX style key-value string into a Lua table. With
--- this function you should be able to parse key-value strings like
--- this example:
---
--- show,
--- hide,
--- key with spaces = String without quotes,
--- string="String with double quotes: ,{}=",
--- dimension = 1cm,
--- number = -1.2,
--- list = {one,two,three},
--- key value list = {one=one,two=two,three=three},
--- nested key = {
--- nested key 2= {
--- key = value,
--- },
--- },
---
--- The string above results in this Lua table:
---
--- {
--- 'show',
--- 'hide',
--- ['key with spaces'] = 'String without quotes',
--- string = 'String with double quotes: ,{}=',
--- dimension = 1864679,
--- number = -1.2,
--- list = {'one', 'two', 'three'},
--- key value list = {
--- one = 'one',
--- three = 'three',
--- two = 'two'
--- },
--- ['nested key'] = {
--- ['nested key 2'] = {
--- key = 'value'
--- }
--- },
--- }
---
--- @tparam string kv_string A string in the TeX/LaTeX style key-value
--- format as described above.
---
--- @tparam table options A table containing
--- settings: `convert_dimensions`, `unpack_single_array_values`, `standalone_as_true`, `converter`
---
--- @treturn table A hopefully properly parsed table you can do
--- something useful with.
-local function parse (kv_string, options)
- if kv_string == nil then
- return {}
+ local function find_value(search_key, def)
+ if input[search_key] ~= nil then
+ local value = input[search_key]
+ input[search_key] = nil
+ return value
+ -- naked keys: values with integer keys
+ elseif utils.remove_from_array(input, search_key) ~= nil then
+ return set_default_value(def)
+ end
end
- options = normalize_parse_options(options)
- local parser = generate_parser(options)
- local parse_tree = parser:match(kv_string)
+ local apply = {
+ alias = function(value, key, def)
+ if type(def.alias) == 'string' then
+ def.alias = { def.alias }
+ end
+ local alias_value
+ local used_alias_key
+ -- To get an error if the key and an alias is present
+ if value ~= nil then
+ alias_value = value
+ used_alias_key = key
+ end
+ for _, alias in ipairs(def.alias) do
+ local v = find_value(alias, def)
+ if v ~= nil then
+ if alias_value ~= nil then
+ throw_error(string.format(
+ 'Duplicate aliases “%s” and “%s” for key “%s”!',
+ used_alias_key, alias, key))
+ end
+ used_alias_key = alias
+ alias_value = v
+ end
+ end
+ if alias_value ~= nil then
+ return alias_value
+ end
+ end,
- if options.converter ~= nil and type(options.converter) == 'function' then
- parse_tree = visit_parse_tree(parse_tree, options.converter)
- end
- return normalize(parse_tree, options)
-end
+ always_present = function(value, key, def)
+ if value == nil and def.always_present then
+ return set_default_value(def)
+ end
+ end,
---- Convert back to strings
--- @section
+ choices = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.choices ~= nil and type(def.choices) == 'table' then
+ local is_in_choices = false
+ for _, choice in ipairs(def.choices) do
+ if value == choice then
+ is_in_choices = true
+ end
+ end
+ if not is_in_choices then
+ throw_error('The value “' .. value ..
+ '” does not exist in the choices: ' ..
+ table.concat(def.choices, ', ') .. '!')
+ end
+ end
+ end,
---- The function `render(tbl)` reverses the function
--- `parse(kv_string)`. It takes a Lua table and converts this table
--- into a key-value string. The resulting string usually has a
--- different order as the input table. In Lua only tables with
--- 1-based consecutive integer keys (a.k.a. array tables) can be
--- parsed in order.
---
--- @tparam table tbl A table to be converted into a key-value string.
---
--- @treturn string A key-value string that can be passed to a TeX
--- macro.
-local function render (tbl)
- local function render_inner(tbl)
- local output = {}
- local function add(text)
- table.insert(output, text)
- end
- for key, value in pairs(tbl) do
- if (key and type(key) == 'string') then
- if (type(value) == 'table') then
- if (next(value)) then
- add(key .. '={')
- add(render_inner(value))
- add('},')
+ data_type = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.data_type ~= nil then
+ local converted
+ -- boolean
+ if def.data_type == 'boolean' then
+ if value == 0 or value == '' or not value then
+ converted = false
else
- add(key .. '={},')
+ converted = true
end
+ -- dimension
+ elseif def.data_type == 'dimension' then
+ if is.dimension(value) then
+ converted = value
+ end
+ -- integer
+ elseif def.data_type == 'integer' then
+ if is.number(value) then
+ converted = math.floor(tonumber(value))
+ end
+ -- number
+ elseif def.data_type == 'number' then
+ if is.number(value) then
+ converted = tonumber(value)
+ end
+ -- string
+ elseif def.data_type == 'string' then
+ converted = tostring(value)
else
- add(key .. '=' .. tostring(value) .. ',')
+ throw_error('Unknown data type: ' .. def.data_type)
end
- else
- add(tostring(value) .. ',')
+ if converted == nil then
+ throw_error('The value “' .. value .. '” of the key “' .. key ..
+ '” could not be converted into the data type “' ..
+ def.data_type .. '”!')
+ else
+ return converted
+ end
+ end
+ end,
+
+ exclusive_group = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.exclusive_group ~= nil then
+ if exclusive_groups[def.exclusive_group] ~= nil then
+ throw_error('The key “' .. key ..
+ '” belongs to a mutually exclusive group “' ..
+ def.exclusive_group .. '” and the key “' ..
+ exclusive_groups[def.exclusive_group] ..
+ '” is already present!')
+ else
+ exclusive_groups[def.exclusive_group] = key
+ end
+ end
+ end,
+
+ l3_tl_set = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.l3_tl_set ~= nil then
+ tex.print(l3_code_cctab, '\\tl_set:Nn \\g_' .. def.l3_tl_set .. '_tl')
+ tex.print('{' .. value .. '}')
+ end
+ end,
+
+ macro = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.macro ~= nil then
+ token.set_macro(def.macro, value, 'global')
+ end
+ end,
+
+ match = function(value, key, def)
+ if value == nil then
+ return
end
+ if def.match ~= nil then
+ if type(def.match) ~= 'string' then
+ throw_error('def.match has to be a string')
+ end
+ local match = string.match(value, def.match)
+ if match == nil then
+ throw_error('The value “' .. value .. '” of the key “' .. key ..
+ '” does not match “' .. def.match .. '”!')
+ else
+ return match
+ end
+ end
+ end,
+
+ opposite_keys = function(value, key, def)
+ if def.opposite_keys ~= nil then
+ local true_value = def.opposite_keys[true]
+ local false_value = def.opposite_keys[false]
+ if true_value == nil or false_value == nil then
+ throw_error(
+ 'Usage opposite_keys = { [true] = "...", [false] = "..." }')
+ end
+ if utils.remove_from_array(input, true_value) ~= nil then
+ return true
+ elseif utils.remove_from_array(input, false_value) ~= nil then
+ return false
+ end
+ end
+ end,
+
+ process = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.process ~= nil and type(def.process) == 'function' then
+ return def.process(value, input_root, output, unknown)
+ end
+ end,
+
+ required = function(value, key, def)
+ if def.required ~= nil and def.required and value == nil then
+ throw_error(string.format('Missing required key “%s”!', key))
+ end
+ end,
+
+ sub_keys = function(value, key, def)
+ if def.sub_keys ~= nil then
+ local v
+ -- To get keys defined with always_present
+ if value == nil then
+ v = {}
+ elseif type(value) == 'string' then
+ v = { value }
+ elseif type(value) == 'table' then
+ v = value
+ end
+ v = apply_definitions(def.sub_keys, opts, v, output[key], unknown,
+ add_to_key_path(key_path, key), input_root)
+ if utils.get_table_size(v) > 0 then
+ return v
+ end
+ end
+ end,
+ }
+
+ --- standalone values are removed.
+ -- For some callbacks and the third return value of parse, we
+ -- need an unchanged raw result from the parse function.
+ input = clone_table(input)
+ if output == nil then
+ output = {}
+ end
+ if unknown == nil then
+ unknown = {}
+ end
+ if key_path == nil then
+ key_path = {}
+ end
+
+ for index, def in pairs(defs) do
+ --- Find key and def
+ local key
+ if type(def) == 'table' and def.name == nil and type(index) == 'string' then
+ key = index
+ elseif type(def) == 'table' and def.name ~= nil then
+ key = def.name
+ elseif type(index) == 'number' and type(def) == 'string' then
+ key = def
+ def = { default = true }
end
- return table.concat(output)
+
+ if type(def) ~= 'table' then
+ throw_error('Key definition must be a table')
+ end
+
+ if key == nil then
+ throw_error('key name couldn’t be detected!')
+ end
+
+ local value = find_value(key, def)
+
+ for _, def_opt in ipairs({
+ 'alias',
+ 'opposite_keys',
+ 'always_present',
+ 'required',
+ 'data_type',
+ 'choices',
+ 'match',
+ 'exclusive_group',
+ 'macro',
+ 'l3_tl_set',
+ 'process',
+ 'sub_keys',
+ }) do
+ if def[def_opt] ~= nil then
+ local tmp_value = apply[def_opt](value, key, def)
+ if tmp_value ~= nil then
+ value = tmp_value
+ end
+ end
+ end
+
+ output[key] = value
end
- return render_inner(tbl)
+
+ if utils.get_table_size(input) > 0 then
+ -- Move to the current unknown table.
+ local current_unknown = unknown
+ for _, key in ipairs(key_path) do
+ if current_unknown[key] == nil then
+ current_unknown[key] = {}
+ end
+ current_unknown = current_unknown[key]
+ end
+
+ -- Copy all unknown key-value-pairs to the current unknown table.
+ for key, value in pairs(input) do
+ current_unknown[key] = value
+ end
+ end
+
+ return output, unknown
end
---- The function `stringify(tbl, for_tex)` converts a Lua table into a
--- printable string. Stringify a table means to convert the table into
--- a string. This function is used to realize the `print` function.
--- `stringify(tbl, true)` (`for_tex = true`) generates a string which
--- can be embeded into TeX documents. The macro `\luakeysdebug{}` uses
--- this option. `stringify(tbl, false)` or `stringify(tbl)` generate a
--- string suitable for the terminal.
---
--- @tparam table input A table to stringify.
---
--- @tparam boolean for_tex Stringify the table into a text string that
--- can be embeded inside a TeX document via tex.print(). Curly braces
--- and whites spaces are escaped.
+--- Parse a LaTeX/TeX style key-value string into a Lua table.
+---
+---@param kv_string string A string in the TeX/LaTeX style key-value format as described above.
+---@param opts table A table containing the settings:
+--- `convert_dimensions`, `unpack`, `naked_as_value`, `converter`,
+--- `debug`, `preprocess`, `postprocess`.
--
--- https://stackoverflow.com/a/54593224/10193818
-local function stringify(input, for_tex)
- local line_break, start_bracket, end_bracket, indent
-
- if for_tex then
- line_break = '\\par'
- start_bracket = '$\\{$'
- end_bracket = '$\\}$'
- indent = '\\ \\ '
- else
- line_break = '\n'
- start_bracket = '{'
- end_bracket = '}'
- indent = ' '
+---@return table result The final result of all individual parsing and normalization steps.
+---@return table unknown A table with unknown, undefinied key-value pairs.
+---@return table raw The unprocessed, raw result of the LPeg parser.
+local function parse(kv_string, opts)
+ if kv_string == nil then
+ return {}
end
- local function stringify_inner(input, depth)
- local output = {}
- depth = depth or 0
+ --- Normalize the parse options.
+ ---
+ --- @param opts table Options in a raw format. The table may be empty or some keys are not set.
+ ---
+ --- @return table
+ local function normalize_opts(opts)
+ if type(opts) ~= 'table' then
+ opts = {}
+ end
+ for key, _ in pairs(opts) do
+ if all_options[key] == nil then
+ throw_error('Unknown parse option: ' .. tostring(key) .. '!')
+ end
+ end
+ local old_opts = opts
+ opts = {}
+ for name, _ in pairs(all_options) do
+ if old_opts[name] ~= nil then
+ opts[name] = old_opts[name]
+ else
+ opts[name] = default_options[name]
+ end
+ end
- local function add(depth, text)
- table.insert(output, string.rep(indent, depth) .. text)
+ local hooks = {
+ kv_string = true,
+ keys_before_opts = true,
+ result_before_opts = true,
+ keys_before_def = true,
+ result_before_def = true,
+ keys = true,
+ result = true,
+ }
+
+ for hook in pairs(opts.hooks) do
+ if hooks[hook] == nil then
+ throw_error('Unknown hook: ' .. tostring(hook) .. '!')
+ end
end
+ return opts
+ end
+ opts = normalize_opts(opts)
- local function format_key(key)
- if (type(key) == 'number') then
- return string.format('[%s]', key)
+ if type(opts.hooks.kv_string) == 'function' then
+ kv_string = opts.hooks.kv_string(kv_string)
+ end
+
+ local result = generate_parser('list', opts.convert_dimensions):match(
+ kv_string)
+ local raw = clone_table(result)
+
+ local function apply_hook(name)
+ if type(opts.hooks[name]) == 'function' then
+ if name:match('^keys') then
+ result = visit_tree(result, opts.hooks[name])
else
- return string.format('[\'%s\']', key)
+ opts.hooks[name](result)
+ end
+
+ if opts.debug then
+ print('After the execution of the hook: ' .. name)
+ debug(result)
end
end
+ end
- if type(input) ~= 'table' then
- return tostring(input)
+ local function apply_hooks(at)
+ if at ~= nil then
+ at = '_' .. at
+ else
+ at = ''
end
+ apply_hook('keys' .. at)
+ apply_hook('result' .. at)
+ end
- for key, value in pairs(input) do
- if (key and type(key) == 'number' or type(key) == 'string') then
- key = format_key(key)
+ apply_hooks('before_opts')
- if (type(value) == 'table') then
- if (next(value)) then
- add(depth, key .. ' = ' .. start_bracket)
- add(0, stringify_inner(value, depth + 1))
- add(depth, end_bracket .. ',');
- else
- add(depth, key .. ' = ' .. start_bracket .. end_bracket .. ',')
- end
- else
- if (type(value) == 'string') then
- value = string.format('\'%s\'', value)
- else
- value = tostring(value)
- end
+ --- Normalize the result table of the LPeg parser. This normalization
+ -- tasks are performed on the raw input table coming directly from
+ -- the PEG parser:
+ --
+ --- @param result table The raw input table coming directly from the PEG parser
+ --- @param opts table Some options.
+ local function apply_opts(result, opts)
+ local callbacks = {
+ unpack = function(key, value)
+ if type(value) == 'table' and utils.get_array_size(value) == 1 and
+ utils.get_table_size(value) == 1 and type(value[1]) ~= 'table' then
+ return key, value[1]
+ end
+ return key, value
+ end,
- add(depth, key .. ' = ' .. value .. ',')
+ process_naked = function(key, value)
+ if type(key) == 'number' and type(value) == 'string' then
+ return value, opts.default
+ end
+ return key, value
+ end,
+
+ format_key = function(key, value)
+ if type(key) == 'string' then
+ for _, style in ipairs(opts.format_keys) do
+ if style == 'lower' then
+ key = key:lower()
+ elseif style == 'snake' then
+ key = key:gsub('[^%w]+', '_')
+ elseif style == 'upper' then
+ key = key:upper()
+ else
+ throw_error('Unknown style to format keys: ' .. tostring(style) ..
+ ' Allowed styles are: lower, snake, upper')
+ end
+ end
end
+ return key, value
+ end,
+ }
+
+ if opts.unpack then
+ result = visit_tree(result, callbacks.unpack)
+ end
+
+ if not opts.naked_as_value and opts.defs == false then
+ result = visit_tree(result, callbacks.process_naked)
+ end
+
+ if opts.format_keys then
+ if type(opts.format_keys) ~= 'table' then
+ throw_error('The option “format_keys” has to be a table not ' ..
+ type(opts.format_keys))
end
+ result = visit_tree(result, callbacks.format_key)
end
- return table.concat(output, line_break)
+ return result
+ end
+ result = apply_opts(result, opts)
+
+ -- All unknown keys are stored in this table
+ local unknown = nil
+ if type(opts.defs) == 'table' then
+ apply_hooks('before_defs')
+ result, unknown = apply_definitions(opts.defs, opts, result, {}, {}, {},
+ clone_table(result))
end
- return start_bracket .. line_break .. stringify_inner(input, 1) .. line_break .. end_bracket
-end
+ apply_hooks()
---- The function `pretty_print(tbl)` pretty prints a Lua table to standard
--- output (stdout). It is a utility function that can be used to
--- debug and inspect the resulting Lua table of the function
--- `parse`. You have to compile your TeX document in a console to
--- see the terminal output.
---
--- @tparam table tbl A table to be printed to standard output for
--- debugging purposes.
-local function pretty_print(tbl)
- print(stringify(tbl, false))
+ if opts.defaults ~= nil and type(opts.defaults) == 'table' then
+ merge_tables(result, opts.defaults)
+ end
+
+ if opts.debug then
+ debug(result)
+ end
+
+ -- no_error
+ if not opts.no_error and type(unknown) == 'table' and
+ utils.get_table_size(unknown) > 0 then
+ throw_error('Unknown keys: ' .. render(unknown))
+ end
+ return result, unknown, raw
end
--- Store results
@@ -623,38 +1073,12 @@ end
--- A table to store parsed key-value results.
local result_store = {}
---- The function `save(identifier, result): void` saves a result (a
--- table from a previous run of `parse`) under an identifier.
--- Therefore, it is not necessary to pollute the global namespace to
--- store results for the later usage.
---
--- @tparam string identifier The identifier under which the result is
--- saved.
---
--- @tparam table result A result to be stored and that was created by
--- the key-value parser.
-local function save(identifier, result)
- result_store[identifier] = result
-end
-
---- The function `get(identifier): table` retrieves a saved result
--- from the result store.
---
--- @tparam string identifier The identifier under which the result was
--- saved.
-local function get(identifier)
- -- if result_store[identifier] == nil then
- -- throw_error('No stored result was found for the identifier \'' .. identifier .. '\'')
- -- end
- return result_store[identifier]
-end
-
--- Exports
-- @section
local export = {
--- @see default_options
- default_options = default_options,
+ opts = default_options,
--- @see stringify
stringify = stringify,
@@ -662,27 +1086,58 @@ local export = {
--- @see parse
parse = parse,
+ define = function(defs, opts)
+ return function(kv_string, inner_opts)
+ local options
+ if inner_opts ~= nil then
+ options = inner_opts
+ elseif opts ~= nil then
+ options = opts
+ end
+
+ if options == nil then
+ options = {}
+ end
+
+ options.defs = defs
+
+ return parse(kv_string, options)
+ end
+ end,
+
--- @see render
render = render,
- --- @see pretty_print
- print = pretty_print,
+ --- @see debug
+ debug = debug,
- --- @see save
- save = save,
-
- --- @see get
- get = get,
+ --- The function `save(identifier, result): void` saves a result (a
+ -- table from a previous run of `parse`) under an identifier.
+ -- Therefore, it is not necessary to pollute the global namespace to
+ -- store results for the later usage.
+ --
+ -- @tparam string identifier The identifier under which the result is
+ -- saved.
+ --
+ -- @tparam table result A result to be stored and that was created by
+ -- the key-value parser.
+ save = function(identifier, result)
+ result_store[identifier] = result
+ end,
+
+ --- The function `get(identifier): table` retrieves a saved result
+ -- from the result store.
+ --
+ -- @tparam string identifier The identifier under which the result was
+ -- saved.
+ get = function(identifier)
+ -- if result_store[identifier] == nil then
+ -- throw_error('No stored result was found for the identifier \'' .. identifier .. '\'')
+ -- end
+ return result_store[identifier]
+ end,
+
+ is = is,
}
--- http://olivinelabs.com/busted/#private
-if _TEST then
- export.luafy_key = luafy_key
- export.luafy_options = luafy_options
- export.normalize = normalize
- export.normalize_parse_options = normalize_parse_options
- export.unpack_single_valued_array_table = unpack_single_valued_array_table
- export.visit_parse_tree = visit_parse_tree
-end
-
return export
diff --git a/macros/luatex/generic/luakeys/luakeys.sty b/macros/luatex/generic/luakeys/luakeys.sty
index 2cf22b837a..51e2c9bec5 100644
--- a/macros/luatex/generic/luakeys/luakeys.sty
+++ b/macros/luatex/generic/luakeys/luakeys.sty
@@ -17,5 +17,5 @@
% luakeys-debug.sty and luakeys-debug.tex.
\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{luakeys}[2022/04/04 v0.5 Parsing key-value options using Lua.]
+\ProvidesPackage{luakeys}[2022/06/09 v0.6 Parsing key-value options using Lua.]
\directlua{luakeys = require('luakeys')}