texlive[65468] Master/texmf-dist: luakeys (5jan23)

commits+karl at tug.org commits+karl at tug.org
Thu Jan 5 22:18:54 CET 2023


Revision: 65468
          http://tug.org/svn/texlive?view=revision&revision=65468
Author:   karl
Date:     2023-01-05 22:18:54 +0100 (Thu, 05 Jan 2023)
Log Message:
-----------
luakeys (5jan23)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/luatex/luakeys/README.md
    trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.sty
    trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.tex
    trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.lua
    trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.sty
    trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.tex

Added Paths:
-----------
    trunk/Master/texmf-dist/doc/luatex/luakeys/documentation.tex
    trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys.pdf

Removed Paths:
-------------
    trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys-doc.pdf
    trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys-doc.tex

Modified: trunk/Master/texmf-dist/doc/luatex/luakeys/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/luatex/luakeys/README.md	2023-01-05 00:50:44 UTC (rev 65467)
+++ trunk/Master/texmf-dist/doc/luatex/luakeys/README.md	2023-01-05 21:18:54 UTC (rev 65468)
@@ -14,7 +14,7 @@
 
 ## License
 
-Copyright 2021-2022 Josef Friedrich
+Copyright 2021-2023 Josef Friedrich
 
 This work may be distributed and/or modified under the
 conditions of the LaTeX Project Public License, either version 1.3c
@@ -48,11 +48,16 @@
     -- Only values listed in the array table are allowed.
     choices = { 'one', 'two', 'three' },
 
-    -- Possible data types: boolean, dimension, integer, number, string
+    -- Possible data types:
+    -- any, boolean, dimension, integer, number, string, list
     data_type = 'string',
 
+    -- To provide a default value for each naked key individually.
     default = true,
 
+    -- Can serve as a comment.
+    description = 'Describe your key-value pair.',
+
     -- The key belongs to a mutually exclusive group of keys.
     exclusive_group = 'name',
 
@@ -64,11 +69,27 @@
 
     -- The name of the key, can be omitted
     name = 'key',
+
+    -- Convert opposite (naked) keys
+    -- into a boolean value and store this boolean under a target key:
+    --   show -> opposite_keys = true
+    --   hide -> opposite_keys = false
+    -- Short form: opposite_keys = { 'show', 'hide' }
     opposite_keys = { [true] = 'show', [false] = 'hide' },
+
+    -- Pick a value by its data type:
+    -- 'any', 'string', 'number', 'dimension', 'integer', 'boolean'.
+    pick = false, -- ’false’ disables the picking.
+
+    -- A function whose return value is passed to the key.
     process = function(value, input, result, unknown)
       return value
     end,
+
+    -- To enforce that a key must be specified.
     required = true,
+
+    -- To build nested key-value pair definitions.
     sub_keys = { key_level_2 = { } },
   }
 }
@@ -78,6 +99,9 @@
 
 ```lua
 local opts = {
+  -- Result table that is filled with each call of the parse function.
+  accumulated_result = accumulated_result,
+
   -- Configure the delimiter that assigns a value to a key.
   assignment_operator = '=',
 
@@ -176,9 +200,6 @@
 [Lua](https://marketplace.visualstudio.com/items?itemName=sumneko.lua)
 extension in Visual Studio Code. This extension understands the modified
 [EmmyLua annotations](https://github.com/sumneko/lua-language-server/wiki/Annotations).
-The Lua source code documentation is generated with
-[LDoc](https://stevedonovan.github.io/ldoc/manual/doc.md.html).
-
 The Lua code is automatically formatted with the help of the
 [LuaFormatter](https://github.com/Koihik/LuaFormatter).
 
@@ -212,7 +233,7 @@
 
 Update version in:
 
-* luakeys-doc.tex
+* documentation.tex
 * luakeys-debug.sty
 * luakeys.sty
 * luakeys.lua

Added: trunk/Master/texmf-dist/doc/luatex/luakeys/documentation.tex
===================================================================
--- trunk/Master/texmf-dist/doc/luatex/luakeys/documentation.tex	                        (rev 0)
+++ trunk/Master/texmf-dist/doc/luatex/luakeys/documentation.tex	2023-01-05 21:18:54 UTC (rev 65468)
@@ -0,0 +1,2161 @@
+\documentclass{ltxdoc}
+
+\EnableCrossrefs
+\CodelineIndex
+\RecordChanges
+
+\usepackage{mdframed}
+\usepackage{minted}
+\usepackage{luakeys}
+\usepackage{luakeys-debug}
+\usepackage{multicol}
+\usepackage{luacode}
+\usepackage{syntax}
+\usepackage{graphicx}
+
+\definecolor{bg}{rgb}{0.95,0.95,0.95}
+
+\usemintedstyle{friendly}
+\setminted{
+  breaklines=true,
+  fontsize=\footnotesize,
+  style=manni,
+}
+\def\lua#1{\mintinline{lua}|#1|}
+\def\latex#1{\mintinline{latex}|#1|}
+
+\NewDocumentCommand { \InputLatex } { O{} m } {
+  \inputminted[
+    linenos=false,
+    bgcolor=bg,
+    #1
+  ]{latex}{examples/#2}
+}
+
+\NewDocumentCommand { \InputLua } { O{} m } {
+  \inputminted[
+    linenos=false,
+    bgcolor=bg,
+    #1
+  ]{lua}{examples/#2}
+}
+
+\catcode`_=12
+\def\DefaultOpt#1{%
+  \texttt{\directlua{
+    tex.print(luakeys.print_default('opts', '#1'))
+  }}%
+}
+
+\def\DefaultOptDescription#1{
+\noindent
+The default value of the option “\texttt{#1}” is:
+\DefaultOpt{#1}.
+}
+
+\begin{document}
+
+\providecommand*{\url}{\texttt}
+
+\title{The \textsf{luakeys} package}
+\author{%
+  Josef Friedrich\\%
+  \url{josef at friedrich.rocks}\\%
+  \href{https://github.com/Josef-Friedrich/luakeys}
+       {github.com/Josef-Friedrich/luakeys}%
+}
+\date{v0.12.0 from 2023/01/05}
+
+\maketitle
+
+\vfill
+
+\InputLua[firstline=4,lastline=7]{first-page.lua}
+
+\noindent
+Result:
+
+\begin{center}
+\begin{minted}{lua}
+{
+  ['level1'] = {
+    ['level2'] = {
+      ['naked'] = true,
+      ['dim'] = 1864679,
+      ['bool'] = false,
+      ['num'] = -0.001,
+      ['str'] = 'lua,{}',
+    }
+  }
+}
+\end{minted}
+\end{center}
+
+\vfill
+
+\strut
+
+\newpage
+
+\tableofcontents
+
+\newpage
+
+% \section{Einführung}
+\section{Introduction}
+
+\noindent
+% |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.
+%
+% |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.
+
+% Der Artikel TUGboat
+% \href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf}
+% {“Implementing key–value input: An introduction” (Volume 30 (2009), No.
+% 1)} von \emph{Joseph Wright} und \emph{Christian Feuersänger} gibt einen
+% einen guten Überblick über die verfügbaren Key-Value-Pakete.
+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 \emph{Joseph Wright} and \emph{Christian Feuersänger} gives a
+good overview of the available key-value packages.
+%
+% Dieser Artikel geht auf eine auf tex.stackexchange.com von Will
+% Robertson: gestellte Frage zurück.
+This article is based on a question asked on tex.stackexchange.com by
+Will Robertson:
+\href{https://tex.stackexchange.com/questions/26771}
+{A big list of every keyval package}.
+%
+% CTAN stellt auch eine Übersichtsseite zum Thema bereit.
+CTAN also provides an overview page on the subject of
+\href{https://www.ctan.org/topic/keyval}
+{Key-Val: packages with key-value argument systems}.
+
+% Dieses Paket wäre ohne den Artikel
+% \href{https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf}
+% {"Parsing complex data formats in LuaTEX with LPEG" (Volume 40 (2019),
+% No. 2)} nicht möglich gewesen.
+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)}.
+
+\subsection{Pros of \texttt{luakeys}}
+
+\begin{itemize}
+% \item Schlüssel-Wert-Paare können unabhängig vom Makro-Paket (latex or
+% context) analysiert werden.
+\item Key-value pairs can be parsed independently of the macro
+collection (\LaTeX{} or Con\TeX{}t).
+% Sogar in Plain LuaTex können Schlüssel analysiert werden
+Even in plain Lua\TeX{} keys can be parsed.
+
+% \item |luakeys| kann mit ineinander verschachtelten listen an
+% Schlüssel-Wert-Paaren umgehen, d. h. es kann mit einer rekursiven
+% Datenstruktur an Schlüssel umgehen.
+\item |luakeys| can handle nested lists of key-value pairs, i.e. it can
+handle a recursive data structure of keys.
+
+% \item Schlüssel müssen nicht, aber können definiert werden.
+\item Keys do not have to be defined, but can they can be defined.
+\end{itemize}
+
+\subsection{Cons of \texttt{luakeys}}
+
+\begin{itemize}
+% \item Das Packet funktioniert nur in der Verbindung mit Lua\TeX.
+\item The package works only in combination with Lua\TeX.
+
+% \item Du musst zwei Sprachen beherrschen: \TeX{} und Lua.
+\item You need to know two languages: \TeX{} and Lua.
+\end{itemize}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+% \section{Wie das Paket geladen wird}
+\section{How the package is loaded}
+
+%%
+%
+%%
+
+\subsection{Using the Lua module \texttt{luakeys.lua}}
+
+% 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|.
+
+\InputLatex{loading/lua.tex}
+
+%%
+%
+%%
+
+\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 Datei \texttt{luakeys.sty} lädt das Lua-Modul in die globale
+% Variable \texttt{luakeys}.
+The \texttt{luakeys.sty} file loads the Lua module into the global
+variable \texttt{luakeys}.
+
+\InputLatex{loading/tex-latex.tex}
+
+%%
+%
+%%
+
+% \subsection{Verwendung des Plain-Lua\TeX{}-Hüllpakets
+% \texttt{luakeys.tex}}
+\subsection{Using the plain Lua\TeX{} wrapper \texttt{luakeys.tex}}
+
+\noindent
+% Es macht dasselbe wie das Lua\LaTeX{}-Hüllpacket und lädt das
+% Lua-Modul \texttt{luakeys.lua} in die globale Variable
+% \texttt{luakeys}.
+The file \texttt{luakeys.tex} does the same as the Lua\LaTeX{} wrapper
+and loads the Lua module \texttt{luakeys.lua} into the global variable
+\texttt{luakeys}.
+
+\InputLatex{loading/tex-plain.tex}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+\section{Lua interface / API}
+
+% Luakeys exportiert nur eine Funktion, die aufgerufen werden muss, um auf
+% die öffentliche API zuzugreifen
+Luakeys exports only one function that must be called to access the
+public API.
+%
+% Das Lua-Modul exportiert diese Funktionen und Tabellen:
+The Lua module exports this functions and tables:
+
+\InputLua[firstline=3,lastline=16]{export.lua}
+
+% 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.
+
+\InputLatex{functions/parse/tex-latex.tex}
+
+\noindent
+In plain \TeX:
+
+\InputLatex{functions/parse/tex-plain.tex}
+
+\subsection{Options to configure the \texttt{parse} function}
+
+\noindent
+% Die Funktion \lua{parse} kann mit einer Optionstabelle aufgerufen
+% werden.
+The \lua{parse} function can be called with an options table.
+% Diese Optionen werden unterstützt:
+This options are supported: \catcode`_=12
+\directlua{luakeys.print_names('opts')}
+
+\InputLua[firstline=7,lastline=99]{opts/all-opts.lua}
+
+\subsection{Table “\texttt{opts}”}
+\label{table-opts}
+
+\noindent
+% Die Optionen können auch global über die exportierte Tabelle
+% \lua{opts} gesetzt werden:
+The options can also be set globally using the exported table
+\lua{opts}:
+
+\InputLua[firstline=4,lastline=4]{opts/exported-default-opts.lua}
+
+\InputLua[firstline=10,lastline=11]{opts/exported-default-opts.lua}
+
+\noindent
+% Damit es zu keinen Wechselwirkungen mit anderen Paketen kommt, die auch
+% |luakeys| verwenden und die Optionen global setzen, ist es anzuraten,
+% die Funktion \lua{get_private_instance()} zum Laden das Paket verwenden.
+To avoid interactions with other packages that also use |luakeys| and
+set the options globally, it is recommended to use the
+\lua{get_private_instance()} function
+(\ref{function:get-private-instance}) to load the package.
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{accumulated_result}”}
+
+% Streng genommen handelt es sich hier um keine Option
+Strictly speaking, this is not an option.
+%
+% Mit der Option \lua{accumulated_result} kann eine Ergebnistabelle
+% angegeben werden, die bei jedem Aufruf der \lua{parse}-Funktion weiter
+% befüllt wird.
+The \lua{accumulated_result} “option” can be used to specify a result
+table that is filled with each call of the \lua{parse} function.
+
+\InputLua[firstline=5,lastline=14]{opts/accumulated-result.lua}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{assignment_operator}”}
+\label{option:assignment-operator}
+\label{options-delimiter}
+
+The option \lua{assignment_operator} configures the delimiter that
+assigns a value to a key. The default value of this option is
+\texttt{"="}.
+
+The code example below demonstrates all six delimiter related options.
+
+\InputLua[firstline=4,lastline=13]{opts/delimiters.lua}
+
+\begin{tabular}{ll}
+\textbf{Delimiter options} & \textbf{Section} \\
+assignment_operator & \ref{option:assignment-operator}\\
+group_begin & \ref{option:group-begin}\\
+group_end & \ref{option:group-end}\\
+list_separator & \ref{option:list-separator}\\
+quotation_begin & \ref{option:quotation-begin}\\
+quotation_end & \ref{option:quotation-end}\\
+\end{tabular}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{convert_dimensions}”}
+
+% Wenn Sie die Option \lua{convert_dimensions} auf \lua{true} setzen,
+% erkennt |luakeys| die \TeX{}-Dimensionen und konvertiert sie mit Hilfe
+% der die Funktion \lua{tex.sp(dim)} in scaled points.
+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)}.
+
+\InputLua[firstline=4,lastline=7]{opts/convert-dimensions/true.lua}
+
+\noindent
+% Standardmäßig werden die Dimensionen nicht in skalierte Punkte
+% umgewandelt.
+By default the dimensions are not converted into scaled points.
+
+\InputLua[firstline=4,lastline=9]{opts/convert-dimensions/false.lua}
+
+\noindent
+% Wenn Sie eine skalierte Punktzahl in einen Dimensionszeichenketten
+% umwandeln möchten, können Sie das Modul
+If you want to convert a scaled points number into a dimension 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}
+
+\DefaultOptDescription{convert_dimensions}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{debug}”}
+
+% Wenn die Option \lua{debug} auf true gesetzt ist, wird die
+% Ergebnistabelle in der Konsole ausgegeben.
+If the option \lua{debug} is set to true, the result table is printed to
+the console.
+
+\InputLatex{opts/debug-latex.tex}
+
+\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}
+
+\DefaultOptDescription{debug}
+
+%%
+%
+%%
+
+\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.
+
+\InputLua[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}.
+
+\InputLua[firstline=11,lastline=12]{opts/default.lua}
+
+\DefaultOptDescription{default}
+
+%%
+%
+%%
+
+\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.
+
+\InputLua[firstline=4,lastline=7]{opts/defaults.lua}
+
+\DefaultOptDescription{defaults}
+
+%%
+%
+%%
+
+\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 ...
+
+\InputLua[firstline=4,lastline=5]{opts/defs.lua}
+
+\noindent
+% können wir schreiben ..
+we can write ...
+
+\InputLua[firstline=11,lastline=13]{opts/defs.lua}
+
+\DefaultOptDescription{defs}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{false_aliases}”}
+\label{option:false-aliases}
+
+% Mit den den Optionen \lua{true_aliases} and \lua{false_aliases} können
+% die Zeichenketten festgelegt werden, die vom Parser als Wahrheitswerte
+% erkannt werden.
+The \lua{true_aliases} and \lua{false_aliases} options can be used to
+specify the strings that will be recognized as boolean values by the
+parser.
+% Standardmäßig sind folgende Zeichenketten konfiguriert
+The following strings are configured by default.
+
+\InputLua[firstline=4,lastline=8]{opts/boolean-aliases.lua}
+
+\InputLua[firstline=14,lastline=18]{opts/boolean-aliases.lua}
+
+\InputLua[firstline=24,lastline=28]{opts/boolean-aliases.lua}
+
+% Siehe Abschnitt \label{option:true-aliases} für die entsprechende
+% Option.
+See section \ref{option:true-aliases} for the corresponding option.
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{format_keys}”}
+
+% Mit Hilfe der Option \lua{format_keys} können die Schlüssel formatiert
+% werden.
+With the help of the option \lua{format_keys} the keys can be formatted.
+% Die Werte dieser Option müssen in einer Tabelle angegeben werden.
+The values of this option must be specified in a table.
+
+\begin{description}
+\item[lower]
+
+% Um alle Schlüssel in \emph{Kleinbuchstaben} umzuwandeln, geben sie in
+% der Optionentabelle \lua{lower} an.
+To convert all keys to \emph{lowercase}, specify \lua{lower} in the
+options table.
+
+\InputLua[firstline=4,lastline=5]{opts/format-keys.lua}
+
+\item[snake]
+
+% Um alle Schlüssel in \emph{snake case} (Die Wörter sind durch
+% Unterstriche getrennt) umzuwandeln, geben sie in der Optionentabelle
+% \lua{snake} an.
+To make all keys \emph{snake case} (The words are separated by
+underscores), specify \lua{snake} in the options
+table.
+
+\InputLua[firstline=11,lastline=12]{opts/format-keys.lua}
+
+\item[upper]
+
+% Um alle Schlüssel in \emph{Grossbuchstaben} umzuwandeln, geben sie in
+% der Optionentabelle \lua{upper} an.
+To convert all keys to \emph{uppercase}, specify \lua{upper} in the
+options table.
+
+\InputLua[firstline=18,lastline=19]{opts/format-keys.lua}
+\end{description}
+
+% Sie können auch mehrere Formatierungsarten kombinieren.
+You can also combine several types of formatting.
+
+\InputLua[firstline=25,lastline=26]{opts/format-keys.lua}
+
+\DefaultOptDescription{format_keys}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{group_begin}”}
+\label{option:group-begin}
+
+The option \lua{group_begin} configures the delimiter that marks the
+beginning of a group. The default value of this option is \texttt{"\{"}.
+A code example can be found in section \ref{options-delimiter}.
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{group_end}”}
+\label{option:group-end}
+
+The option \lua{group_end} configures the delimiter that marks the end
+of a group. The default value of this option is \texttt{"\}"}. A code
+example can be found in section \ref{options-delimiter}.
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{invert_flag}”}
+
+% Wird ein nackter Schlüssel mit einem vorangestellten Ausrufezeichen versehen, so wird sein Standardwert invertiert.
+If a naked key is prefixed with an exclamation mark, its default value
+is inverted.
+% Statt \lua{true} nimmt der Schlüssel jetzt den Wert \lua{falsch} an.
+Instead of \lua{true} the key now takes the value \lua{false}.
+
+\InputLua[firstline=4,lastline=5]{opts/invert-flat.lua}
+
+\noindent
+% Mit der Option \lua{invert_flag} kann dieses Invertierungszeichen
+% geändert werden.
+The \lua{invert_flag} option can be used to change this inversion
+character.
+
+\InputLua[firstline=11,lastline=12]{opts/invert-flat.lua}
+
+\noindent
+% Ist der Standardwert für nackte Schlüssel beispielweise auf \lua{false}
+% gesetzt, so nehmen die mit dem Umkehrungszeichen versehenen nackten
+% Schlüssel den Wert \lua{true} an.
+For example, if the default value for naked keys is set to \lua{false},
+the naked keys prefixed with the invert flat take the value \lua{true}.
+
+\InputLua[firstline=18,lastline=19]{opts/invert-flat.lua}
+
+\noindent
+% Setzen sie die Option \lua{invert_flag} auf \lua{false}, um diese
+% automatische Wertumkehrung zu deaktivieren.
+Set the \lua{invert_flag} option to \lua{false} to disable this
+automatic boolean value inversion.
+
+\InputLua[firstline=25,lastline=26]{opts/invert-flat.lua}
+
+%%
+%
+%%
+
+\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.
+
+\InputLua[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.
+
+\InputLua[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.
+
+\InputLua[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{list_separator}”}
+\label{option:list-separator}
+
+The option \lua{list_separator} configures the delimiter that separates
+list items from each other. The default value of this option is
+\texttt{","}. A code example can be found in section
+\ref{options-delimiter}.
+
+%%
+%
+%%
+
+\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.
+
+\InputLua[firstline=4,lastline=5]{opts/naked-as-value.lua}
+
+\noindent
+If we set the option \lua{naked_as_value} to \lua{true}:
+
+\InputLua[firstline=11,lastline=14]{opts/naked-as-value.lua}
+
+\DefaultOptDescription{naked_as_value}
+
+%%
+%
+%%
+
+\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.
+
+\InputLua[firstline=5,lastline=6]{opts/no-error.lua}
+
+\noindent
+If we set the option \lua{no_error} to \lua{true}:
+
+\InputLua[firstline=9,lastline=10]{opts/no-error.lua}
+
+\DefaultOptDescription{no_error}
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{quotation_begin}”}
+\label{option:quotation-begin}
+
+The option \lua{quotation_begin} configures the delimiter that marks the
+beginning of a string. The default value of this option is
+\texttt{'"'} (double quotes). A code example can be found in section
+\ref{options-delimiter}.
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{quotation_end}”}
+\label{option:quotation-end}
+
+The option \lua{quotation_end} configures the delimiter that marks the
+end of a string. The default value of this option is \texttt{'"'}
+(double quotes). A code example can be found in section
+\ref{options-delimiter}.
+
+%%
+%
+%%
+
+\subsubsection{Option “\texttt{true_aliases}”}
+\label{option:true-aliases}
+
+See section \ref{option:false-aliases}.
+
+%%
+%
+%%
+
+\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 a single naked key or a single standalone value are unpacked.
+
+\InputLua[firstline=4,lastline=5]{opts/unpack.lua}
+
+\InputLua[firstline=11,lastline=12]{opts/unpack.lua}
+
+\DefaultOptDescription{unpack}
+
+%%
+%
+%%
+
+\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}
+
+\InputLua[firstline=4,lastline=16]{functions/define.lua}
+
+\noindent
+% Bei verschachtelten Definitionen können nur die letzten beiden
+% Möglichkeiten zur Angabe der Schlüsselnamen verwendet werden.
+For nested definitions, only the last two ways of specifying the key
+names can be used.
+
+\InputLua[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.
+%
+% Diese Attribute sind erlaubt.
+These attributes are allowed:
+\directlua{luakeys.print_names('attrs')}.
+%
+% 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.
+
+\InputLua[firstline=5,lastline=62]{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 ...
+
+\InputLua[firstline=4,lastline=7]{defs/attrs/alias.lua}
+
+\noindent
+multiple aliases by a list of strings.
+
+\InputLua[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.
+
+\InputLua[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.
+
+\InputLua[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.
+
+\InputLua[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:
+
+\InputLua[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'},
+\lua{'list'}.
+%
+% 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.
+
+\InputLua[firstline=4,lastline=8]{defs/attrs/data-type.lua}
+\InputLua[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.
+
+\InputLua[firstline=4,lastline=9]{defs/attrs/default.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{description}”}
+
+% Dieses Attribut wird momentan nicht weiterverarbeitet.
+This attribute is currently not processed further.
+% Es kann als Kommentar dienen.
+It can serve as a comment.
+
+%%
+%
+%%
+
+\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.
+
+\InputLua[firstline=4,lastline=9]{defs/attrs/exclusive-group.lua}
+
+\noindent
+% 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.
+
+\InputLua[firstline=21,lastline=23]{defs/attrs/exclusive-group.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 zuerst der Lua Funktion
+% \lua{string.match(value, match)} übergeben, bevor er dem Schlüssel
+% zugewiesen wird.
+The value of the key is first passed to the Lua function
+\lua{string.match(value, match)}
+(\url{http://www.lua.org/manual/5.3/manual.html#pdf-string.match})
+before being assigned to the key.
+%
+% Du kannst das Attribut \lua{match} deshalb mit einer Pattern-Matching-
+% Zeichenkette konfigurieren, wie sie in Lua zu Einsatz kommt.
+You can therefore configure the \lua{match} attribute with a pattern
+matching string used in Lua.
+%
+% 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}).
+
+\InputLua[firstline=4,lastline=7]{defs/attrs/match/birthday.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.
+
+\InputLua[firstline=15,lastline=18]{defs/attrs/match/birthday.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.
+%
+% Im nächsten Beispiel wird der gesamte Eingabewert akzeptiert:
+In the next example, the entire input value is accepted:
+
+\InputLua[firstline=4,lastline=5]{defs/attrs/match/year.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.
+
+\InputLua[firstline=11,lastline=11]{defs/attrs/match/year.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 ...
+
+\InputLua[firstline=4,lastline=8]{defs/attrs/name/as-key.lua}
+
+\noindent
+% ... können wir schreiben:
+... we can write:
+
+\InputLua[firstline=4,lastline=8]{defs/attrs/name/name-attr.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}.
+
+\InputLua[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}.
+
+\InputLua[firstline=13,lastline=13]{defs/attrs/opposite-keys.lua}
+
+\noindent
+% Gegensätzliche Schlüsselpaare können in einer Kurzschreibweise
+% angegeben werden, nämlich als Liste
+Opposing key pairs can be specified in a short form, namely as a list:
+% Der gegensätzliche Schlüssel, der den Wahrwert repräsentiert, muss in
+% dieser Liste zuerst angegeben werden, dann folgt der Falschwert.
+The opposite key, which represents the true value, must be specified
+first in this list, followed by the false value.
+
+\InputLua[firstline=19,lastline=21]{defs/attrs/opposite-keys.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{pick}”}
+
+% Das Attribut \lua{pick} sucht nach einem Wert, der keinem Schlüssel
+% zugeordnet ist.
+The attribute \lua{pick} searches for a value not assigned to a key.
+% Der zuerst gefundene Wert, d. h. der weiter links stehende Wert, wird
+% einem Schlüssel zugewiesen.
+The first value found, i.e. the one further to the left, is assigned to
+a key.
+
+\InputLua[firstline=4,lastline=6]{defs/attrs/pick/dimension.lua}
+
+\noindent
+% Es wird nur in der aktuellen Ergebnistabelle gesucht und nicht auf
+% anderen Ebenen in der rekursiven Datenstruktur.
+Only the current result table is searched, not other levels in the
+recursive data structure.
+
+\InputLua[firstline=4,lastline=11]{defs/attrs/pick/different-levels.lua}
+
+\noindent
+% Die Suche nach Werte wird aktiviert, wenn das Attribut \lua{pick} auf
+% \lua{true} gesetzt wird.
+The search for values is activated when the attribute \lua{pick} is set
+to a data type.
+% Mit diesen Datentypen kann nach Werten gesucht werden
+These data types can be used to search for values:
+string, number, dimension, integer, boolean, any.
+% Verwendet den Datentyp any um jeden beliebigen Wert zu akzeptieren.
+Use the data type “any” to accept any value.
+% Wird einem Schlüssel bereits bei der Eingabe ein Wert zugewiesen, dann
+% wird nicht weiter nach Werten gesucht.
+If a value is already assigned to a key when it is entered, then no
+further search for values is performed.
+\InputLua[firstline=4,lastline=8]{defs/attrs/pick/value-set.lua}
+
+\noindent
+% Das Attribut \lua{pick} akzeptiert auch mehrere Datentypen, die in
+% einer Tabelle angegeben werden.
+The \lua{pick} attribute also accepts multiple data types
+specified in a table.
+
+\InputLua[firstline=4,lastline=10]{defs/attrs/pick/multiple-data-types.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{process}”}
+\label{attr-process}
+
+% Das Attribut \lua{process} kann dazu verwendet werden, um eine Funktion
+% zu definieren, deren Rückgabewert an den Schlüssel übergeben wird.
+The \lua{process} attribute can be used to define a function whose
+return value is passed to the key.
+%
+% Beim Aufruf der Funktion werden vier Parameter übergeben:
+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}
+
+\noindent
+% Das folgende Beispiel demonstriert den Parameter \lua{value}:
+The following example demonstrates the \lua{value} parameter:
+\InputLua[firstline=4,lastline=14]{defs/attrs/process/parameter-value.lua}
+
+\noindent
+% Das folgende Beispiel demonstriert den Parameter \lua{input}:
+The following example demonstrates the \lua{input} parameter:
+
+\InputLua[firstline=4,lastline=16]{defs/attrs/process/parameter-input.lua}
+
+\noindent
+% Das folgende Beispiel demonstriert den Parameter \lua{result}:
+The following example demonstrates the \lua{result} parameter:
+
+\InputLua[firstline=4,lastline=12]{defs/attrs/process/parameter-result.lua}
+
+\noindent
+% Das folgende Beispiel demonstriert den Parameter \lua{unknown}:
+The following example demonstrates the \lua{unknown} parameter:
+
+\InputLua[firstline=4,lastline=11]{defs/attrs/process/parameter-unknown.lua}
+\InputLua[firstline=15,lastline=15]{defs/attrs/process/parameter-unknown.lua}
+
+%%
+%
+%%
+
+\subsubsection{Attribute “\texttt{required}”}
+
+% Durch das Attribut \lua{required} kann erzwungen werden, dass ein
+% bestimmter Schlüssel angegeben werden muss.
+The \lua{required} attribute can be used to enforce that a specific key
+must be specified.
+%
+% Im untenstehenden Beispiel wird der Schlüssel \lua{important} als
+% zwingend notwendig definiert.
+In the example below, the key \lua{important} is defined as mandatory.
+
+\InputLua[firstline=4,lastline=5]{defs/attrs/required.lua}
+
+\noindent
+% Fehlt der Schlüssel \lua{important} in der Eingabe, so tritt eine
+% Fehlermeldung auf.
+If the key \lua{important} is missing in the input, an error message
+occurs.
+
+\InputLua[firstline=13,lastline=14]{defs/attrs/required.lua}
+
+\noindent
+A recursive example:
+
+\InputLua[firstline=18,lastline=23]{defs/attrs/required.lua}
+
+\noindent
+% Der Schlüssel \lua{important2} auf der Ebene 2 fehlt.
+The \lua{important2} key on level 2 is missing.
+
+\InputLua[firstline=29,lastline=30]{defs/attrs/required.lua}
+
+\noindent
+% Der Schlüssel \lua{important1} auf der untersten Schlüssel-Ebene fehlt.
+The \lua{important1} key at the lowest key level is missing.
+
+\InputLua[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.
+
+\InputLua[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.
+
+\InputLua[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.
+
+\InputLua[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.
+
+\InputLua[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}”}
+
+% In der Tabelle \lua{is} werden einige Funktionen zusammengefasst, die
+% überprüft ob eine Eingabe einem bestimmen Datentyp entspricht.
+In the table \lua{is} some functions are summarized, which check whether
+an input corresponds to a certain data type.
+%
+% Einige Funktionen akzeptieren nicht nur die entsprechenden
+% Lua-Datentypen, sondern auch Eingaben als Zeichenketten.
+Some functions accept not only the corresponding Lua data types, but also
+input as strings.
+%
+% Beispielsweise wird die Zeichenkette \lua{'true'} von der
+% \lua{is.boolean()}-Funktion als Wahrheitswert erkannt.
+For example, the string \lua{'true'} is recognized by the
+\lua{is.boolean()} function as a boolean value.
+
+\subsubsection{Function “\texttt{is.boolean(value): boolean}”}
+\InputLua[firstline=6,lastline=23]{functions/is/boolean.lua}
+
+\subsubsection{Function “\texttt{is.dimension(value): boolean}”}
+\InputLua[firstline=6,lastline=16]{functions/is/dimension.lua}
+
+\subsubsection{Function “\texttt{is.integer(value): boolean}”}
+\InputLua[firstline=6,lastline=11]{functions/is/integer.lua}
+
+\subsubsection{Function “\texttt{is.number(value): boolean}”}
+\InputLua[firstline=6,lastline=13]{functions/is/number.lua}
+
+\subsubsection{Function “\texttt{is.string(value): boolean}”}
+\InputLua[firstline=6,lastline=12]{functions/is/string.lua}
+
+\subsubsection{Function “\texttt{is.list(value): boolean}”}
+\InputLua[firstline=6,lastline=16]{functions/is/list.lua}
+
+\subsubsection{Function “\texttt{is.any(value): boolean}”}
+
+% Die Funktion \lua{is.any(value)}  gibt immer wahr zurück und
+% akzeptiert deshalb jeden Datentyp.
+The function \lua{is.any(value)} always returns \lua{true} and
+therefore accepts any data type.
+
+%%
+%
+%%
+
+\subsection{Table “\texttt{utils}”}
+
+% In der Tabelle \lua{utils} sind einige Hilfsfunktionen gebündelt.
+The \lua{utils} table bundles some auxiliary functions.
+
+\InputLua[firstline=3,lastline=34]{utils/all.lua}
+
+%%
+%
+%%
+
+\subsubsection{Function “\texttt{utils.merge_tables(target, source, overwrite): table}”}
+
+% Die Funktion \lua{merge_tables} führt zwei Tabellen in die erste
+% angegebene Tabelle zusammen. Sie kopiert Schlüssel aus der
+% `source` Tabelle in die `target`-Tabelle. Sie gibt die geänderte
+% Zieltabelle zurück.
+The function \lua{merge_tables} merges two tables into the first
+specified table. It copies keys from the `source` table into the
+`target` table. It returns the target table.
+
+% Wird der Parameter \lua{overwrite} auf wahr gesetzten, so werden Werte
+% in der Zieltabelle überschrieben
+If the \lua{overwrite} parameter is set to \lua{true}, values in the
+target table are overwritten.
+
+\InputLua[firstline=4,lastline=8]{utils/merge-tables.lua}
+
+\noindent
+% Geben sie dem Parameter \lua{overwrite} den Wert \lua{false}, um Werte
+% in der Zieltabelle zu überschreiben.
+Give the parameter \lua{overwrite} the value \lua{false} to overwrite
+values in the target table.
+
+\InputLua[firstline=14,lastline=18]{utils/merge-tables.lua}
+
+%%
+%
+%%
+
+\subsubsection{Function “\texttt{utils.scan_oarg(initial_delimiter?, end_delimiter?): string}”}
+
+% Plain \TeX{} kennt keine optionalen Argumente (oarg).
+Plain \TeX{} does not know optional arguments (oarg).
+%
+% Die Funktion ermöglicht es nicht nur in \LaTeX{}, sondern auch in
+% Plain \TeX{} nach optionalen Argumenten zu suchen.
+The function \\ \lua{utils.scan_oarg(initial_delimiter?,
+end_delimiter?): string} allows to search for optional arguments not only
+in \LaTeX{} but also in Plain \TeX.
+%
+% Die Funktion basiert auf der Token-Bibliothek.
+The function uses the token library built into Lua\TeX{}.
+%
+% Die beiden Parameter \lua{initial_delimiter} und \lua{end_delimiter}
+% können weggelassen werden.
+The two parameters \lua{initial_delimiter} and \lua{end_delimiter} can
+be omitted.
+%
+% Dann werden eckige Klammern als Begrenzungszeichen angenommen.
+Then square brackets are assumed to be delimiters.
+%
+% Dieser Lua-Code \lua{utils.scan_oarg('(', ')')} sucht beispielsweise
+% nach an einem optionalen Argument in runden Klammern.
+For example, this Lua code \lua{utils.scan_oarg('(', ')')} searches for
+an optional argument in round brackets
+%
+% Die Funktion gibt die Zeichenkette zwischen den Begrenzungszeichen
+% zurück, oder nil wenn Begrenzungszeichen gefunden werden konnten.
+The function returns the string between the delimiters or \lua{nil} if
+no delimiters could be found.
+%
+% Die Begrenzungszeichen sind im Ergebnis nicht enthalten.
+The delimiters themselves are not included in the result.
+%
+% Nach dem \latex{\directlua{}} darf das Makro, indem
+% \lua{utils.scan_oarg} eingesetzt wird, zu keinen Zeichen expandieren.
+After the \latex{\directlua{}}, the macro using \lua{utils.scan_oarg}
+must not expand to any characters.
+
+\InputLatex{utils/scan-oarg-plain.tex}
+
+%%
+%
+%%
+
+\subsection{Table “version”}
+
+% Das luakeys Projekt verwendet semantic versioning
+The luakeys project uses semantic versioning.
+% Die drei Versionenzahlen des Semantic Versioning Schemas werden in
+% einer Tabelle in der Reihenfolge MAJOR, MINOR, PATCH als Ganzzahlen
+% abgelegt.
+The three version numbers of the semantic versioning scheme are stored
+in a table as integers in the order MAJOR, MINOR, PATCH.
+% Mit Hilfe dieser Tabelle kann überprüft werden, ob die richtige
+% Version installiert ist.
+This table can be used to check whether the correct version is
+installed.
+
+\InputLua[firstline=4,lastline=10]{version.lua}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\section{Syntax of the recognized key-value format}
+
+%%
+%
+%%
+
+\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 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\}\}}).
+
+%%
+%
+%%
+
+\subsection{An (incomplete) attempt to put the syntax into the Extended Backus-Naur Form}
+
+\begin{grammar}
+<list> ::= \{ <list-item> \}
+
+<list-container> ::= `{' <list> `}'
+
+<list-item> ::= ( <list-container> | <key-value-pair> | <value> ) [ `,' ]
+
+<key-value-pair> ::= <value> `=' ( <list-container> | <value> )
+
+<value> ::= <boolean>
+  \alt <dimension>
+  \alt <number>
+  \alt <string-quoted>
+  \alt <string-unquoted>
+
+<dimension> ::= <number> <unit>
+
+<number> ::= <sign> ( <integer> [ <fractional> ] | <fractional> )
+
+<fractional> ::= `.' <integer>
+
+<sign> ::= `-' | `+'
+
+<integer> ::= <digit> \{ <digit> \}
+
+<digit> ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9'
+
+<unit> ::= `bp' | `BP'
+  \alt `cc' | `CC'
+  \alt `cm' | `CM'
+  \alt `dd' | `DD'
+  \alt `em' | `EM'
+  \alt `ex' | `EX'
+  \alt `in' | `IN'
+  \alt `mm' | `MM'
+  \alt `mu' | `MU'
+  \alt `nc' | `NC'
+  \alt `nd' | `ND'
+  \alt `pc' | `PC'
+  \alt `pt' | `PT'
+  \alt `px' | `PX'
+  \alt `sp' | `SP'
+
+<boolean> ::= <boolean-true> | <boolean-false>
+
+<boolean-true> ::= `true' | `TRUE' | `True'
+
+<boolean-false> ::= `false' | `FALSE' | `False'
+\end{grammar}
+
+... to be continued
+
+%%
+%
+%%
+
+\subsection{Recognized data types}
+
+\subsubsection{boolean}
+
+The strings \texttt{true}, \texttt{TRUE} and \texttt{True} are converted
+into Lua’s boolean type \lua{true}, the strings \texttt{false},
+\texttt{FALSE} and \texttt{False} into \lua{false}.
+
+\begin{multicols}{2}
+\InputLatex[firstline=5,lastline=12]{luakeysdebug/boolean-latex.tex}
+
+\columnbreak
+
+\begin{minted}{lua}
+{
+  ['lower case true'] = true,
+  ['upper case true'] = true,
+  ['title case true'] = true,
+  ['lower case false'] = false,
+  ['upper case false'] = false
+  ['title case false'] = false,
+}
+\end{minted}
+\end{multicols}
+
+%%
+%
+%%
+
+\subsubsection{number}
+
+\begin{multicols}{2}
+\InputLatex[firstline=5,lastline=13]{luakeysdebug/number-latex.tex}
+
+\columnbreak
+
+\begin{minted}{lua}
+{
+  ['num0'] = 42,
+  ['num1'] = 42,
+  ['num2'] = -42,
+  ['num3'] = 4.2,
+  ['num4'] = 0.42,
+  ['num5'] = 0.42,
+  ['num6'] = '0 . 42', -- string
+}
+\end{minted}
+\end{multicols}
+
+%%
+%
+%%
+
+\subsubsection{dimension}
+
+% Luakeys versucht alle Einheiten zu erkennen, die in der TeX-Welt
+% verwendet werden.
+|luakeys| tries to recognize all units used in the \TeX{} world.
+%
+% Nach dem Lua\TeX-Quellcode .
+According to the Lua\TeX{} source code
+(\href{https://github.com/TeX-Live/luatex/blob/51db1985f5500dafd2393aa2e403fefa57d3cb76/source/texk/web2c/luatexdir/lua/ltexlib.c#L434-L625}
+{source/texk/web2c/luatexdir/lua/ltexlib.c})
+%
+% und nach dem Dimensionen-Modul der lulibs-Bibliothek
+and the dimension module of the lualibs library
+(\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua}
+{lualibs-util-dim.lua}),
+%
+% müssten alle Einheiten erkannt werden
+all units should be recognized.
+\begin{multicols}{3}
+\tiny
+\begin{center}
+\begin{tabular}{rl}
+% \textbf{Unit name}
+& \textbf{Description} \\\hline
+bp & big point \\
+cc & cicero \\
+cm & centimeter \\
+dd & didot \\
+em & horizontal measure of \emph{M} \\
+ex & vertical measure of \emph{x} \\
+in & inch \\
+mm & milimeter \\
+mu & math unit \\
+nc & new cicero \\
+nd & new didot \\
+pc & pica \\
+pt & point \\
+px & x height current font \\
+sp & scaledpoint \\
+\end{tabular}
+\end{center}
+
+\columnbreak
+
+\InputLatex[firstline=5,lastline=21]{luakeysdebug/dimension/all-latex.tex}
+
+\columnbreak
+
+\begin{minted}{lua}
+{
+  ['bp'] = 65781,
+  ['cc'] = 841489,
+  ['cm'] = 1864679,
+  ['dd'] = 70124,
+  ['em'] = 655360,
+  ['ex'] = 282460,
+  ['in'] = 4736286,
+  ['mm'] = 186467,
+  ['mu'] = 65536,
+  ['nc'] = 839105,
+  ['nd'] = 69925,
+  ['pc'] = 786432,
+  ['pt'] = 65536,
+  ['px'] = 65781,
+  ['sp'] = 1,
+}
+\end{minted}
+\end{multicols}
+
+\noindent
+% Im nächsten Beispiel werden die unterschiedlichen Notationsformen der
+% Dimensionen illustriert.
+The next example illustrates the different notations of the dimensions.
+
+\begin{multicols}{2}
+\InputLatex[firstline=5,lastline=12]{luakeysdebug/dimension/notations-latex.tex}
+
+\columnbreak
+
+\begin{minted}{lua}
+{
+  ['upper'] = 1864679,
+  ['lower'] = 1864679,
+  ['space'] = 1864679,
+  ['plus'] = 1864679,
+  ['minus'] = -1864679,
+  ['nodim'] = '1 c m', -- string
+}
+\end{minted}
+\end{multicols}
+
+%%
+%
+%%
+
+\subsubsection{string}
+
+% 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.
+
+\InputLua[firstline=4,lastline=17]{data-types/string.lua}
+
+\subsubsection{Naked keys}
+
+% 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).
+
+\InputLatex[firstline=5,lastline=12]{luakeysdebug/naked-keys-latex.tex}
+
+\noindent
+% Alle erkannten Datentypen können als eigenständige Werte verwendet
+% werden.
+All recognized data types can be used as standalone values.
+
+\InputLatex[firstline=14,lastline=19]{luakeysdebug/naked-keys-latex.tex}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Examples}
+
+%%
+%
+%%
+
+\subsection{Extend and modify keys of existing macros}
+
+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}
+
+\InputLua{extend-includegraphics/extend-keys.lua}
+\InputLatex{extend-includegraphics/extend-keys-latex.tex}
+
+%%
+%
+%%
+
+\subsection{Process document class options}
+
+% Auf die Optionen einer \LaTeX{} Dokumentenklasse kann über das Macro
+% \latex{\LuakeysGetClassOptions} zu gegriffen werden.
+The options of a \LaTeX{} document class can be accessed via the
+\latex{\LuakeysGetClassOptions} macro.
+%
+% \latex{\LuakeysGetClassOptions} ist ein anderer Name für
+\latex{\LuakeysGetClassOptions} is an alias for
+
+\begin{center}
+\latex{\luaescapestring{\@raw at classoptionslist}}.
+\end{center}
+
+\InputLatex{class-options/test-class.cls}
+\InputLatex{class-options/use-test-class-latex.tex}
+
+%%
+%
+%%
+
+\subsection{Process package options}
+
+\noindent
+% Auf die Optionen eines \LaTeX{}-Pakets kann über das Macro
+% \latex{\LuakeysGetPackageOptions} zu gegriffen werden.
+The options of a \LaTeX{} package can be accessed via the
+\latex{\LuakeysGetPackageOptions} macro.
+% \latex{\LuakeysGetPackageOptions} ist ein anderer Name für
+\latex{\LuakeysGetPackageOptions} is an alias for
+
+\begin{center}
+\latex{\luaescapestring{\@ptionlist{\@currname.\@currext}}}.
+\end{center}
+
+\InputLatex{package-options/test-package.sty}
+\InputLatex{package-options/use-test-package-latex.tex}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Debug packages}
+
+Two small debug packages are included in |luakeys|. One debug package
+can be used in \LaTeX{} (luakeys-debug.sty) and one can be used in plain
+\TeX{} (luakeys-debug.tex). Both packages provide only one command:
+|\luakeysdebug{kv-string}|
+
+\begin{minted}{latex}
+\luakeysdebug{one,two,three}
+\end{minted}
+
+\noindent
+Then the following output should appear in the document:
+\bigskip
+
+\luakeysdebug{one,two,three}
+
+%%
+%
+%%
+
+\subsection{For plain \TeX: luakeys-debug.tex}
+
+An example of how to use the command in plain \TeX:
+
+\begin{minted}{latex}
+\input luakeys-debug.tex
+\luakeysdebug{one,two,three}
+\bye
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{For \LaTeX: luakeys-debug.sty}
+
+An example of how to use the command in \LaTeX:
+
+\begin{minted}{latex}
+\documentclass{article}
+\usepackage{luakeys-debug}
+\begin{document}
+\luakeysdebug[
+  unpack=false,
+  convert dimensions=false
+]{one,two,three}
+\end{document}
+\end{minted}
+
+\section{Activity diagramm of the parsing process}
+
+\begin{center}
+\includegraphics[width=0.8\linewidth]{Activity-Diagramm.eps}
+\end{center}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Implementation}
+
+%%
+%
+%%
+
+\subsection{luakeys.lua}
+
+\inputminted[linenos=true]{lua}{luakeys.lua}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsection{luakeys.tex}
+
+\inputminted[linenos=true]{latex}{luakeys.tex}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsection{luakeys.sty}
+
+\inputminted[linenos=true]{latex}{luakeys.sty}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsection{luakeys-debug.tex}
+
+\inputminted[linenos=true]{latex}{luakeys-debug.tex}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsection{luakeys-debug.sty}
+
+\inputminted[linenos=true]{latex}{luakeys-debug.sty}
+
+\changes{v0.1.0}{2021/01/18}{Inital release}
+\changes{v0.2.0}{2021/09/19}{
+* Allow all recognized data types as keys.
+* Allow TeX macros in the values.
+* New public Lua functions: save(identifier, result), get(identifier).
+}
+\changes{v0.3.0}{2021/11/05}{
+* Add a LuaLaTeX wrapper “luakeys.sty”.
+* Add a plain LuaTeX wrapper “luakeys.tex”.
+* Rename the previous documentation file “luakeys.tex” to luakeys-doc.tex”.
+}
+\changes{v0.4.0}{2021/12/31}{
+* Parser: Add support for nested tables (for example {{'a', 'b'}}).
+* Parser: Allow only strings and numbers as keys.
+* Parser: Remove support from Lua numbers with exponents (for example '5e+20').
+* Switch the Lua testing framework to busted.
+}
+\changes{v0.5.0}{2022/04/04}{
+* Add possibility to change options globally.
+* New option: standalone_as_true.
+* Add a recursive converter callback / hook to process the parse tree.
+* New option: case_insensitive_keys.
+}
+\changes{v0.6.0}{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()”.
+}
+\changes{v0.7.0}{2022/07/06}{
+* The project now uses semantic versioning.
+* New definition attribute “pick” to pick standalone values and assign
+  them to a key.
+* New function “utils.scan_oarg()” to search for an optional argument,
+  that means scan for tokens that are enclosed in square brackets.
+* Extend and improve the documentation.
+}
+\changes{v0.8.0}{2022/11/17}{
+* Add 6 new options to change the delimiters: “assignment_operator”,
+  “group_begin”, “group_end”, “list_separator”, “quotation_begin”,
+  “quotation_end”.
+* Extend the documentation about the option “format_keys”.
+}
+\changes{v0.9.0}{2022/11/21}{
+* The definition attibute “pick” accepts a new data type: “any”.
+* The attribute value “true” for the attribute “pick” is deprecated.
+* The attribute “pick” accepts now multiple data types specified in
+  a table.
+* Add a new function called “any(value)” in the “is” table that accepts
+  any data type.
+}
+\changes{v0.10.0}{2022/12/16}{
+* Add support for an invert flat that flips the value of naked keys.
+* Add new options to specify which strings are recognized as Boolean
+  values.
+}
+\changes{v0.11.0}{2022/12/23}{
+* Add a new function called “get_private_instance()” to load a private
+  version of the luakeys module.
+}
+\changes{v0.12.0}{2023/01/05}{
+Added
+* Macros \cmd{\LuakeysGetPackageOptions}, \cmd{\LuakeysGetClassOptions}.
+* Option “accumulated_result”.
+* Data type “list” to the attribute “data_type”.
+* Attribute “description”.
+* Tables “utils.log” and “utils.ansi_color”.
+* Table “errors_message” to set custom messages.
+* Short form syntax for the definition attribute “opposite_keys”.
+Changed
+* Breaking change! luakeys exports now a function instead of a table.
+  Use “require('luakeys')()” or “luakeys.new()” instead of
+  “require('luakeys')”.
+* Breaking change! “luakeys.parse()”, “luakeys.define()”, “luakeys.save()”
+  and “luakeys.get()” can’t be used anymore from the global variable luakeys.
+* New name for the function “new()” instead of “get_private_instance()".
+}
+
+\pagebreak
+\PrintChanges
+\pagebreak
+\PrintIndex
+\end{document}


Property changes on: trunk/Master/texmf-dist/doc/luatex/luakeys/documentation.tex
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Deleted: trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys-doc.pdf
===================================================================
(Binary files differ)

Deleted: trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys-doc.tex
===================================================================
--- trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys-doc.tex	2023-01-05 00:50:44 UTC (rev 65467)
+++ trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys-doc.tex	2023-01-05 21:18:54 UTC (rev 65468)
@@ -1,2102 +0,0 @@
-\documentclass{ltxdoc}
-
-\EnableCrossrefs
-\CodelineIndex
-\RecordChanges
-
-\usepackage{mdframed}
-\usepackage{minted}
-\usepackage{luakeys}
-\usepackage{luakeys-debug}
-\usepackage{multicol}
-\usepackage{luacode}
-\usepackage{syntax}
-\usepackage{graphicx}
-
-\usemintedstyle{friendly}
-\BeforeBeginEnvironment{minted}{\begin{mdframed}}
-\AfterEndEnvironment{minted}{\end{mdframed}}
-\setminted{
-  breaklines=true,
-  fontsize=\footnotesize,
-  style=manni,
-}
-\def\lua#1{\mintinline{lua}|#1|}
-\def\latex#1{\mintinline{latex}|#1|}
-
-\NewDocumentCommand { \InputLatex } { O{} m } {
-  \begin{mdframed}
-  \inputminted[linenos=false,#1]{latex}{examples/#2}
-  \end{mdframed}
-}
-
-\NewDocumentCommand { \InputLua } { O{} m } {
-  \begin{mdframed}
-  \inputminted[linenos=false,#1]{lua}{examples/#2}
-  \end{mdframed}
-}
-
-\catcode`_=12
-\def\DefaultOpt#1{%
-  \texttt{\directlua{
-    tex.print(luakeys.print_default('opts', '#1'))
-  }}%
-}
-
-\def\DefaultOptDescription#1{
-\noindent
-The default value of the option “\texttt{#1}” is:
-\DefaultOpt{#1}.
-}
-
-\begin{document}
-
-\providecommand*{\url}{\texttt}
-
-\title{The \textsf{luakeys} package}
-\author{%
-  Josef Friedrich\\%
-  \url{josef at friedrich.rocks}\\%
-  \href{https://github.com/Josef-Friedrich/luakeys}
-       {github.com/Josef-Friedrich/luakeys}%
-}
-\date{0.11.0 from 2022/12/23}
-
-\maketitle
-
-\vfill
-
-\InputLua[firstline=4,lastline=7]{first-page.lua}
-
-\noindent
-Result:
-
-\begin{center}
-\begin{minted}{lua}
-{
-  ['level1'] = {
-    ['level2'] = {
-      ['naked'] = true,
-      ['dim'] = 1864679,
-      ['bool'] = false,
-      ['num'] = -0.001,
-      ['str'] = 'lua,{}',
-    }
-  }
-}
-\end{minted}
-\end{center}
-
-\vfill
-
-\strut
-
-\newpage
-
-\tableofcontents
-
-\newpage
-
-% \section{Einführung}
-\section{Introduction}
-
-\noindent
-% |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.
-%
-% |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.
-
-% Der Artikel TUGboat
-% \href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf}
-% {“Implementing key–value input: An introduction” (Volume 30 (2009), No.
-% 1)} von \emph{Joseph Wright} und \emph{Christian Feuersänger} gibt einen
-% einen guten Überblick über die verfügbaren Key-Value-Pakete.
-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 \emph{Joseph Wright} and \emph{Christian Feuersänger} gives a
-good overview of the available key-value packages.
-
-% Dieses Paket wäre ohne den Artikel
-% \href{https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf}
-% {"Parsing complex data formats in LuaTEX with LPEG" (Volume 40 (2019),
-% No. 2)} nicht möglich gewesen.
-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)}.
-
-\subsection{Pros of \texttt{luakeys}}
-
-\begin{itemize}
-% \item Schlüssel-Wert-Paare können unabhängig vom Makro-Paket (latex or
-% context) analysiert werden.
-\item Key-value pairs can be parsed independently of the macro
-collection (\LaTeX{} or Con\TeX{}t).
-
-% \item Sogar in Plain LuaTex können Schlüssel analysiert werden
-\item Even in plain Lua\TeX{} keys can be parsed.
-
-% \item |luakeys| kann mit ineinander verschachtelten listen an
-% Schlüssel-Wert-Paaren umgehen, d. h. es kann mit einer rekursiven
-% Datenstruktur an Schlüssel umgehen.
-\item |luakeys| can handle nested lists of key-value pairs, i.e. it can
-handle a recursive data structure of keys.
-
-% \item Schlüssel müssen nicht, aber können definiert werden.
-\item Keys do not have to be defined, but can they can be defined.
-\end{itemize}
-
-\subsection{Cons of \texttt{luakeys}}
-
-\begin{itemize}
-% \item Das Packet funktioniert nur in der Verbindung mit Lua\TeX.
-\item The package works only in combination with Lua\TeX.
-
-% \item Du musst zwei Sprachen beherrschen: \TeX{} und Lua.
-\item You need to know two languages: \TeX{} and Lua.
-\end{itemize}
-
-%-----------------------------------------------------------------------
-%
-%-----------------------------------------------------------------------
-
-% \section{Wie das Paket geladen wird}
-\section{How the package is loaded}
-
-%%
-%
-%%
-
-\subsection{Using the Lua module \texttt{luakeys.lua}}
-
-% 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|.
-
-\InputLatex{loading/lua.tex}
-
-%%
-%
-%%
-
-\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}
-\NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{luakeys}
-\directlua{luakeys = require('luakeys')}
-\end{minted}
-
-\noindent
-% Es lädt das Lua-Modul in die globale Variable \texttt{luakeys}.
-It loads the Lua module into the global variable \texttt{luakeys}.
-
-\InputLatex{loading/tex-latex.tex}
-
-%%
-%
-%%
-
-% \subsection{Verwendung des Plain-Lua\TeX{}-Hüllpakets
-% \texttt{luakeys.tex}}
-\subsection{Using the plain Lua\TeX{} wrapper \texttt{luakeys.tex}}
-
-% Noch kleiner ist die Datei \texttt{luakeys.tex}.
-Even smaller is the file \texttt{luakeys.tex}.
-%
-% Sie besteht aus nur einer Zeile:
-It consists of only one line:
-
-\begin{minted}{latex}
-\directlua{luakeys = require('luakeys')}
-\end{minted}
-
-\noindent
-% Es macht dasselbe wie das Lua\LaTeX{}-Hüllpacket und lädt das
-% Lua-Modul \texttt{luakeys.lua} in die globale Variable
-% \texttt{luakeys}.
-It does the same as the Lua\LaTeX{} wrapper and loads the Lua module
-\texttt{luakeys.lua} into the global variable \texttt{luakeys}.
-
-\InputLatex{loading/tex-plain.tex}
-
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-
-\section{Lua interface / API}
-
-% Das Lua-Modul exportiert diese Funktionen und Tabellen:
-The Lua module exports this functions and tables:
-
-\InputLua[firstline=3,lastline=15]{export.lua}
-
-\noindent
-% Diese Dokumentation stellt nur die öffentlichen Funktionen und
-% Tabellen vor.
-This documentation presents only the public functions and tables.
-%
-% Um mehr über die privaten, nicht exportierten Funktionen zu erfahren,
-% lesen Sie bitte die
-% \href{https://josef-friedrich.github.io/luakeys/}{Quellcode
-% Dokumentation}, die mit
-% \href{http://stevedonovan.github.io/ldoc/}{LDoc} erstellt wurde.
-To learn more about the private, not exported 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}.
-
-\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{get_private_instance(): table}”}
-\label{function:get-private-instance}
-
-% Über die Tabelle \lua{opts} können die  Standardoptionen global
-% geändert werden.
-The default options can be changed globally via the \lua{opts} table
-(\ref{table-opts}).
-% Um zu verhindern, dass andere Module die Standardeinstellungen
-% ändern, kann mit Hilfe der Funktion \lua{get_private_instance()} eine neue
-% Instanz des Moduls \lua{luakeys} erzeugt werden.
-To prevent another modules from changing the default settings, a new
-instance of the  \lua{luakeys} module can be created using the
-\lua{get_private_instance()} function.
-
-\InputLua[firstline=3,lastline=4]{functions/get-private-instance.lua}
-
-\noindent
-% Die Funktion \lua{require()} gibt standardmäßig beim mehrmaligen
-% Aufruf immer die gleiche Tabelle zurück.
-The \lua{require()} function always returns the same table
-when called multiple times.
-
-\InputLua[firstline=14,lastline=17]{functions/get-private-instance.lua}
-
-%%
-%
-%%
-
-\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.
-
-\InputLatex{functions/parse/tex-latex.tex}
-
-\noindent
-In plain \TeX:
-
-\InputLatex{functions/parse/tex-plain.tex}
-
-\subsection{Options to configure the \texttt{parse} function}
-
-\noindent
-% Die Funktion \lua{parse} kann mit einer Optionstabelle aufgerufen
-% werden.
-The \lua{parse} function can be called with an options table.
-% Diese Optionen werden unterstützt:
-This options are supported: \catcode`_=12
-\directlua{luakeys.print_names('opts')}
-
-\InputLua[firstline=5,lastline=94]{opts/all-opts.lua}
-
-\subsection{Table “\texttt{opts}”}
-\label{table-opts}
-
-\noindent
-% Die Optionen können auch global über die exportierte Tabelle
-% \lua{opts} gesetzt werden:
-The options can also be set globally using the exported table
-\lua{opts}:
-
-\InputLua[firstline=4,lastline=4]{opts/exported-default-opts.lua}
-
-\InputLua[firstline=10,lastline=11]{opts/exported-default-opts.lua}
-
-\noindent
-% Damit es zu keinen Wechselwirkungen mit anderen Paketen kommt, die auch
-% |luakeys| verwenden und die Optionen global setzen, ist es anzuraten,
-% die Funktion \lua{get_private_instance()} zum Laden das Paket verwenden.
-To avoid interactions with other packages that also use |luakeys| and
-set the options globally, it is recommended to use the
-\lua{get_private_instance()} function
-(\ref{function:get-private-instance}) to load the package.
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{assignment_operator}”}
-\label{option:assignment-operator}
-\label{options-delimiter}
-
-The option \lua{assignment_operator} configures the delimiter that
-assigns a value to a key. The default value of this option is
-\texttt{"="}.
-
-The code example below demonstrates all six delimiter related options.
-
-\InputLua[firstline=4,lastline=13]{opts/delimiters.lua}
-
-\begin{tabular}{ll}
-\textbf{Delimiter options} & \textbf{Section} \\
-assignment_operator & \ref{option:assignment-operator}\\
-group_begin & \ref{option:group-begin}\\
-group_end & \ref{option:group-end}\\
-list_separator & \ref{option:list-separator}\\
-quotation_begin & \ref{option:quotation-begin}\\
-quotation_end & \ref{option:quotation-end}\\
-\end{tabular}
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{convert_dimensions}”}
-
-% Wenn Sie die Option \lua{convert_dimensions} auf \lua{true} setzen,
-% erkennt |luakeys| die \TeX{}-Dimensionen und konvertiert sie mit Hilfe
-% der die Funktion \lua{tex.sp(dim)} in scaled points.
-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)}.
-
-\InputLua[firstline=4,lastline=7]{opts/convert-dimensions/true.lua}
-
-\noindent
-% Standardmäßig werden die Dimensionen nicht in skalierte Punkte
-% umgewandelt.
-By default the dimensions are not converted into scaled points.
-
-\InputLua[firstline=4,lastline=9]{opts/convert-dimensions/false.lua}
-
-\noindent
-% Wenn Sie eine skalierte Punktzahl in einen Dimensionszeichenketten
-% umwandeln möchten, können Sie das Modul
-If you want to convert a scaled points number into a dimension 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}
-
-\DefaultOptDescription{convert_dimensions}
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{debug}”}
-
-% Wenn die Option \lua{debug} auf true gesetzt ist, wird die
-% Ergebnistabelle in der Konsole ausgegeben.
-If the option \lua{debug} is set to ture, the result table is printed to
-the console.
-
-\InputLatex{opts/debug-latex.tex}
-
-\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}
-
-\DefaultOptDescription{debug}
-
-%%
-%
-%%
-
-\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.
-
-\InputLua[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}.
-
-\InputLua[firstline=11,lastline=12]{opts/default.lua}
-
-\DefaultOptDescription{default}
-
-%%
-%
-%%
-
-\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.
-
-\InputLua[firstline=4,lastline=7]{opts/defaults.lua}
-
-\DefaultOptDescription{defaults}
-
-%%
-%
-%%
-
-\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 ...
-
-\InputLua[firstline=4,lastline=5]{opts/defs.lua}
-
-\noindent
-% können wir schreiben ..
-we can write ...
-
-\InputLua[firstline=11,lastline=13]{opts/defs.lua}
-
-\DefaultOptDescription{defs}
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{false_aliases}”}
-\label{option:false-aliases}
-
-% Mit den den Optionen \lua{true_aliases} and \lua{false_aliases} können
-% die Zeichenketten festgelegt werden, die vom Parser als Wahrheitswerte
-% erkannt werden.
-The \lua{true_aliases} and \lua{false_aliases} options can be used to
-specify the strings that will be recognized as boolean values by the
-parser.
-% Standardmäßig sind folgende Zeichenketten konfiguriert
-The following strings are configured by default.
-
-\InputLua[firstline=4,lastline=8]{opts/boolean-aliases.lua}
-
-\InputLua[firstline=14,lastline=18]{opts/boolean-aliases.lua}
-
-\InputLua[firstline=24,lastline=28]{opts/boolean-aliases.lua}
-
-% Siehe Abschnitt \label{option:true-aliases} für die entsprechende
-% Option.
-See section \ref{option:true-aliases} for the corresponding option.
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{format_keys}”}
-
-% Mit Hilfe der Option \lua{format_keys} können die Schlüssel formatiert
-% werden.
-With the help of the option \lua{format_keys} the keys can be formatted.
-% Die Werte dieser Option müssen in einer Tabelle angegeben werden.
-The values of this option must be specified in a table.
-
-\begin{description}
-\item[lower]
-
-% Um alle Schlüssel in \emph{Kleinbuchstaben} umzuwandeln, geben sie in
-% der Optionentabelle \lua{lower} an.
-To convert all keys to \emph{lowercase}, specify \lua{lower} in the
-options table.
-
-\InputLua[firstline=4,lastline=5]{opts/format-keys.lua}
-
-\item[snake]
-
-% Um alle Schlüssel in \emph{snake case} (Die Wörter sind durch
-% Unterstriche getrennt) umzuwandeln, geben sie in der Optionentabelle
-% \lua{snake} an.
-To make all keys \emph{snake case} (The words are separated by
-underscores), specify \lua{snake} in the options
-table.
-
-\InputLua[firstline=11,lastline=12]{opts/format-keys.lua}
-
-\item[upper]
-
-% Um alle Schlüssel in \emph{Grossbuchstaben} umzuwandeln, geben sie in
-% der Optionentabelle \lua{upper} an.
-To convert all keys to \emph{uppercase}, specify \lua{upper} in the
-options table.
-
-\InputLua[firstline=18,lastline=19]{opts/format-keys.lua}
-\end{description}
-
-% Sie können auch mehrere Formatierungsarten kombinieren.
-You can also combine several types of formatting.
-
-\InputLua[firstline=25,lastline=26]{opts/format-keys.lua}
-
-\DefaultOptDescription{format_keys}
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{group_begin}”}
-\label{option:group-begin}
-
-The option \lua{group_begin} configures the delimiter that marks the
-beginning of a group. The default value of this option is \texttt{"\{"}.
-A code example can be found in section \ref{options-delimiter}.
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{group_end}”}
-\label{option:group-end}
-
-The option \lua{group_end} configures the delimiter that marks the end
-of a group. The default value of this option is \texttt{"\}"}. A code
-example can be found in section \ref{options-delimiter}.
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{invert_flag}”}
-
-% Wird ein nackter Schlüssel mit einem vorangestellten Ausrufezeichen versehen, so wird sein Standardwert invertiert.
-If a naked key is prefixed with an exclamation mark, its default value
-is inverted.
-% Statt \lua{true} nimmt der Schlüssel jetzt den Wert \lua{falsch} an.
-Instead of \lua{true} the key now takes the value \lua{false}.
-
-\InputLua[firstline=4,lastline=5]{opts/invert-flat.lua}
-
-\noindent
-% Mit der Option \lua{invert_flag} kann dieses Invertierungszeichen
-% geändert werden.
-The \lua{invert_flag} option can be used to change this inversion
-character.
-
-\InputLua[firstline=11,lastline=12]{opts/invert-flat.lua}
-
-\noindent
-% Ist der Standardwert für nackte Schlüssel beispielweise auf \lua{false}
-% gesetzt, so nehmen die mit dem Umkehrungszeichen versehenen nackten
-% Schlüssel den Wert \lua{true} an.
-For example, if the default value for naked keys is set to \lua{false},
-the naked keys prefixed with the invert flat take the value \lua{true}.
-
-\InputLua[firstline=18,lastline=19]{opts/invert-flat.lua}
-
-\noindent
-% Setzen sie die Option \lua{invert_flag} auf \lua{false}, um diese
-% automatische Wertumkehrung zu deaktivieren.
-Set the \lua{invert_flag} option to \lua{false} to disable this
-automatic boolean value inversion.
-
-\InputLua[firstline=25,lastline=26]{opts/invert-flat.lua}
-
-%%
-%
-%%
-
-\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.
-
-\InputLua[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.
-
-\InputLua[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.
-
-\InputLua[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{list_separator}”}
-\label{option:list-separator}
-
-The option \lua{list_separator} configures the delimiter that separates
-list items from each other. The default value of this option is
-\texttt{","}. A code example can be found in section
-\ref{options-delimiter}.
-
-%%
-%
-%%
-
-\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.
-
-\InputLua[firstline=4,lastline=5]{opts/naked-as-value.lua}
-
-\noindent
-If we set the option \lua{naked_as_value} to \lua{true}:
-
-\InputLua[firstline=11,lastline=14]{opts/naked-as-value.lua}
-
-\DefaultOptDescription{naked_as_value}
-
-%%
-%
-%%
-
-\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.
-
-\InputLua[firstline=5,lastline=6]{opts/no-error.lua}
-
-\noindent
-If we set the option \lua{no_error} to \lua{true}:
-
-\InputLua[firstline=9,lastline=10]{opts/no-error.lua}
-
-\DefaultOptDescription{no_error}
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{quotation_begin}”}
-\label{option:quotation-begin}
-
-The option \lua{quotation_begin} configures the delimiter that marks the
-beginning of a string. The default value of this option is
-\texttt{'"'} (double quotes). A code example can be found in section
-\ref{options-delimiter}.
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{quotation_end}”}
-\label{option:quotation-end}
-
-The option \lua{quotation_end} configures the delimiter that marks the
-end of a string. The default value of this option is \texttt{'"'}
-(double quotes). A code example can be found in section
-\ref{options-delimiter}.
-
-%%
-%
-%%
-
-\subsubsection{Option “\texttt{true_aliases}”}
-\label{option:true-aliases}
-
-See section \ref{option:false-aliases}.
-
-%%
-%
-%%
-
-\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 a single naked key or a single standalone value are unpacked.
-
-\InputLua[firstline=4,lastline=5]{opts/unpack.lua}
-
-\InputLua[firstline=11,lastline=12]{opts/unpack.lua}
-
-\DefaultOptDescription{unpack}
-
-%%
-%
-%%
-
-\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}
-
-\InputLua[firstline=4,lastline=16]{functions/define.lua}
-
-\noindent
-% Bei verschachtelten Definitionen können nur die letzten beiden
-% Möglichkeiten zur Angabe der Schlüsselnamen verwendet werden.
-For nested definitions, only the last two ways of specifying the key
-names can be used.
-
-\InputLua[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.
-%
-% Diese Attribute sind erlaubt.
-These attributes are allowed:
-\directlua{luakeys.print_names('attrs')}.
-%
-% 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.
-
-\InputLua[firstline=5,lastline=46]{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 ...
-
-\InputLua[firstline=4,lastline=7]{defs/attrs/alias.lua}
-
-\noindent
-multiple aliases by a list of strings.
-
-\InputLua[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.
-
-\InputLua[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.
-
-\InputLua[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.
-
-\InputLua[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:
-
-\InputLua[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.
-
-\InputLua[firstline=4,lastline=8]{defs/attrs/data-type.lua}
-\InputLua[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.
-
-\InputLua[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.
-
-\InputLua[firstline=4,lastline=9]{defs/attrs/exclusive-group.lua}
-
-\noindent
-% 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.
-
-\InputLua[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}.
-
-\InputLua[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}.
-
-\InputLua[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 zuerst der Lua Funktion
-% \lua{string.match(value, match)} übergeben, bevor er dem Schlüssel
-% zugewiesen wird.
-The value of the key is first passed to the Lua function
-\lua{string.match(value, match)}
-(\url{http://www.lua.org/manual/5.3/manual.html#pdf-string.match})
-before being assigned to the key.
-%
-% Du kannst das Attribut \lua{match} deshalb mit einer Pattern-Matching-
-% Zeichenkette konfigurieren, wie sie in Lua zu Einsatz kommt.
-You can therefore configure the \lua{match} attribute with a pattern
-matching string used in Lua.
-%
-% 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}).
-
-\InputLua[firstline=4,lastline=7]{defs/attrs/match/birthday.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.
-
-\InputLua[firstline=15,lastline=18]{defs/attrs/match/birthday.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.
-%
-% Im nächsten Beispiel wird der gesamte Eingabewert akzeptiert:
-In the next example, the entire input value is accepted:
-
-\InputLua[firstline=4,lastline=5]{defs/attrs/match/year.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.
-
-\InputLua[firstline=11,lastline=11]{defs/attrs/match/year.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 ...
-
-\InputLua[firstline=4,lastline=8]{defs/attrs/name/as-key.lua}
-
-\noindent
-% ... können wir schreiben:
-... we can write:
-
-\InputLua[firstline=4,lastline=8]{defs/attrs/name/name-attr.lua}
-
-%%
-%
-%%
-
-\subsubsection{Attribute “\texttt{pick}”}
-
-% Das Attribut \lua{pick} sucht nach einem Wert, der keinem Schlüssel
-% zugeordnet ist.
-The attribute \lua{pick} searches for a value not assigned to a key.
-% Der zuerst gefundene Wert, d. h. der weiter links stehende Wert, wird
-% einem Schlüssel zugewiesen.
-The first value found, i.e. the one further to the left, is assigned to
-a key.
-
-\InputLua[firstline=4,lastline=6]{defs/attrs/pick/dimension.lua}
-
-\noindent
-% Es wird nur in der aktuellen Ergebnistabelle gesucht und nicht auf
-% anderen Ebenen in der rekursiven Datenstruktur.
-Only the current result table is searched, not other levels in the
-recursive data structure.
-
-\InputLua[firstline=4,lastline=11]{defs/attrs/pick/different-levels.lua}
-
-\noindent
-% Die Suche nach Werte wird aktiviert, wenn das Attribut \lua{pick} auf
-% \lua{true} gesetzt wird.
-The search for values is activated when the attribute \lua{pick} is set
-to a data type.
-% Mit diesen Datentypen kann nach Werten gesucht werden
-These data types can be used to search for values:
-\directlua{
-  local types = {}
-  for t, fn in pairs(luakeys.is) do
-    table.insert(types, t)
-  end
-  tex.print(table.concat(types, ', '))
-}.
-% Verwendet den Datentyp any um jeden beliebigen Wert zu akzeptieren.
-Use the data type “any” to accept any value.
-% Wird einem Schlüssel bereits bei der Eingabe ein Wert zugewiesen, dann
-% wird nicht weiter nach Werten gesucht.
-If a value is already assigned to a key when it is entered, then no
-further search for values is performed.
-\InputLua[firstline=4,lastline=8]{defs/attrs/pick/value-set.lua}
-
-\noindent
-% Das Attribut \lua{pick} akzeptiert auch mehrere Datentypen, die in
-% einer Tabelle angegeben werden.
-The \lua{pick} attribute also accepts multiple data types
-specified in a table.
-
-\InputLua[firstline=4,lastline=10]{defs/attrs/pick/multiple-data-types.lua}
-
-%%
-%
-%%
-
-\subsubsection{Attribute “\texttt{process}”}
-\label{attr-process}
-
-% Das Attribut \lua{process} kann dazu verwendet werden, um eine Funktion
-% zu definieren, deren Rückgabewert an den Schlüssel übergeben wird.
-The \lua{process} attribute can be used to define a function whose
-return value is passed to the key.
-%
-% Beim Aufruf der Funktion werden vier Parameter übergeben:
-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}
-
-\noindent
-% Das folgende Beispiel demonstriert den Parameter \lua{value}:
-The following example demonstrates the \lua{value} parameter:
-\InputLua[firstline=4,lastline=14]{defs/attrs/process/parameter-value.lua}
-
-\noindent
-% Das folgende Beispiel demonstriert den Parameter \lua{input}:
-The following example demonstrates the \lua{input} parameter:
-
-\InputLua[firstline=4,lastline=16]{defs/attrs/process/parameter-input.lua}
-
-\noindent
-% Das folgende Beispiel demonstriert den Parameter \lua{result}:
-The following example demonstrates the \lua{result} parameter:
-
-\InputLua[firstline=4,lastline=12]{defs/attrs/process/parameter-result.lua}
-
-\noindent
-% Das folgende Beispiel demonstriert den Parameter \lua{unknown}:
-The following example demonstrates the \lua{unknown} parameter:
-
-\InputLua[firstline=4,lastline=11]{defs/attrs/process/parameter-unknown.lua}
-\InputLua[firstline=15,lastline=15]{defs/attrs/process/parameter-unknown.lua}
-
-%%
-%
-%%
-
-\subsubsection{Attribute “\texttt{required}”}
-
-% Durch das Attribut \lua{required} kann erzwungen werden, dass ein
-% bestimmter Schlüssel angegeben werden muss.
-The \lua{required} attribute can be used to enforce that a specific key
-must be specified.
-%
-% Im untenstehenden Beispiel wird der Schlüssel \lua{important} als
-% zwingend notwendig definiert.
-In the example below, the key \lua{important} is defined as mandatory.
-
-\InputLua[firstline=4,lastline=5]{defs/attrs/required.lua}
-
-\noindent
-% Fehlt der Schlüssel \lua{important} in der Eingabe, so tritt eine
-% Fehlermeldung auf.
-If the key \lua{important} is missing in the input, an error message
-occurs.
-
-\InputLua[firstline=13,lastline=14]{defs/attrs/required.lua}
-
-\noindent
-A recursive example:
-
-\InputLua[firstline=18,lastline=23]{defs/attrs/required.lua}
-
-\noindent
-% Der Schlüssel \lua{important2} auf der Ebene 2 fehlt.
-The \lua{important2} key on level 2 is missing.
-
-\InputLua[firstline=29,lastline=30]{defs/attrs/required.lua}
-
-\noindent
-% Der Schlüssel \lua{important1} auf der untersten Schlüssel-Ebene fehlt.
-The \lua{important1} key at the lowest key level is missing.
-
-\InputLua[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.
-
-\InputLua[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.
-
-\InputLua[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.
-
-\InputLua[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.
-
-\InputLua[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}”}
-
-% In der Tabelle \lua{is} werden einige Funktionen zusammengefasst, die
-% überprüft ob eine Eingabe einem bestimmen Datentyp entspricht.
-In the table \lua{is} some functions are summarized, which check whether
-an input corresponds to a certain data type.
-%
-% Alle Funktionen akzeptieren nicht nur die entsprechenden Lua-Datentypen,
-% sondern auch Eingaben als Zeichenketten.
-All functions accept not only the corresponding Lua data types, but also
-input as strings.
-%
-% Beispielsweise wird die Zeichenkette \lua{'true'} von der
-% \lua{is.boolean()}-Funktion als Wahrheitswert erkannt.
-For example, the string \lua{'true'} is recognized by the
-\lua{is.boolean()} function as a boolean value.
-
-\subsubsection{Function “\texttt{is.boolean(value): boolean}”}
-\InputLua[firstline=6,lastline=23]{functions/is/boolean.lua}
-
-\subsubsection{Function “\texttt{is.dimension(value): boolean}”}
-\InputLua[firstline=6,lastline=16]{functions/is/dimension.lua}
-
-\subsubsection{Function “\texttt{is.integer(value): boolean}”}
-\InputLua[firstline=6,lastline=11]{functions/is/integer.lua}
-
-\subsubsection{Function “\texttt{is.number(value): boolean}”}
-\InputLua[firstline=6,lastline=13]{functions/is/number.lua}
-
-\subsubsection{Function “\texttt{is.string(value): boolean}”}
-\InputLua[firstline=6,lastline=12]{functions/is/string.lua}
-
-\subsubsection{Function “\texttt{is.any(value): boolean}”}
-
-% Die Funktion \lua{is.any(value)}  gibt immer wahr zurück und
-% akzeptiert deshalb jeden Datentyp.
-The function \lua{is.any(value)} always returns \lua{true} and
-therefore accepts any data type.
-
-%%
-%
-%%
-
-\subsection{Table “\texttt{utils}”}
-
-\subsubsection{Function “\texttt{utils.scan_oarg(initial_delimiter?, end_delimiter?): string}”}
-
-% Plain \TeX{} kennt keine optionalen Argumente (oarg).
-Plain \TeX{} does not know optional arguments (oarg).
-%
-% Die Funktion ermöglicht es nicht nur in \LaTeX{}, sondern auch in
-% Plain \TeX{} nach optionalen Argumenten zu suchen.
-The function \\ \lua{utils.scan_oarg(initial_delimiter?,
-end_delimiter?): string} allows to search for optional arguments not only
-in \LaTeX{} but also in Plain \TeX.
-%
-% Die Funktion basiert auf der Token-Bibliothek.
-The function uses the token library built into Lua\TeX{}.
-%
-% Die beiden Parameter \lua{initial_delimiter} und \lua{end_delimiter}
-% können weggelassen werden.
-The two parameters \lua{initial_delimiter} and \lua{end_delimiter} can
-be omitted.
-%
-% Dann werden eckige Klammern als Begrenzungszeichen angenommen.
-Then square brackets are assumed to be delimiters.
-%
-% Dieser Lua-Code \lua{utils.scan_oarg('(', ')')} sucht beispielsweise
-% nach an einem optionalen Argument in runden Klammern.
-For example, this Lua code \lua{utils.scan_oarg('(', ')')} searches for
-an optional argument in round brackets
-%
-% Die Funktion gibt die Zeichenkette zwischen den Begrenzungszeichen
-% zurück, oder nil wenn Begrenzungszeichen gefunden werden konnten.
-The function returns the string between the delimiters or \lua{nil} if
-no delimiters could be found.
-%
-% Die Begrenzungszeichen sind im Ergebnis nicht enthalten.
-The delimiters themselves are not included in the result.
-%
-% Nach dem \latex{\directlua{}} darf das Makro, indem
-% \lua{utils.scan_oarg} eingesetzt wird, zu keinen Zeichen expandieren.
-After the \latex{\directlua{}}, the macro using \lua{utils.scan_oarg}
-must not expand to any characters.
-
-\InputLatex{utils/scan-oarg-plain.tex}
-
-%%
-%
-%%
-
-\subsection{Table “version”}
-
-% Das luakeys Projekt verwendet semantic versioning
-The luakeys project uses semantic versioning.
-% Die drei Versionenzahlen des Semantic Versioning Schemas werden in
-% einer Tabelle in der Reihenfolge MAJOR, MINOR, PATCH als Ganzzahlen
-% abgelegt.
-The three version numbers of the semantic versioning scheme are stored
-in a table as integers in the order MAJOR, MINOR, PATCH.
-% Mit Hilfe dieser Tabelle kann überprüft werden, ob die richtige
-% Version installiert ist.
-This table can be used to check whether the correct version is
-installed.
-
-\InputLua[firstline=4,lastline=10]{version.lua}
-
-%-----------------------------------------------------------------------
-%
-%-----------------------------------------------------------------------
-
-\section{Syntax of the recognized key-value format}
-
-%%
-%
-%%
-
-\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 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\}\}}).
-
-%%
-%
-%%
-
-\subsection{An (incomplete) attempt to put the syntax into the Extended Backus-Naur Form}
-
-\begin{grammar}
-<list> ::= \{ <list-item> \}
-
-<list-container> ::= `{' <list> `}'
-
-<list-item> ::= ( <list-container> | <key-value-pair> | <value> ) [ `,' ]
-
-<key-value-pair> ::= <value> `=' ( <list-container> | <value> )
-
-<value> ::= <boolean>
-  \alt <dimension>
-  \alt <number>
-  \alt <string-quoted>
-  \alt <string-unquoted>
-
-<dimension> ::= <number> <unit>
-
-<number> ::= <sign> ( <integer> [ <fractional> ] | <fractional> )
-
-<fractional> ::= `.' <integer>
-
-<sign> ::= `-' | `+'
-
-<integer> ::= <digit> \{ <digit> \}
-
-<digit> ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9'
-
-<unit> ::= `bp' | `BP'
-  \alt `cc' | `CC'
-  \alt `cm' | `CM'
-  \alt `dd' | `DD'
-  \alt `em' | `EM'
-  \alt `ex' | `EX'
-  \alt `in' | `IN'
-  \alt `mm' | `MM'
-  \alt `mu' | `MU'
-  \alt `nc' | `NC'
-  \alt `nd' | `ND'
-  \alt `pc' | `PC'
-  \alt `pt' | `PT'
-  \alt `px' | `PX'
-  \alt `sp' | `SP'
-
-<boolean> ::= <boolean-true> | <boolean-false>
-
-<boolean-true> ::= `true' | `TRUE' | `True'
-
-<boolean-false> ::= `false' | `FALSE' | `False'
-\end{grammar}
-
-... to be continued
-
-%%
-%
-%%
-
-\subsection{Recognized data types}
-
-\subsubsection{boolean}
-
-The strings \texttt{true}, \texttt{TRUE} and \texttt{True} are converted
-into Lua’s boolean type \lua{true}, the strings \texttt{false},
-\texttt{FALSE} and \texttt{False} into \lua{false}.
-
-\begin{multicols}{2}
-\InputLatex[firstline=5,lastline=12]{luakeysdebug/boolean-latex.tex}
-
-\columnbreak
-
-\begin{minted}{lua}
-{
-  ['lower case true'] = true,
-  ['upper case true'] = true,
-  ['title case true'] = true,
-  ['lower case false'] = false,
-  ['upper case false'] = false
-  ['title case false'] = false,
-}
-\end{minted}
-\end{multicols}
-
-%%
-%
-%%
-
-\subsubsection{number}
-
-\begin{multicols}{2}
-\InputLatex[firstline=5,lastline=13]{luakeysdebug/number-latex.tex}
-
-\columnbreak
-
-\begin{minted}{lua}
-{
-  ['num0'] = 42,
-  ['num1'] = 42,
-  ['num2'] = -42,
-  ['num3'] = 4.2,
-  ['num4'] = 0.42,
-  ['num5'] = 0.42,
-  ['num6'] = '0 . 42', -- string
-}
-\end{minted}
-\end{multicols}
-
-%%
-%
-%%
-
-\subsubsection{dimension}
-
-% Luakeys versucht alle Einheiten zu erkennen, die in der TeX-Welt
-% verwendet werden.
-|luakeys| tries to recognize all units used in the \TeX{} world.
-%
-% Nach dem Lua\TeX-Quellcode .
-According to the Lua\TeX{} source code
-(\href{https://github.com/TeX-Live/luatex/blob/51db1985f5500dafd2393aa2e403fefa57d3cb76/source/texk/web2c/luatexdir/lua/ltexlib.c#L434-L625}
-{source/texk/web2c/luatexdir/lua/ltexlib.c})
-%
-% und nach dem Dimensionen-Modul der lulibs-Bibliothek
-and the dimension module of the lualibs library
-(\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua}
-{lualibs-util-dim.lua}),
-%
-% müssten alle Einheiten erkannt werden
-all units should be recognized.
-\begin{multicols}{3}
-\tiny
-\begin{center}
-\begin{tabular}{rl}
-% \textbf{Unit name}
-& \textbf{Description} \\\hline
-bp & big point \\
-cc & cicero \\
-cm & centimeter \\
-dd & didot \\
-em & horizontal measure of \emph{M} \\
-ex & vertical measure of \emph{x} \\
-in & inch \\
-mm & milimeter \\
-mu & math unit \\
-nc & new cicero \\
-nd & new didot \\
-pc & pica \\
-pt & point \\
-px & x height current font \\
-sp & scaledpoint \\
-\end{tabular}
-\end{center}
-
-\columnbreak
-
-\InputLatex[firstline=5,lastline=21]{luakeysdebug/dimension/all-latex.tex}
-
-\columnbreak
-
-\begin{minted}{lua}
-{
-  ['bp'] = 65781,
-  ['cc'] = 841489,
-  ['cm'] = 1864679,
-  ['dd'] = 70124,
-  ['em'] = 655360,
-  ['ex'] = 282460,
-  ['in'] = 4736286,
-  ['mm'] = 186467,
-  ['mu'] = 65536,
-  ['nc'] = 839105,
-  ['nd'] = 69925,
-  ['pc'] = 786432,
-  ['pt'] = 65536,
-  ['px'] = 65781,
-  ['sp'] = 1,
-}
-\end{minted}
-\end{multicols}
-
-\noindent
-% Im nächsten Beispiel werden die unterschiedlichen Notationsformen der
-% Dimensionen illustriert.
-The next example illustrates the different notations of the dimensions.
-
-\begin{multicols}{2}
-\InputLatex[firstline=5,lastline=12]{luakeysdebug/dimension/notations-latex.tex}
-
-\columnbreak
-
-\begin{minted}{lua}
-{
-  ['upper'] = 1864679,
-  ['lower'] = 1864679,
-  ['space'] = 1864679,
-  ['plus'] = 1864679,
-  ['minus'] = -1864679,
-  ['nodim'] = '1 c m', -- string
-}
-\end{minted}
-\end{multicols}
-
-%%
-%
-%%
-
-\subsubsection{string}
-
-% 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.
-
-\InputLua[firstline=4,lastline=17]{data-types/string.lua}
-
-\subsubsection{Naked keys}
-
-% 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).
-
-\InputLatex[firstline=5,lastline=12]{luakeysdebug/naked-keys-latex.tex}
-
-\noindent
-% VAlle erkannten Datentypen können als eigenständige Werte verwendet
-% werden.
-All recognized data types can be used as standalone values.
-
-\InputLatex[firstline=14,lastline=19]{luakeysdebug/naked-keys-latex.tex}
-
-%-----------------------------------------------------------------------
-%
-%-----------------------------------------------------------------------
-
-\clearpage
-
-\section{Examples}
-
-%%
-%
-%%
-
-\subsection{Extend and modify keys of existing macros}
-
-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}
-
-\InputLua{extend-includegraphics/extend-keys.lua}
-\InputLatex{extend-includegraphics/extend-keys-latex.tex}
-
-%%
-%
-%%
-
-\subsection{Process document class options}
-
-% Auf die Optionen einer \LaTeX{} Dokumentenklasse kann über das Macro
-% \latex{\@classoptionslist} zu gegriffen werden.
-The options of a \LaTeX{} document class can be accessed via the
-\latex{\@classoptionslist} macro.
-%
-% Die Zeichenkette des Makros \latex{\@classoptionslist} ist bereits
-% expandiert und normalisiert.
-The string of the macro \latex{\@classoptionslist} is already expanded
-and normalized.
-%
-% Neben Leerzeichen werden auch die geschweiften Klammern entfernt.
-In addition to spaces, the curly brackets are also removed.
-% Es ist deshalb nicht möglich, in einander geschachtelte hierarische
-% Schlüssel-Wert-Paare über die Option zu verarbeiten.
-It is therefore not possible to process nested hierarchical key-value
-pairs via the option.
-
-\InputLatex{class-options/test-class.cls}
-\InputLatex{class-options/use-test-class-latex.tex}
-
-\begin{minted}{lua}
-{
-  [1] = '12pt',
-  [2] = 'landscape',
-}
-\end{minted}
-
-%-----------------------------------------------------------------------
-%
-%-----------------------------------------------------------------------
-
-\clearpage
-
-\section{Debug packages}
-
-Two small debug packages are included in |luakeys|. One debug package
-can be used in \LaTeX{} (luakeys-debug.sty) and one can be used in plain
-\TeX{} (luakeys-debug.tex). Both packages provide only one command:
-|\luakeysdebug{kv-string}|
-
-\begin{minted}{latex}
-\luakeysdebug{one,two,three}
-\end{minted}
-
-\noindent
-Then the following output should appear in the document:
-\bigskip
-
-\luakeysdebug{one,two,three}
-
-%%
-%
-%%
-
-\subsection{For plain \TeX: luakeys-debug.tex}
-
-An example of how to use the command in plain \TeX:
-
-\begin{minted}{latex}
-\input luakeys-debug.tex
-\luakeysdebug{one,two,three}
-\bye
-\end{minted}
-
-%%
-%
-%%
-
-\subsection{For \LaTeX: luakeys-debug.sty}
-
-An example of how to use the command in \LaTeX:
-
-\begin{minted}{latex}
-\documentclass{article}
-\usepackage{luakeys-debug}
-\begin{document}
-\luakeysdebug[
-  unpack=false,
-  convert dimensions=false
-]{one,two,three}
-\end{document}
-\end{minted}
-
-\section{Activity diagramm of the parsing process}
-
-\begin{center}
-\includegraphics[width=0.8\linewidth]{Activity-Diagramm.eps}
-\end{center}
-
-%-----------------------------------------------------------------------
-%
-%-----------------------------------------------------------------------
-
-\clearpage
-
-\section{Implementation}
-
-%%
-%
-%%
-
-\subsection{luakeys.lua}
-
-\inputminted[linenos=true]{lua}{luakeys.lua}
-
-%%
-%
-%%
-
-\clearpage
-
-\subsection{luakeys.tex}
-
-\inputminted[linenos=true]{latex}{luakeys.tex}
-
-%%
-%
-%%
-
-\clearpage
-
-\subsection{luakeys.sty}
-
-\inputminted[linenos=true]{latex}{luakeys.sty}
-
-%%
-%
-%%
-
-\clearpage
-
-\subsection{luakeys-debug.tex}
-
-\inputminted[linenos=true]{latex}{luakeys-debug.tex}
-
-%%
-%
-%%
-
-\clearpage
-
-\subsection{luakeys-debug.sty}
-
-\inputminted[linenos=true]{latex}{luakeys-debug.sty}
-
-\changes{0.1.0}{2021/01/18}{Inital release}
-\changes{0.2.0}{2021/09/19}{
-* Allow all recognized data types as keys.
-* Allow TeX macros in the values.
-* New public Lua functions: save(identifier, result), get(identifier).
-}
-\changes{0.3.0}{2021/11/05}{
-* Add a LuaLaTeX wrapper “luakeys.sty”.
-* Add a plain LuaTeX wrapper “luakeys.tex”.
-* Rename the previous documentation file “luakeys.tex” to luakeys-doc.tex”.
-}
-\changes{0.4.0}{2021/12/31}{
-* Parser: Add support for nested tables (for example {{'a', 'b'}}).
-* Parser: Allow only strings and numbers as keys.
-* Parser: Remove support from Lua numbers with exponents (for example '5e+20').
-* Switch the Lua testing framework to busted.
-}
-\changes{0.5.0}{2022/04/04}{
-* Add possibility to change options globally.
-* New option: standalone_as_true.
-* Add a recursive converter callback / hook to process the parse tree.
-* New option: case_insensitive_keys.
-}
-\changes{0.6.0}{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()”.
-}
-\changes{0.7.0}{2022/07/06}{
-* The project now uses semantic versioning.
-* New definition attribute “pick” to pick standalone values and assign
-  them to a key.
-* New function “utils.scan_oarg()” to search for an optional argument,
-  that means scan for tokens that are enclosed in square brackets.
-* Extend and improve the documentation.
-}
-\changes{0.8.0}{2022/11/17}{
-* Add 6 new options to change the delimiters: “assignment_operator”,
-  “group_begin”, “group_end”, “list_separator”, “quotation_begin”,
-  “quotation_end”.
-* Extend the documentation about the option “format_keys”.
-}
-\changes{0.9.0}{2022/11/21}{
-* The definition attibute “pick” accepts a new data type: “any”.
-* The attribute value “true” for the attribute “pick” is deprecated.
-* The attribute “pick” accepts now multiple data types specified in
-  a table.
-* Add a new function called “any(value)” in the “is” table that accepts
-  any data type.
-}
-\changes{0.10.0}{2022/12/16}{
-* Add support for an invert flat that flips the value of naked keys.
-* Add new options to specify which strings are recognized as Boolean
-  values.
-}
-\changes{0.11.0}{2022/12/23}{
-* Add a new function called “get_private_instance()” to load a private
-  version of the luakeys module.
-}
-
-\pagebreak
-\PrintChanges
-\pagebreak
-\PrintIndex
-\end{document}

Added: trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys.pdf
===================================================================
(Binary files differ)

Index: trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys.pdf
===================================================================
--- trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys.pdf	2023-01-05 00:50:44 UTC (rev 65467)
+++ trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys.pdf	2023-01-05 21:18:54 UTC (rev 65468)

Property changes on: trunk/Master/texmf-dist/doc/luatex/luakeys/luakeys.pdf
___________________________________________________________________
Added: svn:mime-type
## -0,0 +1 ##
+application/pdf
\ No newline at end of property
Modified: trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.sty
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.sty	2023-01-05 00:50:44 UTC (rev 65467)
+++ trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.sty	2023-01-05 21:18:54 UTC (rev 65468)
@@ -1,5 +1,5 @@
 %% luakeys-debug.sty
-%% Copyright 2021-2022 Josef Friedrich
+%% Copyright 2021-2023 Josef Friedrich
 %
 % This work may be distributed and/or modified under the
 % conditions of the LaTeX Project Public License, either version 1.3c
@@ -17,6 +17,6 @@
 % luakeys-debug.sty and luakeys-debug.tex.
 
 \NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{luakeys-debug}[2022/12/23 0.11.0 Debug package for luakeys.]
+\ProvidesPackage{luakeys-debug}[2023/01/05 v0.12.0 Debug package for luakeys.]
 
 \input luakeys-debug.tex

Modified: trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.tex
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.tex	2023-01-05 00:50:44 UTC (rev 65467)
+++ trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys-debug.tex	2023-01-05 21:18:54 UTC (rev 65468)
@@ -1,5 +1,5 @@
 %% luakeys-debug.tex
-%% Copyright 2021-2022 Josef Friedrich
+%% Copyright 2021-2023 Josef Friedrich
 %
 % This work may be distributed and/or modified under the
 % conditions of the LaTeX Project Public License, either version 1.3c
@@ -18,7 +18,7 @@
 
 \directlua
 {
-  luakeys = require('luakeys')
+  luakeys = require('luakeys')()
 }
 
 \def\luakeysdebug%

Modified: trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.lua
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.lua	2023-01-05 00:50:44 UTC (rev 65467)
+++ trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.lua	2023-01-05 21:18:54 UTC (rev 65468)
@@ -1,35 +1,33 @@
--- luakeys.lua
--- Copyright 2021-2022 Josef Friedrich
---
--- 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 2008/05/04 or later.
---
--- This work has the LPPL maintenance status `maintained'.
---
--- The Current Maintainer of this work is Josef Friedrich.
---
--- 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.
---
--- @module luakeys
+---luakeys.lua
+---Copyright 2021-2023 Josef Friedrich
+---
+---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 2008/05/04 or later.
+---
+---This work has the LPPL maintenance status `maintained'.
+---
+---The Current Maintainer of this work is Josef Friedrich.
+---
+---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.
+---
+--- at module luakeys
 local lpeg = require('lpeg')
 
 if not tex then
+  ---Dummy functions for the tests.
   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,
@@ -36,97 +34,120 @@
   }
 end
 
---- Merge two tables in the first specified table.
---- The `merge_tables` function copies all keys from the `source` table
---- to a target table. It returns the modified target table.
----
---- at see https://stackoverflow.com/a/1283608/10193818
----
---- at param target table
---- at param source table
----
---- at return table target The modified target table.
-local function merge_tables(target, source)
-  for key, value in pairs(source) do
-    if type(value) == 'table' then
-      if type(target[key] or false) == 'table' then
-        merge_tables(target[key] or {}, source[key] or {})
-      elseif target[key] == nil then
+local utils = (function()
+  ---
+  ---Merge two tables into the first specified table.
+  ---The `merge_tables` function copies keys from the `source` table
+  ---to the `target` table. It returns the target table.
+  ---
+  ---https://stackoverflow.com/a/1283608/10193818
+  ---
+  --- at param target table # The target table where all values are copied.
+  --- at param source table # The source table from which all values are copied.
+  --- at param overwrite? boolean # Overwrite the values in the target table if they are present (default true).
+  ---
+  --- at return table target The modified target table.
+  local function merge_tables(target, source, overwrite)
+    if overwrite == nil then
+      overwrite = true
+    end
+    for key, value in pairs(source) do
+      if type(value) == 'table' and type(target[key] or false) ==
+        'table' then
+        merge_tables(target[key] or {}, source[key] or {}, overwrite)
+      elseif (not overwrite and target[key] == nil) or
+        (overwrite and target[key] ~= value) then
         target[key] = value
       end
-    elseif target[key] == nil then
-      target[key] = value
     end
+    return target
   end
-  return target
-end
 
----Clone a table.
----
---- at see http://lua-users.org/wiki/CopyTable
----
---- at param orig table
----
---- at return table
-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)
+  ---
+  ---Clone a table, i.e. make a deep copy of the source table.
+  ---
+  ---http://lua-users.org/wiki/CopyTable
+  ---
+  --- at param source table # The source table to be cloned.
+  ---
+  --- at return table # A deep copy of the source table.
+  local function clone_table(source)
+    local copy
+    if type(source) == 'table' then
+      copy = {}
+      for orig_key, orig_value in next, source, nil do
+        copy[clone_table(orig_key)] = clone_table(orig_value)
+      end
+      setmetatable(copy, clone_table(getmetatable(source)))
+    else ---number, string, boolean, etc
+      copy = source
     end
-    setmetatable(copy, clone_table(getmetatable(orig)))
-  else -- number, string, boolean, etc
-    copy = orig
+    return copy
   end
-  return copy
-end
 
-local utils = {
-  --- Get the size of an array like table `{ 'one', 'two', 'three' }` = 3.
   ---
-  --- at param value table # A table or any input.
+  ---Remove an element from a table.
   ---
+  --- at param source table
+  --- at param value any
+  ---
+  --- at return any|nil
+  local function remove_from_table(source, value)
+    for index, v in pairs(source) do
+      if value == v then
+        source[index] = nil
+        return value
+      end
+    end
+  end
+
+  ---
+  --- at param source table
+  ---
+  --- at return table # An array table with the sorted key names.
+  local function get_table_keys(source)
+    local keys = {}
+    for key in pairs(source) do
+      table.insert(keys, key)
+    end
+    table.sort(keys)
+    return keys
+  end
+
+  ---
+  ---Get the size of a table `{ one = 'one', 'two', 'three' }` = 3.
+  ---
+  --- at param value any # A table or any input.
+  ---
   --- at return 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 function get_table_size(value)
     local count = 0
     if type(value) == 'table' then
-      for _ in ipairs(value) do
+      for _ in pairs(value) do
         count = count + 1
       end
     end
     return count
-  end,
+  end
 
-  ---Get the size of a table `{ one = 'one', 'two', 'three' }` = 3.
   ---
-  --- at param value table|any # A table or any input.
+  ---Get the size of an array like table, for example `{ 'one', 'two',
+  ---'three' }` = 3.
   ---
+  --- at param value any # A table or any input.
+  ---
   --- at return 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 function get_array_size(value)
     local count = 0
     if type(value) == 'table' then
-      for _ in pairs(value) do
+      for _ in ipairs(value) do
         count = count + 1
       end
     end
     return count
-  end,
+  end
 
-  merge_tables = merge_tables,
-
-  clone_table = clone_table,
-
-  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,
-
+  ---
   ---Scan for an optional argument.
   ---
   --- at param initial_delimiter? string # The character that marks the beginning of an optional argument (by default `[`).
@@ -133,7 +154,8 @@
   --- at param end_delimiter? string # The character that marks the end of an optional argument (by default `]`).
   ---
   --- at return string|nil # The string that was enclosed by the delimiters. The delimiters themselves are not returned.
-  scan_oarg = function(initial_delimiter, end_delimiter)
+  local function scan_oarg(initial_delimiter,
+    end_delimiter)
     if initial_delimiter == nil then
       initial_delimiter = '['
     end
@@ -167,11 +189,603 @@
     else
       token.put_next(t)
     end
-  end,
-}
+  end
 
+  ---
+  ---Throw a single error message.
+  ---
+  --- at param message string
+  --- at param help? table
+  local function throw_error_message(message, help)
+    if type(tex.error) == 'function' then
+      tex.error(message, help)
+    else
+      error(message)
+    end
+  end
+
+  ---
+  ---Throw an error by specifying an error code.
+  ---
+  --- at param error_messages table
+  --- at param error_code string
+  --- at param args? table
+  local function throw_error_code(error_messages,
+    error_code,
+    args)
+    local template = error_messages[error_code]
+
+    ---
+    --- at param message string
+    --- at param a table
+    ---
+    --- at return string
+    local function replace_args(message, a)
+      for key, value in pairs(a) do
+        if type(value) == 'table' then
+          value = table.concat(value, ', ')
+        end
+        message = message:gsub('@' .. key,
+          '“' .. tostring(value) .. '”')
+      end
+      return message
+    end
+
+    ---
+    --- at param list table
+    --- at param a table
+    ---
+    --- at return table
+    local function replace_args_in_list(list, a)
+      for index, message in ipairs(list) do
+        list[index] = replace_args(message, a)
+      end
+      return list
+    end
+
+    ---
+    --- at type string
+    local message
+    --- at type table
+    local help = {}
+
+    if type(template) == 'table' then
+      message = template[1]
+      if args ~= nil then
+        help = replace_args_in_list(template[2], args)
+      else
+        help = template[2]
+      end
+    else
+      message = template
+    end
+
+    if args ~= nil then
+      message = replace_args(message, args)
+    end
+
+    message = 'luakeys error [' .. error_code .. ']: ' .. message
+
+    for _, help_message in ipairs({
+      'You may be able to find more help in the documentation:',
+      'http://mirrors.ctan.org/macros/luatex/generic/luakeys/luakeys-doc.pdf',
+      'Or ask a question in the issue tracker on Github:',
+      'https://github.com/Josef-Friedrich/luakeys/issues',
+    }) do
+      table.insert(help, help_message)
+    end
+
+    throw_error_message(message, help)
+  end
+
+  local function visit_tree(tree, callback_func)
+    if type(tree) ~= 'table' then
+      throw_error_message(
+        'Parameter “tree” has to be a table, got: ' ..
+          tostring(tree))
+    end
+    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_tree_recursive(tree, value, {}, depth + 1,
+            callback_func)
+        end
+
+        key, value = callback_func(key, value, depth, current, tree)
+
+        if key ~= nil and value ~= nil then
+          result[key] = value
+        end
+      end
+      if next(result) ~= nil then
+        return result
+      end
+    end
+
+    local result =
+      visit_tree_recursive(tree, tree, {}, 1, callback_func)
+
+    if result == nil then
+      return {}
+    end
+    return result
+  end
+
+  --- at alias ColorName 'black'|'red'|'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'reset'
+  --- at alias ColorMode 'bright'|'dim'
+
+  ---
+  ---Small library to surround strings with ANSI color codes.
+  --
+  ---[SGR (Select Graphic Rendition) Parameters](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters)
+  ---
+  ---__attributes__
+  ---
+  ---| color      |code|
+  ---|------------|----|
+  ---| reset      |  0 |
+  ---| clear      |  0 |
+  ---| bright     |  1 |
+  ---| dim        |  2 |
+  ---| underscore |  4 |
+  ---| blink      |  5 |
+  ---| reverse    |  7 |
+  ---| hidden     |  8 |
+  ---
+  ---__foreground__
+  ---
+  ---| color      |code|
+  ---|------------|----|
+  ---| black      | 30 |
+  ---| red        | 31 |
+  ---| green      | 32 |
+  ---| yellow     | 33 |
+  ---| blue       | 34 |
+  ---| magenta    | 35 |
+  ---| cyan       | 36 |
+  ---| white      | 37 |
+  ---
+  ---__background__
+  ---
+  ---| color      |code|
+  ---|------------|----|
+  ---| onblack    | 40 |
+  ---| onred      | 41 |
+  ---| ongreen    | 42 |
+  ---| onyellow   | 43 |
+  ---| onblue     | 44 |
+  ---| onmagenta  | 45 |
+  ---| oncyan     | 46 |
+  ---| onwhite    | 47 |
+  local ansi_color = (function()
+
+    ---
+    --- at param code integer
+    ---
+    --- at return string
+    local function format_color_code(code)
+      return string.char(27) .. '[' .. tostring(code) .. 'm'
+    end
+
+    ---
+    --- at private
+    ---
+    --- at param color ColorName # A color name.
+    --- at param mode? ColorMode
+    --- at param background? boolean # Colorize the background not the text.
+    ---
+    --- at return string
+    local function get_color_code(color, mode, background)
+      local output = ''
+      local code
+
+      if mode == 'bright' then
+        output = format_color_code(1)
+      elseif mode == 'dim' then
+        output = format_color_code(2)
+      end
+
+      if not background then
+        if color == 'reset' then
+          code = 0
+        elseif color == 'black' then
+          code = 30
+        elseif color == 'red' then
+          code = 31
+        elseif color == 'green' then
+          code = 32
+        elseif color == 'yellow' then
+          code = 33
+        elseif color == 'blue' then
+          code = 34
+        elseif color == 'magenta' then
+          code = 35
+        elseif color == 'cyan' then
+          code = 36
+        elseif color == 'white' then
+          code = 37
+        else
+          code = 37
+        end
+      else
+        if color == 'black' then
+          code = 40
+        elseif color == 'red' then
+          code = 41
+        elseif color == 'green' then
+          code = 42
+        elseif color == 'yellow' then
+          code = 43
+        elseif color == 'blue' then
+          code = 44
+        elseif color == 'magenta' then
+          code = 45
+        elseif color == 'cyan' then
+          code = 46
+        elseif color == 'white' then
+          code = 47
+        else
+          code = 40
+        end
+      end
+      return output .. format_color_code(code)
+    end
+
+    ---
+    --- at param text any
+    --- at param color ColorName # A color name.
+    --- at param mode? ColorMode
+    --- at param background? boolean # Colorize the background not the text.
+    ---
+    --- at return string
+    local function colorize(text, color, mode, background)
+      return string.format('%s%s%s',
+        get_color_code(color, mode, background), text,
+        get_color_code('reset'))
+    end
+
+    return {
+      colorize = colorize,
+
+      ---
+      --- at param text any
+      ---
+      --- at return string
+      red = function(text)
+        return colorize(text, 'red')
+      end,
+
+      ---
+      --- at param text any
+      ---
+      --- at return string
+      green = function(text)
+        return colorize(text, 'green')
+      end,
+
+      --- at return string
+      yellow = function(text)
+        return colorize(text, 'yellow')
+      end,
+
+      ---
+      --- at param text any
+      ---
+      --- at return string
+      blue = function(text)
+        return colorize(text, 'blue')
+      end,
+
+      ---
+      --- at param text any
+      ---
+      --- at return string
+      magenta = function(text)
+        return colorize(text, 'magenta')
+      end,
+
+      ---
+      --- at param text any
+      ---
+      --- at return string
+      cyan = function(text)
+        return colorize(text, 'cyan')
+      end,
+    }
+  end)()
+
+  ---
+  ---A small logging library.
+  ---
+  ---Log levels:
+  ---
+  ---* 0: silent
+  ---* 1: error
+  ---* 2: warn
+  ---* 3: info
+  ---* 4: verbose
+  ---* 5: debug
+  ---
+  local log = (function()
+    --- at private
+    local opts = { level = 0 }
+
+    --- at private
+    local function print_message(message, ...)
+      print(string.format(message, ...))
+    end
+
+    ---
+    ---Set the log level.
+    ---
+    --- at param level 0|'silent'|1|'error'|2|'warn'|3|'info'|4|'verbose'|5|'debug'
+    local function set_log_level(level)
+      if type(level) == 'string' then
+        if level == 'silent' then
+          opts.level = 0
+        elseif level == 'error' then
+          opts.level = 1
+        elseif level == 'warn' then
+          opts.level = 2
+        elseif level == 'info' then
+          opts.level = 3
+        elseif level == 'verbose' then
+          opts.level = 4
+        elseif level == 'debug' then
+          opts.level = 5
+        else
+          throw_error_message(string.format('Unknown log level: %s',
+            level))
+        end
+      else
+        if level > 5 or level < 0 then
+          throw_error_message(string.format(
+            'Log level out of range 0-5: %s', level))
+        end
+        opts.level = level
+      end
+
+    end
+
+    ---
+    ---Log at level 1 (error).
+    ---
+    ---The other log levels are: 0 (silent), 1 (error), 2 (warn), 3 (info), 4 (verbose), 5 (debug).
+    ---
+    --- at param message string
+    --- at param ... any
+    local function error(message, ...)
+      if opts.level >= 1 then
+        print_message(message, ...)
+      end
+    end
+
+    ---
+    ---Log at level 2 (warn).
+    ---
+    ---The other log levels are: 0 (silent), 1 (error), 2 (warn), 3 (info), 4 (verbose), 5 (debug).
+    ---
+    --- at param message string
+    --- at param ... any
+    local function warn(message, ...)
+      if opts.level >= 2 then
+        print_message(message, ...)
+      end
+    end
+
+    ---
+    ---Log at level 3 (info).
+    ---
+    ---The other log levels are: 0 (silent), 1 (error), 2 (warn), 3 (info), 4 (verbose), 5 (debug).
+    ---
+    --- at param message string
+    --- at param ... any
+    local function info(message, ...)
+      if opts.level >= 3 then
+        print_message(message, ...)
+      end
+    end
+
+    ---
+    ---Log at level 4 (verbose).
+    ---
+    ---The other log levels are: 0 (silent), 1 (error), 2 (warn), 3 (info), 4 (verbose), 5 (debug).
+    ---
+    --- at param message string
+    --- at param ... any
+    local function verbose(message, ...)
+      if opts.level >= 4 then
+        print_message(message, ...)
+      end
+    end
+
+    ---
+    ---Log at level 5 (debug).
+    ---
+    ---The other log levels are: 0 (silent), 1 (error), 2 (warn), 3 (info), 4 (verbose), 5 (debug).
+    ---
+    --- at param message string
+    --- at param ... any
+    local function debug(message, ...)
+      if opts.level >= 5 then
+        print_message(message, ...)
+      end
+    end
+
+    return {
+      set_log_level = set_log_level,
+      error = error,
+      warn = warn,
+      info = info,
+      verbose = verbose,
+      debug = debug,
+    }
+  end)()
+
+  return {
+    merge_tables = merge_tables,
+    clone_table = clone_table,
+    remove_from_table = remove_from_table,
+    get_table_keys = get_table_keys,
+    get_table_size = get_table_size,
+    get_array_size = get_array_size,
+    visit_tree = visit_tree,
+    scan_oarg = scan_oarg,
+    throw_error_message = throw_error_message,
+    throw_error_code = throw_error_code,
+    log = log,
+    ansi_color = ansi_color,
+  }
+end)()
+
+---
+---Convert back to strings
+--- at section
+local visualizers = (function()
+
+  ---
+  ---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.
+  ---
+  --- at param result table # A table to be converted into a key-value string.
+  ---
+  --- at return 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 render_inner(result)
+  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 `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.
+  ---
+  --- at see https://stackoverflow.com/a/54593224/10193818
+  ---
+  --- at param result table # A table to stringify.
+  --- at param for_tex? boolean # 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.
+  ---
+  --- at return string
+  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 start_bracket .. line_break .. stringify_inner(result, 1) ..
+             line_break .. end_bracket
+  end
+
+  ---
+  ---The function `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
+  ---`parse`. You have to compile your TeX document in a console to
+  ---see the terminal output.
+  ---
+  --- at param result table # A table to be printed to standard output for debugging purposes.
+  local function debug(result)
+    print('\n' .. stringify(result, false))
+  end
+
+  return { render = render, stringify = stringify, debug = debug }
+end)()
+
 local namespace = {
   opts = {
+    accumulated_result = false,
     assignment_operator = '=',
     convert_dimensions = false,
     debug = false,
@@ -209,6 +823,7 @@
     choices = true,
     data_type = true,
     default = true,
+    description = true,
     exclusive_group = true,
     l3_tl_set = true,
     macro = true,
@@ -220,1151 +835,1116 @@
     required = true,
     sub_keys = true,
   },
-}
 
---- The default options.
-local default_options = clone_table(namespace.opts)
+  error_messages = {
+    E001 = {
+      'Unknown parse option: @unknown!',
+      { 'The available options are:', '@opt_names' },
+    },
+    E002 = {
+      'Unknown hook: @unknown!',
+      { 'The available hooks are:', '@hook_names' },
+    },
+    E003 = 'Duplicate aliases @alias1 and @alias2 for key @key!',
+    E004 = 'The value @value does not exist in the choices: @choices',
+    E005 = 'Unknown data type: @unknown',
+    E006 = 'The value @value of the key @key could not be converted into the data type @data_type!',
+    E007 = 'The key @key belongs to the mutually exclusive group @exclusive_group and another key of the group named @another_key is already present!',
+    E008 = 'def.match has to be a string',
+    E009 = 'The value @value of the key @key does not match @match!',
 
-local function throw_error(message)
-  if type(tex.error) == 'function' then
-    tex.error(message)
-  else
-    error(message)
-  end
-end
+    E011 = 'Wrong data type in the “pick” attribute: @unknown. Allowed are: @data_types.',
+    E012 = 'Missing required key @key!',
+    E013 = 'The key definition must be a table! Got @data_type for key @key.',
+    E014 = {
+      'Unknown definition attribute: @unknown',
+      { 'The available attributes are:', '@attr_names' },
+    },
+    E015 = 'Key name couldn’t be detected!',
+    E017 = 'Unknown style to format keys: @unknown! Allowed styles are: @styles',
+    E018 = 'The option “format_keys” has to be a table not @data_type',
+    E019 = 'Unknown keys: @unknown',
 
---- Normalize the parse options.
+    ---Input / parsing error
+    E021 = 'Opposite key was specified more than once: @key!',
+    E020 = 'Both opposite keys were given: @true and @false!',
+    ---Config error (wrong configuration of luakeys)
+    E010 = 'Usage: opposite_keys = { "true_key", "false_key" } or { [true] = "true_key", [false] = "false_key" } ',
+    E023 = {
+      'Don’t use this function from the global luakeys table. Create a new instance using e. g.: local lk = luakeys.new()',
+      {
+        'This functions should not be used from the global luakeys table:',
+        'parse()',
+        'save()',
+        'get()',
+      },
+    },
+  },
+}
+
 ---
---- at param opts? table # Options in a raw format. The table may be empty or some keys are not set.
----
---- at return table
-local function normalize_opts(opts)
-  if type(opts) ~= 'table' then
-    opts = {}
-  end
-  for key, _ in pairs(opts) do
-    if namespace.opts[key] == nil then
-      throw_error('Unknown parse option: ' .. tostring(key) .. '!')
-    end
-  end
-  local old_opts = opts
-  opts = {}
-  for name, _ in pairs(namespace.opts) do
-    if old_opts[name] ~= nil then
-      opts[name] = old_opts[name]
-    else
-      opts[name] = default_options[name]
-    end
-  end
+--- at return table # The public interface of the module.
+local function main()
 
-  for hook in pairs(opts.hooks) do
-    if namespace.hooks[hook] == nil then
-      throw_error('Unknown hook: ' .. tostring(hook) .. '!')
-    end
-  end
-  return opts
-end
+  ---The default options.
+  local default_opts = utils.clone_table(namespace.opts)
 
-local l3_code_cctab = 10
+  local error_messages = utils.clone_table(namespace.error_messages)
 
---- Convert back to strings
--- @section
+  ---
+  --- at param error_code string
+  --- at param args? table
+  local function throw_error(error_code, args)
+    utils.throw_error_code(error_messages, error_code, args)
+  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.
----
---- at param result table # A table to be converted into a key-value string.
----
---- at return 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)
+  ---
+  ---Normalize the parse options.
+  ---
+  --- at param opts? table # Options in a raw format. The table may be empty or some keys are not set.
+  ---
+  --- at return table
+  local function normalize_opts(opts)
+    if type(opts) ~= 'table' then
+      opts = {}
     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
+    for key, _ in pairs(opts) do
+      if namespace.opts[key] == nil then
+        throw_error('E001', {
+          unknown = key,
+          opt_names = utils.get_table_keys(namespace.opts),
+        })
+      end
+    end
+    local old_opts = opts
+    opts = {}
+    for name, _ in pairs(namespace.opts) do
+      if old_opts[name] ~= nil then
+        opts[name] = old_opts[name]
       else
-        add(tostring(value) .. ',')
+        opts[name] = default_opts[name]
       end
     end
-    return table.concat(output)
-  end
-  return render_inner(result)
-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 `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.
----
---- at see https://stackoverflow.com/a/54593224/10193818
----
---- at param result table # A table to stringify.
---- at param for_tex? boolean # 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.
----
---- at return string
-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 = '  '
+    for hook in pairs(opts.hooks) do
+      if namespace.hooks[hook] == nil then
+        throw_error('E002', {
+          unknown = hook,
+          hook_names = utils.get_table_keys(namespace.hooks),
+        })
+      end
+    end
+    return opts
   end
 
-  local function stringify_inner(input, depth)
-    local output = {}
-    depth = depth or 0
+  local l3_code_cctab = 10
 
-    local function add(depth, text)
-      table.insert(output, string.rep(indent, depth) .. text)
-    end
+  ---
+  ---Parser / Lpeg related
+  --- at section
 
-    local function format_key(key)
-      if (type(key) == 'number') then
-        return string.format('[%s]', key)
-      else
-        return string.format('[\'%s\']', key)
-      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.or-g/TUGboat/tb40-2/tb125menke-Patterndf)
+  ---
+  --- at param initial_rule string # The name of the first rule of the grammar table passed to the `lpeg.P(attern)` function (e. g. `list`, `number`).
+  --- at param opts? table # Whether the dimensions should be converted to scaled points (by default `false`).
+  ---
+  --- at return userdata # The parser.
+  local function generate_parser(initial_rule, opts)
+    if type(opts) ~= 'table' then
+      opts = normalize_opts(opts)
     end
 
-    if type(input) ~= 'table' then
-      return tostring(input)
+    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
     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
+    local line_up_pattern = function(patterns)
+      local result
+      for _, pattern in ipairs(patterns) do
+        if result == nil then
+          result = Pattern(pattern)
         else
-          if (type(value) == 'string') then
-            value = string.format('\'%s\'', value)
-          else
-            value = tostring(value)
-          end
-
-          add(depth, key .. ' = ' .. value .. ',')
+          result = result + Pattern(pattern)
         end
       end
+      return result
     end
 
-    return table.concat(output, line_break)
-  end
-
-  return start_bracket .. line_break .. stringify_inner(result, 1) ..
-           line_break .. end_bracket
-end
-
---- The function `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
---   `parse`. You have to compile your TeX document in a console to
---   see the terminal output.
---
---- at param result table # A table to be printed to standard output for debugging purposes.
-local function debug(result)
-  print('\n' .. stringify(result, false))
-end
-
---- Parser / Lpeg related
--- @section
-
---- 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.or-g/TUGboat/tb40-2/tb125menke-Patterndf)
----
---- at param initial_rule string # The name of the first rule of the grammar table passed to the `lpeg.P(attern)` function (e. g. `list`, `number`).
---- at param opts? table # Whether the dimensions should be converted to scaled points (by default `false`).
----
---- at return userdata # The parser.
-local function generate_parser(initial_rule, opts)
-  if type(opts) ~= 'table' then
-    opts = normalize_opts(opts)
-  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
-  end
-
-  local line_up_pattern = function(patterns)
-    local result
-    for _, pattern in ipairs(patterns) do
-      if result == nil then
-        result = Pattern(pattern)
+    ---
+    ---Convert a dimension to an normalized dimension string or an
+    ---integer in the scaled points format.
+    ---
+    --- at param input string
+    ---
+    --- at return integer|string # A dimension as an integer or a dimension string.
+    local capture_dimension = function(input)
+      ---Remove all whitespaces
+      input = input:gsub('%s+', '')
+      ---Convert the unit string into lowercase.
+      input = input:lower()
+      if opts.convert_dimensions then
+        return tex.sp(input)
       else
-        result = result + Pattern(pattern)
+        return input
       end
     end
-    return result
-  end
 
-  --- Convert a dimension to an normalized dimension string or an
-  --- integer in the scaled points format.
-  ---
-  --- at param input string
-  ---
-  --- at return integer|string # A dimension as an integer or a dimension string.
-  local capture_dimension = function(input)
-    -- Remove all whitespaces
-    input = input:gsub('%s+', '')
-    -- Convert the unit string into lowercase.
-    input = input:lower()
-    if opts.convert_dimensions then
-      return tex.sp(input)
-    else
-      return input
+    ---
+    ---Add values to a table in two modes:
+    ---
+    ---Key-value pair:
+    ---
+    ---If `arg1` and `arg2` are not nil, then `arg1` is the key and `arg2` is the
+    ---value of a new table entry.
+    ---
+    ---Indexed value:
+    ---
+    ---If `arg2` is nil, then `arg1` is the value and is added as an indexed
+    ---(by an integer) value.
+    ---
+    --- at param result table # The result table to which an additional key-value pair or value should to be added
+    --- at param arg1 any # The key or the value.
+    --- at param arg2? any # Always the value.
+    ---
+    --- at return table # The result table to which an additional key-value pair or value has been added.
+    local add_to_table = function(result, arg1, arg2)
+      if arg2 == nil then
+        local index = #result + 1
+        return rawset(result, index, arg1)
+      else
+        return rawset(result, arg1, arg2)
+      end
     end
-  end
 
-  --- Add values to a table in two modes:
-  --
-  -- Key-value pair:
-  --
-  -- If `arg1` and `arg2` are not nil, then `arg1` is the key and `arg2` is the
-  -- value of a new table entry.
-  --
-  -- Indexed value:
-  --
-  -- If `arg2` is nil, then `arg1` is the value and is added as an indexed
-  -- (by an integer) value.
-  --
-  --- at param result table # The result table to which an additional key-value pair or value should to be added
-  --- at param arg1 any # The key or the value.
-  --- at param arg2? any # Always the value.
-  ---
-  --- at return table # The result table to which an additional key-value pair or value has been added.
-  local add_to_table = function(result, arg1, arg2)
-    if arg2 == nil then
-      local index = #result + 1
-      return rawset(result, index, arg1)
-    else
-      return rawset(result, arg1, arg2)
-    end
-  end
+    -- LuaFormatter off
+    return Pattern({
+      [1] = initial_rule,
 
-  -- LuaFormatter off
-  return Pattern({
-    [1] = initial_rule,
+      ---list_item*
+      list = CaptureFolding(
+        CaptureTable('') * Variable('list_item')^0,
+        add_to_table
+      ),
 
-    -- list_item*
-    list = CaptureFolding(
-      CaptureTable('') * Variable('list_item')^0,
-      add_to_table
-    ),
+      ---'{' list '}'
+      list_container =
+        ws(opts.group_begin) * Variable('list') * ws(opts.group_end),
 
-    -- '{' list '}'
-    list_container =
-      ws(opts.group_begin) * Variable('list') * ws(opts.group_end),
+      ---( list_container / key_value_pair / value ) ','?
+      list_item =
+        CaptureGroup(
+          Variable('list_container') +
+          Variable('key_value_pair') +
+          Variable('value')
+        ) * ws(opts.list_separator)^-1,
 
-    -- ( list_container / key_value_pair / value ) ','?
-    list_item =
-      CaptureGroup(
-        Variable('list_container') +
-        Variable('key_value_pair') +
-        Variable('value')
-      ) * ws(opts.list_separator)^-1,
+      ---key '=' (list_container / value)
+      key_value_pair =
+        (Variable('key') * ws(opts.assignment_operator)) * (Variable('list_container') + Variable('value')),
 
-    -- key '=' (list_container / value)
-    key_value_pair =
-      (Variable('key') * ws(opts.assignment_operator)) * (Variable('list_container') + Variable('value')),
+      ---number / string_quoted / string_unquoted
+      key =
+        Variable('number') +
+        Variable('string_quoted') +
+        Variable('string_unquoted'),
 
-    -- number / string_quoted / string_unquoted
-    key =
-      Variable('number') +
-      Variable('string_quoted') +
-      Variable('string_unquoted'),
+      ---boolean !value / dimension !value / number !value / string_quoted !value / string_unquoted
+      ---!value -> Not-predicate -> * -Variable('value')
+      value =
+        Variable('boolean') * -Variable('value') +
+        Variable('dimension') * -Variable('value') +
+        Variable('number') * -Variable('value')  +
+        Variable('string_quoted') * -Variable('value') +
+        Variable('string_unquoted'),
 
-    -- boolean !value / dimension !value / number !value / string_quoted !value / string_unquoted
-    -- !value -> Not-predicate -> * -Variable('value')
-    value =
-      Variable('boolean') * -Variable('value') +
-      Variable('dimension') * -Variable('value') +
-      Variable('number') * -Variable('value')  +
-      Variable('string_quoted') * -Variable('value') +
-      Variable('string_unquoted'),
+      ---for is.boolean()
+      boolean_only = Variable('boolean') * -1,
 
-    -- for is.boolean()
-    boolean_only = Variable('boolean') * -1,
+      ---boolean_true / boolean_false
+      boolean =
+        (
+          Variable('boolean_true') * CaptureConstant(true) +
+          Variable('boolean_false') * CaptureConstant(false)
+        ),
 
-    -- boolean_true / boolean_false
-    boolean =
-      (
-        Variable('boolean_true') * CaptureConstant(true) +
-        Variable('boolean_false') * CaptureConstant(false)
-      ),
+      boolean_true = line_up_pattern(opts.true_aliases),
 
-    boolean_true = line_up_pattern(opts.true_aliases),
+      boolean_false = line_up_pattern(opts.false_aliases),
 
-    boolean_false = line_up_pattern(opts.false_aliases),
+      ---for is.dimension()
+      dimension_only = Variable('dimension') * -1,
 
-    -- for is.dimension()
-    dimension_only = Variable('dimension') * -1,
+      dimension = (
+        Variable('tex_number') * white_space^0 *
+        Variable('unit')
+      ) / capture_dimension,
 
-    dimension = (
-      Variable('tex_number') * white_space^0 *
-      Variable('unit')
-    ) / capture_dimension,
+      ---for is.number()
+      number_only = Variable('number') * -1,
 
-    -- for is.number()
-    number_only = Variable('number') * -1,
+      ---capture number
+      number = Variable('tex_number') / tonumber,
 
-    -- capture number
-    number = Variable('tex_number') / tonumber,
+      ---sign? white_space? (integer+ fractional? / fractional)
+      tex_number =
+        Variable('sign')^0 * white_space^0 *
+        (Variable('integer')^1 * Variable('fractional')^0) +
+        Variable('fractional'),
 
-    -- sign? white_space? (integer+ fractional? / fractional)
-    tex_number =
-      Variable('sign')^0 * white_space^0 *
-      (Variable('integer')^1 * Variable('fractional')^0) +
-      Variable('fractional'),
+      sign = Set('-+'),
 
-    sign = Set('-+'),
+      fractional = Pattern('.') * Variable('integer')^1,
 
-    fractional = Pattern('.') * Variable('integer')^1,
+      integer = Range('09')^1,
 
-    integer = Range('09')^1,
+      ---'bp' / 'BP' / 'cc' / etc.
+      ---https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua
+      ---https://github.com/TeX-Live/luatex/blob/51db1985f5500dafd2393aa2e403fefa57d3cb76/source/texk/web2c/luatexdir/lua/ltexlib.c#L434-L625
+      unit =
+        Pattern('bp') + Pattern('BP') +
+        Pattern('cc') + Pattern('CC') +
+        Pattern('cm') + Pattern('CM') +
+        Pattern('dd') + Pattern('DD') +
+        Pattern('em') + Pattern('EM') +
+        Pattern('ex') + Pattern('EX') +
+        Pattern('in') + Pattern('IN') +
+        Pattern('mm') + Pattern('MM') +
+        Pattern('mu') + Pattern('MU') +
+        Pattern('nc') + Pattern('NC') +
+        Pattern('nd') + Pattern('ND') +
+        Pattern('pc') + Pattern('PC') +
+        Pattern('pt') + Pattern('PT') +
+        Pattern('px') + Pattern('PX') +
+        Pattern('sp') + Pattern('SP'),
 
-    -- 'bp' / 'BP' / 'cc' / etc.
-    -- https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua
-    -- https://github.com/TeX-Live/luatex/blob/51db1985f5500dafd2393aa2e403fefa57d3cb76/source/texk/web2c/luatexdir/lua/ltexlib.c#L434-L625
-    unit =
-      Pattern('bp') + Pattern('BP') +
-      Pattern('cc') + Pattern('CC') +
-      Pattern('cm') + Pattern('CM') +
-      Pattern('dd') + Pattern('DD') +
-      Pattern('em') + Pattern('EM') +
-      Pattern('ex') + Pattern('EX') +
-      Pattern('in') + Pattern('IN') +
-      Pattern('mm') + Pattern('MM') +
-      Pattern('mu') + Pattern('MU') +
-      Pattern('nc') + Pattern('NC') +
-      Pattern('nd') + Pattern('ND') +
-      Pattern('pc') + Pattern('PC') +
-      Pattern('pt') + Pattern('PT') +
-      Pattern('px') + Pattern('PX') +
-      Pattern('sp') + Pattern('SP'),
+      ---'"' ('\"' / !'"')* '"'
+      string_quoted =
+        white_space^0 * Pattern(opts.quotation_begin) *
+        CaptureSimple((Pattern('\\' .. opts.quotation_end) + 1 - Pattern(opts.quotation_end))^0) *
+        Pattern(opts.quotation_end) * white_space^0,
 
-    -- '"' ('\"' / !'"')* '"'
-    string_quoted =
-      white_space^0 * Pattern(opts.quotation_begin) *
-      CaptureSimple((Pattern('\\' .. opts.quotation_end) + 1 - Pattern(opts.quotation_end))^0) *
-      Pattern(opts.quotation_end) * white_space^0,
+      string_unquoted =
+        white_space^0 *
+        CaptureSimple(
+          Variable('word_unquoted')^1 *
+          (Set(' \t')^1 * Variable('word_unquoted')^1)^0) *
+        white_space^0,
 
-    string_unquoted =
-      white_space^0 *
-      CaptureSimple(
-        Variable('word_unquoted')^1 *
-        (Set(' \t')^1 * Variable('word_unquoted')^1)^0) *
-      white_space^0,
-
-    word_unquoted = (1 - white_space - Set(
-      opts.group_begin ..
-      opts.group_end ..
-      opts.assignment_operator  ..
-      opts.list_separator))^1
-  })
+      word_unquoted = (1 - white_space - Set(
+        opts.group_begin ..
+        opts.group_end ..
+        opts.assignment_operator  ..
+        opts.list_separator))^1
+    })
 -- LuaFormatter on
-end
+  end
 
-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_tree_recursive(tree,
-    current,
-    result,
-    depth,
-    callback_func)
-    for key, value in pairs(current) do
-      if type(value) == 'table' then
-        value = visit_tree_recursive(tree, value, {}, depth + 1,
-          callback_func)
+  local is = {
+    boolean = function(value)
+      if value == nil then
+        return false
       end
+      if type(value) == 'boolean' then
+        return true
+      end
+      local parser = generate_parser('boolean_only')
+      local result = parser:match(tostring(value))
+      return result ~= nil
+    end,
 
-      key, value = callback_func(key, value, depth, current, tree)
+    dimension = function(value)
+      if value == nil then
+        return false
+      end
+      local parser = generate_parser('dimension_only')
+      local result = parser:match(tostring(value))
+      return result ~= nil
+    end,
 
-      if key ~= nil and value ~= nil then
-        result[key] = value
+    integer = function(value)
+      local n = tonumber(value)
+      if n == nil then
+        return false
       end
-    end
-    if next(result) ~= nil then
-      return result
-    end
-  end
+      return n == math.floor(n)
+    end,
 
-  local result = visit_tree_recursive(tree, tree, {}, 1, callback_func)
+    number = function(value)
+      if value == nil then
+        return false
+      end
+      if type(value) == 'number' then
+        return true
+      end
+      local parser = generate_parser('number_only')
+      local result = parser:match(tostring(value))
+      return result ~= nil
+    end,
 
-  if result == nil then
-    return {}
-  end
-  return result
-end
+    string = function(value)
+      return type(value) == 'string'
+    end,
 
-local is = {
-  boolean = function(value)
-    if value == nil then
-      return false
-    end
-    if type(value) == 'boolean' then
+    list = function(value)
+      if type(value) ~= 'table' then
+        return false
+      end
+
+      for k, _ in pairs(value) do
+        if type(k) ~= 'number' then
+          return false
+        end
+      end
       return true
-    end
-    local parser = generate_parser('boolean_only')
-    local result = parser:match(tostring(value))
-    return result ~= nil
-  end,
+    end,
 
-  dimension = function(value)
-    if value == nil then
-      return false
-    end
-    local parser = generate_parser('dimension_only')
-    local result = parser:match(tostring(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,
-
-  number = function(value)
-    if value == nil then
-      return false
-    end
-    if type(value) == 'number' then
+    any = function(value)
       return true
-    end
-    local parser = generate_parser('number_only')
-    local result = parser:match(tostring(value))
-    return result ~= nil
-  end,
+    end,
+  }
 
-  string = function(value)
-    return type(value) == 'string'
-  end,
+  ---
+  ---Apply the key-value-pair definitions (defs) on an input table in a
+  ---recursive fashion.
+  ---
+  --- at param defs table # A table containing all definitions.
+  --- at param opts table # The parse options table.
+  --- at param input table # The current input table.
+  --- at param output table # The current output table.
+  --- at param unknown table # Always the root unknown table.
+  --- at param key_path table # An array of key names leading to the current
+  --- at 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 = {}
 
-  any = function(value)
-    return true
-  end,
-}
+    local function add_to_key_path(key_path, key)
+      local new_key_path = {}
 
---- Apply the key-value-pair definitions (defs) on an input table in a
---- recursive fashion.
----
---- at param defs table # A table containing all definitions.
---- at param opts table # The parse options table.
---- at param input table # The current input table.
---- at param output table # The current output table.
---- at param unknown table # Always the root unknown table.
---- at param key_path table # An array of key names leading to the current
---- at 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 = {}
+      for index, value in ipairs(key_path) do
+        new_key_path[index] = value
+      end
 
-  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
+      table.insert(new_key_path, key)
+      return new_key_path
     end
 
-    table.insert(new_key_path, key)
-    return new_key_path
-  end
-
-  local function get_default_value(def)
-    if def.default ~= nil then
-      return def.default
-    elseif opts ~= nil and opts.default ~= nil then
-      return opts.default
+    local function get_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
-    return true
-  end
 
-  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 get_default_value(def)
+    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_table(input, search_key) ~= nil then
+        return get_default_value(def)
+      end
     end
-  end
 
-  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))
+    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('E003', {
+                alias1 = used_alias_key,
+                alias2 = alias,
+                key = key,
+              })
+            end
+            used_alias_key = alias
+            alias_value = v
           end
-          used_alias_key = alias
-          alias_value = v
         end
-      end
-      if alias_value ~= nil then
-        return alias_value
-      end
-    end,
+        if alias_value ~= nil then
+          return alias_value
+        end
+      end,
 
-    always_present = function(value, key, def)
-      if value == nil and def.always_present then
-        return get_default_value(def)
-      end
-    end,
+      always_present = function(value, key, def)
+        if value == nil and def.always_present then
+          return get_default_value(def)
+        end
+      end,
 
-    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
+      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('E004', { value = value, choices = def.choices })
+          end
         end
-        if not is_in_choices then
-          throw_error('The value “' .. value ..
-                        '” does not exist in the choices: ' ..
-                        table.concat(def.choices, ', ') .. '!')
+      end,
+
+      data_type = function(value, key, def)
+        if value == nil then
+          return
         end
-      end
-    end,
-
-    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
+        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
+              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
+              local n = tonumber(value)
+              if type(n) == 'number' and n ~= nil then
+                converted = math.floor(n)
+              end
+            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)
+            ---list
+          elseif def.data_type == 'list' then
+            if is.list(value) then
+              converted = value
+            end
           else
-            converted = true
+            throw_error('E005', { data_type = def.data_type })
           end
-          -- dimension
-        elseif def.data_type == 'dimension' then
-          if is.dimension(value) then
-            converted = value
+          if converted == nil then
+            throw_error('E006', {
+              value = value,
+              key = key,
+              data_type = def.data_type,
+            })
+          else
+            return converted
           end
-          -- integer
-        elseif def.data_type == 'integer' then
-          if is.number(value) then
-            local n = tonumber(value)
-            if type(n) == 'number' and n ~= nil then
-              converted = math.floor(n)
-            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('E007', {
+              key = key,
+              exclusive_group = def.exclusive_group,
+              another_key = exclusive_groups[def.exclusive_group],
+            })
+          else
+            exclusive_groups[def.exclusive_group] = key
           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
-          throw_error('Unknown data type: ' .. def.data_type)
         end
-        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,
+      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
+      l3_tl_set = function(value, key, def)
+        if value == nil then
+          return
         end
-      end
-    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,
 
-    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')
+      macro = function(value, key, def)
+        if value == nil then
+          return
         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
+        if def.macro ~= nil then
+          token.set_macro(def.macro, value, 'global')
         end
-      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] = "..." }')
+      match = function(value, key, def)
+        if value == nil then
+          return
         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
+        if def.match ~= nil then
+          if type(def.match) ~= 'string' then
+            throw_error('E008')
+          end
+          local match = string.match(value, def.match)
+          if match == nil then
+            throw_error('E009', {
+              value = value,
+              key = key,
+              match = def.match:gsub('%%', '%%%%'),
+            })
+          else
+            return match
+          end
         end
-      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,
+      opposite_keys = function(value, key, def)
+        if def.opposite_keys ~= nil then
+          local function get_value(key1, key2)
+            local opposite_name
+            if def.opposite_keys[key1] ~= nil then
+              opposite_name = def.opposite_keys[key1]
+            elseif def.opposite_keys[key2] ~= nil then
+              opposite_name = def.opposite_keys[key2]
+            end
+            return opposite_name
+          end
+          local true_key = get_value(true, 1)
+          local false_key = get_value(false, 2)
+          if true_key == nil or false_key == nil then
+            throw_error('E010')
+          end
 
-    pick = function(value, key, def)
-      if def.pick then
-        local pick_types
+          --- at param value string
+          local function remove_values(value)
+            local count = 0
+            while utils.remove_from_table(input, value) do
+              count = count + 1
+            end
+            return count
+          end
 
-        -- Allow old deprecated attribut pick = true
-        if def.pick == true then
-          pick_types = { 'any' }
-        elseif type(def.pick) == 'table' then
-          pick_types = def.pick
-        else
-          pick_types = { def.pick }
-        end
+          local true_count = remove_values(true_key)
+          local false_count = remove_values(false_key)
 
-        -- Check if the pick attribute is valid
-        for _, pick_type in ipairs(pick_types) do
-          if type(pick_type) == 'string' and is[pick_type] == nil then
-            throw_error(
-              'Wrong data type in the “pick” attribute: ' ..
-                tostring(pick_type) ..
-                '. Allowed are: any, boolean, dimension, integer, number, string.')
+          if true_count > 1 then
+            throw_error('E021', { key = true_key })
           end
+
+          if false_count > 1 then
+            throw_error('E021', { key = false_key })
+          end
+
+          if true_count > 0 and false_count > 0 then
+            throw_error('E020',
+              { ['true'] = true_key, ['false'] = false_key })
+          end
+
+          return true_count == 1 or false_count == 0
         end
+      end,
 
-        -- The key has already a value. We leave the function at this
-        -- point to be able to check the pick attribute for errors
-        -- beforehand.
-        if value ~= nil then
-          return value
+      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,
 
-        for _, pick_type in ipairs(pick_types) do
-          for i, v in pairs(input) do
-            -- We can not use ipairs here. `ipairs(t)` iterates up to the
-            -- first absent index. Values are deleted from the `input`
-            -- table.
-            if type(i) == 'number' then
-              local picked_value = nil
-              if is[pick_type](v) then
-                picked_value = v
-              end
+      pick = function(value, key, def)
+        if def.pick then
+          local pick_types
 
-              if picked_value ~= nil then
-                input[i] = nil
-                return picked_value
+          ---Allow old deprecated attribut pick = true
+          if def.pick == true then
+            pick_types = { 'any' }
+          elseif type(def.pick) == 'table' then
+            pick_types = def.pick
+          else
+            pick_types = { def.pick }
+          end
+
+          ---Check if the pick attribute is valid
+          for _, pick_type in ipairs(pick_types) do
+            if type(pick_type) == 'string' and is[pick_type] == nil then
+              throw_error('E011', {
+                unknown = tostring(pick_type),
+                data_types = {
+                  'any',
+                  'boolean',
+                  'dimension',
+                  'integer',
+                  'number',
+                  'string',
+                },
+              })
+            end
+          end
+
+          ---The key has already a value. We leave the function at this
+          ---point to be able to check the pick attribute for errors
+          ---beforehand.
+          if value ~= nil then
+            return value
+          end
+
+          for _, pick_type in ipairs(pick_types) do
+            for i, v in pairs(input) do
+              ---We can not use ipairs here. `ipairs(t)` iterates up to the
+              ---first absent index. Values are deleted from the `input`
+              ---table.
+              if type(i) == 'number' then
+                local picked_value = nil
+                if is[pick_type](v) then
+                  picked_value = v
+                end
+
+                if picked_value ~= nil then
+                  input[i] = nil
+                  return picked_value
+                end
               end
             end
           end
         end
+      end,
+
+      required = function(value, key, def)
+        if def.required ~= nil and def.required and value == nil then
+          throw_error('E012', { key = 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 = utils.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
+      ---`{ key1 = { }, key2 = { } }`
+      if type(def) == 'table' and def.name == nil and type(index) ==
+        'string' then
+        key = index
+        ---`{ { name = 'key1' }, { name = 'key2' } }`
+      elseif type(def) == 'table' and def.name ~= nil then
+        key = def.name
+        ---Definitions as strings in an array: `{ 'key1', 'key2' }`
+      elseif type(index) == 'number' and type(def) == 'string' then
+        key = def
+        def = { default = get_default_value({}) }
       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))
+      if type(def) ~= 'table' then
+        throw_error('E013', { data_type = tostring(def), key = index }) ---key is nil
       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
+      for attr, _ in pairs(def) do
+        if namespace.attrs[attr] == nil then
+          throw_error('E014', {
+            unknown = attr,
+            attr_names = utils.get_table_keys(namespace.attrs),
+          })
         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
+      if key == nil then
+        throw_error('E015')
+      end
 
-  for index, def in pairs(defs) do
-    -- Find key and def
-    local key
-    -- `{ key1 = { }, key2 = { } }`
-    if type(def) == 'table' and def.name == nil and type(index) ==
-      'string' then
-      key = index
-      -- `{ { name = 'key1' }, { name = 'key2' } }`
-    elseif type(def) == 'table' and def.name ~= nil then
-      key = def.name
-      -- Definitions as strings in an array: `{ 'key1', 'key2' }`
-    elseif type(index) == 'number' and type(def) == 'string' then
-      key = def
-      def = { default = get_default_value({}) }
-    end
+      local value = find_value(key, def)
 
-    if type(def) ~= 'table' then
-      throw_error('Key definition must be a table!')
-    end
-
-    for attr, _ in pairs(def) do
-      if namespace.attrs[attr] == nil then
-        throw_error('Unknown definition attribute: ' .. tostring(attr))
+      for _, def_opt in ipairs({
+        'alias',
+        'opposite_keys',
+        'pick',
+        '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
-    end
 
-    if key == nil then
-      throw_error('Key name couldn’t be detected!')
+      output[key] = value
     end
 
-    local value = find_value(key, def)
-
-    for _, def_opt in ipairs({
-      'alias',
-      'opposite_keys',
-      'pick',
-      '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
+    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
-    end
 
-    output[key] = value
-  end
-
-  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] = {}
+      ---Copy all unknown key-value-pairs to the current unknown table.
+      for key, value in pairs(input) do
+        current_unknown[key] = value
       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
+    return output, unknown
   end
 
-  return output, unknown
-end
+  ---
+  ---Parse a LaTeX/TeX style key-value string into a Lua table.
+  ---
+  --- at param kv_string string # A string in the TeX/LaTeX style key-value format as described above.
+  --- at param opts? table # A table containing options.
+  ---
+  --- at return table result # The final result of all individual parsing and normalization steps.
+  --- at return table unknown # A table with unknown, undefinied key-value pairs.
+  --- at return table raw # The unprocessed, raw result of the LPeg parser.
+  local function parse(kv_string, opts)
+    if kv_string == nil then
+      return {}, {}, {}
+    end
 
---- Parse a LaTeX/TeX style key-value string into a Lua table.
----
---- at param kv_string string # A string in the TeX/LaTeX style key-value format as described above.
---- at param opts? table # A table containing the settings:
----   `convert_dimensions`, `unpack`, `naked_as_value`, `converter`,
----   `debug`, `preprocess`, `postprocess`.
---
---- at return table result # The final result of all individual parsing and normalization steps.
---- at return table unknown # A table with unknown, undefinied key-value pairs.
---- at return table raw # The unprocessed, raw result of the LPeg parser.
-local function parse(kv_string, opts)
-  if kv_string == nil then
-    return {}, {}, {}
-  end
+    opts = normalize_opts(opts)
 
-  opts = normalize_opts(opts)
+    if type(opts.hooks.kv_string) == 'function' then
+      kv_string = opts.hooks.kv_string(kv_string)
+    end
 
-  if type(opts.hooks.kv_string) == 'function' then
-    kv_string = opts.hooks.kv_string(kv_string)
-  end
+    local result = generate_parser('list', opts):match(kv_string)
+    local raw = utils.clone_table(result)
 
-  local result = generate_parser('list', opts):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 = utils.visit_tree(result, opts.hooks[name])
+        else
+          opts.hooks[name](result)
+        end
 
-  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
-        opts.hooks[name](result)
+        if opts.debug then
+          print('After the execution of the hook: ' .. name)
+          visualizers.debug(result)
+        end
       end
+    end
 
-      if opts.debug then
-        print('After the execution of the hook: ' .. name)
-        debug(result)
+    local function apply_hooks(at)
+      if at ~= nil then
+        at = '_' .. at
+      else
+        at = ''
       end
+      apply_hook('keys' .. at)
+      apply_hook('result' .. at)
     end
-  end
 
-  local function apply_hooks(at)
-    if at ~= nil then
-      at = '_' .. at
-    else
-      at = ''
-    end
-    apply_hook('keys' .. at)
-    apply_hook('result' .. at)
-  end
+    apply_hooks('before_opts')
 
-  apply_hooks('before_opts')
+    ---
+    ---Normalize the result table of the LPeg parser. This normalization
+    ---tasks are performed on the raw input table coming directly from
+    ---the PEG parser:
+    --
+    --- at param result table # The raw input table coming directly from the PEG parser
+    --- at 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,
 
-  --- Normalize the result table of the LPeg parser. This normalization
-  --  tasks are performed on the raw input table coming directly from
-  --  the PEG parser:
-  --
-  --- at param result table # The raw input table coming directly from the PEG parser
-  --- at 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,
+        process_naked = function(key, value)
+          if type(key) == 'number' and type(value) == 'string' then
+            return value, opts.default
+          end
+          return key, value
+        end,
 
-      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')
+        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('E017', {
+                  unknown = style,
+                  styles = { 'lower', 'snake', 'upper' },
+                })
+              end
             end
           end
-        end
-        return key, value
-      end,
+          return key, value
+        end,
 
-      apply_invert_flag = function(key, value)
-        if type(key) == 'string' and key:find(opts.invert_flag) then
-          return key:gsub(opts.invert_flag, ''), not value
+        apply_invert_flag = function(key, value)
+          if type(key) == 'string' and key:find(opts.invert_flag) then
+            return key:gsub(opts.invert_flag, ''), not value
+          end
+          return key, value
+        end,
+      }
+
+      if opts.unpack then
+        result = utils.visit_tree(result, callbacks.unpack)
+      end
+
+      if not opts.naked_as_value and opts.defs == false then
+        result = utils.visit_tree(result, callbacks.process_naked)
+      end
+
+      if opts.format_keys then
+        if type(opts.format_keys) ~= 'table' then
+          throw_error('E018', { data_type = type(opts.format_keys) })
         end
-        return key, value
-      end,
-    }
+        result = utils.visit_tree(result, callbacks.format_key)
+      end
 
-    if opts.unpack then
-      result = visit_tree(result, callbacks.unpack)
+      if opts.invert_flag then
+        result = utils.visit_tree(result, callbacks.apply_invert_flag)
+      end
+
+      return result
     end
+    result = apply_opts(result, opts)
 
-    if not opts.naked_as_value and opts.defs == false then
-      result = visit_tree(result, callbacks.process_naked)
+    ---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, {},
+        {}, {}, utils.clone_table(result))
     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)
+    apply_hooks()
+
+    if opts.defaults ~= nil and type(opts.defaults) == 'table' then
+      utils.merge_tables(result, opts.defaults, false)
     end
 
-    if opts.invert_flag then
-      result = visit_tree(result, callbacks.apply_invert_flag)
+    if opts.debug then
+      visualizers.debug(result)
     end
 
-    return result
-  end
-  result = apply_opts(result, opts)
+    if opts.accumulated_result ~= nil and type(opts.accumulated_result) ==
+      'table' then
+      utils.merge_tables(opts.accumulated_result, result, true)
+    end
 
-  -- 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))
+    ---no_error
+    if not opts.no_error and type(unknown) == 'table' and
+      utils.get_table_size(unknown) > 0 then
+      throw_error('E019', { unknown = visualizers.render(unknown) })
+    end
+    return result, unknown, raw
   end
 
-  apply_hooks()
+  ---
+  ---A table to store parsed key-value results.
+  local result_store = {}
 
-  if opts.defaults ~= nil and type(opts.defaults) == 'table' then
-    merge_tables(result, opts.defaults)
-  end
+  return {
+    new = main,
 
-  if opts.debug then
-    debug(result)
-  end
+    version = { 0, 12, 0 },
 
-  -- 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
+    --- at see parse
+    parse = parse,
 
---- Store results
--- @section
+    define = function(defs, opts)
+      return function(kv_string, inner_opts)
+        local options
 
---- A table to store parsed key-value results.
-local result_store = {}
+        if inner_opts ~= nil and opts ~= nil then
+          options = utils.merge_tables(opts, inner_opts)
+        elseif inner_opts ~= nil then
+          options = inner_opts
+        elseif opts ~= nil then
+          options = opts
+        end
 
---- Exports
--- @section
+        if options == nil then
+          options = {}
+        end
 
-local export = {
-  version = { 0, 11, 0 },
+        options.defs = defs
 
-  ---Get a new instance of the luakeys module.
-  ---
-  --- at return table # A new instance of the luakeys module.
-  get_private_instance = function()
-    package.loaded.luakeys = nil
-    local luakeys = require('luakeys')
-    package.loaded.luakeys = nil
-    return luakeys
-  end,
+        return parse(kv_string, options)
+      end
+    end,
 
-  namespace = namespace,
+    --- at see default_opts
+    opts = default_opts,
 
-  ---This function is used in the documentation.
-  ---
-  --- at param from string # A key in the namespace table, either `opts`, `hook` or `attrs`.
-  print_names = function(from)
-    local names = {}
-    for name in pairs(namespace[from]) do
-      table.insert(names, name)
-    end
-    table.sort(names)
-    tex.print(table.concat(names, ', '))
-  end,
+    error_messages = error_messages,
 
-  print_default = function(from, name)
-    tex.print(tostring(namespace[from][name]))
-  end,
+    --- at see visualizers.render
+    render = visualizers.render,
 
-  --- @see default_options
-  opts = default_options,
+    --- at see visualizers.stringify
+    stringify = visualizers.stringify,
 
-  --- @see stringify
-  stringify = stringify,
+    --- at see visualizers.debug
+    debug = visualizers.debug,
 
-  --- @see parse
-  parse = parse,
+    ---
+    ---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.
+    ---
+    --- at param identifier string # The identifier under which the result is saved.
+    ---
+    --- at param result table # A result to be stored and that was created by the key-value parser.
+    save = function(identifier, result)
+      result_store[identifier] = result
+    end,
 
-  define = function(defs, opts)
-    return function(kv_string, inner_opts)
-      local options
+    ---The function `get(identifier): table` retrieves a saved result
+    ---from the result store.
+    ---
+    --- at param identifier string # The identifier under which the result was saved.
+    ---
+    --- at return table
+    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,
 
-      if inner_opts ~= nil and opts ~= nil then
-        options = merge_tables(opts, inner_opts)
-      elseif inner_opts ~= nil then
-        options = inner_opts
-      elseif opts ~= nil then
-        options = opts
-      end
+    is = is,
 
-      if options == nil then
-        options = {}
-      end
+    utils = utils,
 
-      options.defs = defs
+    ---
+    ---Exported but intentionally undocumented functions
+    ---
 
-      return parse(kv_string, options)
-    end
-  end,
+    namespace = utils.clone_table(namespace),
 
-  --- @see render
-  render = render,
+    ---
+    ---This function is used in the documentation.
+    ---
+    --- at param from string # A key in the namespace table, either `opts`, `hook` or `attrs`.
+    print_names = function(from)
+      local names = utils.get_table_keys(namespace[from])
+      tex.print(table.concat(names, ', '))
+    end,
 
-  --- @see debug
-  debug = debug,
+    print_default = function(from, name)
+      tex.print(tostring(namespace[from][name]))
+    end,
 
-  --- 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.
-  --
-  --- at param identifier string # The identifier under which the result is saved.
-  --
-  --- at param result table # A result to be stored and that was created by the key-value parser.
-  save = function(identifier, result)
-    result_store[identifier] = result
-  end,
+    ---
+    --- at param exported_table table
+    depublish_functions = function(exported_table)
+      local function warn_global_import()
+        throw_error('E023')
+      end
 
-  --- The function `get(identifier): table` retrieves a saved result
-  --  from the result store.
-  --
-  --- at param identifier string # The identifier under which the result was saved.
-  ---
-  --- at return table
-  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,
+      exported_table.parse = warn_global_import
+      exported_table.define = warn_global_import
+      exported_table.save = warn_global_import
+      exported_table.get = warn_global_import
+    end,
+  }
 
-  is = is,
+end
 
-  utils = utils,
-}
-
-return export
+return main

Modified: trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.sty
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.sty	2023-01-05 00:50:44 UTC (rev 65467)
+++ trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.sty	2023-01-05 21:18:54 UTC (rev 65468)
@@ -1,5 +1,5 @@
 %% luakeys.sty
-%% Copyright 2021-2022 Josef Friedrich
+%% Copyright 2021-2023 Josef Friedrich
 %
 % This work may be distributed and/or modified under the
 % conditions of the LaTeX Project Public License, either version 1.3c
@@ -17,5 +17,14 @@
 % luakeys-debug.sty and luakeys-debug.tex.
 
 \NeedsTeXFormat{LaTeX2e}
-\ProvidesPackage{luakeys}[2022/12/23 0.11.0 Parsing key-value options using Lua.]
-\directlua{luakeys = require('luakeys')}
+\ProvidesPackage{luakeys}[2023/01/05 v0.12.0 Parsing key-value options using Lua.]
+\directlua{
+  if luakeys == nil then
+    luakeys = require('luakeys')()
+    luakeys.depublish_functions(luakeys)
+  end
+}
+
+\def\LuakeysGetPackageOptions{\luaescapestring{\@ptionlist{\@currname.\@currext}}}
+
+\def\LuakeysGetClassOptions{\luaescapestring{\@raw at classoptionslist}}

Modified: trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.tex
===================================================================
--- trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.tex	2023-01-05 00:50:44 UTC (rev 65467)
+++ trunk/Master/texmf-dist/tex/luatex/luakeys/luakeys.tex	2023-01-05 21:18:54 UTC (rev 65468)
@@ -1,5 +1,5 @@
 %% luakeys.tex
-%% Copyright 2021-2022 Josef Friedrich
+%% Copyright 2021-2023 Josef Friedrich
 %
 % This work may be distributed and/or modified under the
 % conditions of the LaTeX Project Public License, either version 1.3c
@@ -16,4 +16,9 @@
 % This work consists of the files luakeys.lua, luakeys.sty, luakeys.tex
 % luakeys-debug.sty and luakeys-debug.tex.
 
-\directlua{luakeys = require('luakeys')}
+\directlua{
+  if luakeys == nil then
+    luakeys = require('luakeys')()
+    luakeys.depublish_functions(luakeys)
+  end
+}



More information about the tex-live-commits mailing list.