summaryrefslogtreecommitdiff
path: root/macros/luatex/generic
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2023-01-06 03:06:05 +0000
committerNorbert Preining <norbert@preining.info>2023-01-06 03:06:05 +0000
commit90bcf28fa05846213901e6a0fff80656a13bc701 (patch)
treef2871bd21df7a65090c45517d3b9769a34f3cf6c /macros/luatex/generic
parent7b266590db3f49ad361a129142e0531d2acc1b36 (diff)
CTAN sync 202301060306
Diffstat (limited to 'macros/luatex/generic')
-rw-r--r--macros/luatex/generic/luakeys/README.md33
-rw-r--r--macros/luatex/generic/luakeys/documentation.tex (renamed from macros/luatex/generic/luakeys/luakeys-doc.tex)403
-rw-r--r--macros/luatex/generic/luakeys/luakeys-debug.sty4
-rw-r--r--macros/luatex/generic/luakeys/luakeys-debug.tex4
-rw-r--r--macros/luatex/generic/luakeys/luakeys-doc.pdfbin398632 -> 0 bytes
-rw-r--r--macros/luatex/generic/luakeys/luakeys.lua2734
-rw-r--r--macros/luatex/generic/luakeys/luakeys.pdfbin0 -> 427721 bytes
-rw-r--r--macros/luatex/generic/luakeys/luakeys.sty15
-rw-r--r--macros/luatex/generic/luakeys/luakeys.tex9
9 files changed, 1938 insertions, 1264 deletions
diff --git a/macros/luatex/generic/luakeys/README.md b/macros/luatex/generic/luakeys/README.md
index 2779de92e8..b4a0ddb8a8 100644
--- a/macros/luatex/generic/luakeys/README.md
+++ b/macros/luatex/generic/luakeys/README.md
@@ -14,7 +14,7 @@ mechanism should be pretty robust.
## 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 @@ local defs = {
-- 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 @@ local defs = {
-- 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 @@ local defs = {
```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 @@ local result = luakeys.parse('one,two,three', opts)
[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 @@ This project uses [semantic versioning](https://semver.org).
Update version in:
-* luakeys-doc.tex
+* documentation.tex
* luakeys-debug.sty
* luakeys.sty
* luakeys.lua
diff --git a/macros/luatex/generic/luakeys/luakeys-doc.tex b/macros/luatex/generic/luakeys/documentation.tex
index 90041ec581..42da7c1acc 100644
--- a/macros/luatex/generic/luakeys/luakeys-doc.tex
+++ b/macros/luatex/generic/luakeys/documentation.tex
@@ -13,9 +13,9 @@
\usepackage{syntax}
\usepackage{graphicx}
+\definecolor{bg}{rgb}{0.95,0.95,0.95}
+
\usemintedstyle{friendly}
-\BeforeBeginEnvironment{minted}{\begin{mdframed}}
-\AfterEndEnvironment{minted}{\end{mdframed}}
\setminted{
breaklines=true,
fontsize=\footnotesize,
@@ -25,15 +25,19 @@
\def\latex#1{\mintinline{latex}|#1|}
\NewDocumentCommand { \InputLatex } { O{} m } {
- \begin{mdframed}
- \inputminted[linenos=false,#1]{latex}{examples/#2}
- \end{mdframed}
+ \inputminted[
+ linenos=false,
+ bgcolor=bg,
+ #1
+ ]{latex}{examples/#2}
}
\NewDocumentCommand { \InputLua } { O{} m } {
- \begin{mdframed}
- \inputminted[linenos=false,#1]{lua}{examples/#2}
- \end{mdframed}
+ \inputminted[
+ linenos=false,
+ bgcolor=bg,
+ #1
+ ]{lua}{examples/#2}
}
\catcode`_=12
@@ -60,7 +64,7 @@ The default value of the option “\texttt{#1}” is:
\href{https://github.com/Josef-Friedrich/luakeys}
{github.com/Josef-Friedrich/luakeys}%
}
-\date{0.11.0 from 2022/12/23}
+\date{v0.12.0 from 2023/01/05}
\maketitle
@@ -103,7 +107,7 @@ Result:
\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
+|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},
@@ -137,6 +141,18 @@ The TUGboat article
{“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}
@@ -154,9 +170,8 @@ No. 2)}.
% 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.
+% 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
@@ -222,18 +237,10 @@ wrapper files.
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}.
+% 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}
@@ -245,22 +252,13 @@ It loads the Lua module into the global variable \texttt{luakeys}.
% \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}.
+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}
@@ -270,27 +268,15 @@ It does the same as the Lua\LaTeX{} wrapper and loads the Lua module
\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=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}
+\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.
@@ -334,34 +320,6 @@ The raw result of the Lpeg grammar parser. \\
%
%%
-\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}
@@ -391,7 +349,7 @@ The \lua{parse} function can be called with an options table.
This options are supported: \catcode`_=12
\directlua{luakeys.print_names('opts')}
-\InputLua[firstline=5,lastline=94]{opts/all-opts.lua}
+\InputLua[firstline=7,lastline=99]{opts/all-opts.lua}
\subsection{Table “\texttt{opts}”}
\label{table-opts}
@@ -419,6 +377,23 @@ set the options globally, it is recommended to use the
%
%%
+\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}
@@ -486,7 +461,7 @@ tex.print(number.todimen(tex.sp('1cm'), 'cm', '%0.0F%s'))
% 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
+If the option \lua{debug} is set to true, the result table is printed to
the console.
\InputLatex{opts/debug-latex.tex}
@@ -969,7 +944,7 @@ These attributes are allowed:
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}
+\InputLua[firstline=5,lastline=62]{defs/all-attrs.lua}
%%
%
@@ -1034,8 +1009,8 @@ choices:
\subsubsection{Attribute “\texttt{data_type}”}
% source: Python argparse documentation.
-The \lua{data_type} attribute allows type-checking and type conversions to
-be performed.
+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:
@@ -1043,7 +1018,8 @@ The following data types are supported:
\lua{'dimension'},
\lua{'integer'},
\lua{'number'},
-\lua{'string'}.
+\lua{'string'},
+\lua{'list'}.
%
% Bei den drei Datentypen integer, number, dimension kann eine
% Typenumwandlung scheitern.
@@ -1080,6 +1056,17 @@ value can be specified for all naked keys.
%
%%
+\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,
@@ -1108,48 +1095,6 @@ thrown.
%
%%
-\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.
@@ -1251,6 +1196,59 @@ Instead of ...
%
%%
+\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
@@ -1278,13 +1276,7 @@ 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, ', '))
-}.
+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
@@ -1488,9 +1480,9 @@ result store.
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
+% 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
@@ -1513,6 +1505,9 @@ For example, the string \lua{'true'} is recognized by the
\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
@@ -1526,6 +1521,44 @@ 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).
@@ -1848,7 +1881,7 @@ 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
+% Alle erkannten Datentypen können als eigenständige Werte verwendet
% werden.
All recognized data types can be used as standalone values.
@@ -1883,31 +1916,40 @@ between 0 and 1 is allowed and converted into
\subsection{Process document class options}
% Auf die Optionen einer \LaTeX{} Dokumentenklasse kann über das Macro
-% \latex{\@classoptionslist} zu gegriffen werden.
+% \latex{\LuakeysGetClassOptions} 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.
+\latex{\LuakeysGetClassOptions} macro.
%
-% 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.
+% \latex{\LuakeysGetClassOptions} ist ein anderer Name für
+\latex{\LuakeysGetClassOptions} is an alias for
+
+\begin{center}
+\latex{\luaescapestring{\@raw@classoptionslist}}.
+\end{center}
\InputLatex{class-options/test-class.cls}
\InputLatex{class-options/use-test-class-latex.tex}
-\begin{minted}{lua}
-{
- [1] = '12pt',
- [2] = 'landscape',
-}
-\end{minted}
+%%
+%
+%%
+
+\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}
%-----------------------------------------------------------------------
%
@@ -2027,30 +2069,30 @@ An example of how to use the command in \LaTeX:
\inputminted[linenos=true]{latex}{luakeys-debug.sty}
-\changes{0.1.0}{2021/01/18}{Inital release}
-\changes{0.2.0}{2021/09/19}{
+\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{0.3.0}{2021/11/05}{
+\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{0.4.0}{2021/12/31}{
+\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{0.5.0}{2022/04/04}{
+\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{0.6.0}{2022/06/09}{
+\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”.
@@ -2063,7 +2105,7 @@ An example of how to use the command in \LaTeX:
produce the previous effect.
* The function “print()” is now called “debug()”.
}
-\changes{0.7.0}{2022/07/06}{
+\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.
@@ -2071,13 +2113,13 @@ An example of how to use the command in \LaTeX:
that means scan for tokens that are enclosed in square brackets.
* Extend and improve the documentation.
}
-\changes{0.8.0}{2022/11/17}{
+\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{0.9.0}{2022/11/21}{
+\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
@@ -2085,15 +2127,32 @@ An example of how to use the command in \LaTeX:
* Add a new function called “any(value)” in the “is” table that accepts
any data type.
}
-\changes{0.10.0}{2022/12/16}{
+\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{0.11.0}{2022/12/23}{
+\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
diff --git a/macros/luatex/generic/luakeys/luakeys-debug.sty b/macros/luatex/generic/luakeys/luakeys-debug.sty
index ceefd6d0e2..aa82ed78be 100644
--- a/macros/luatex/generic/luakeys/luakeys-debug.sty
+++ b/macros/luatex/generic/luakeys/luakeys-debug.sty
@@ -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
diff --git a/macros/luatex/generic/luakeys/luakeys-debug.tex b/macros/luatex/generic/luakeys/luakeys-debug.tex
index b78951779b..0b4830f779 100644
--- a/macros/luatex/generic/luakeys/luakeys-debug.tex
+++ b/macros/luatex/generic/luakeys/luakeys-debug.tex
@@ -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%
diff --git a/macros/luatex/generic/luakeys/luakeys-doc.pdf b/macros/luatex/generic/luakeys/luakeys-doc.pdf
deleted file mode 100644
index ff012a46c6..0000000000
--- a/macros/luatex/generic/luakeys/luakeys-doc.pdf
+++ /dev/null
Binary files differ
diff --git a/macros/luatex/generic/luakeys/luakeys.lua b/macros/luatex/generic/luakeys/luakeys.lua
index 7e86d729cd..c3da56e716 100644
--- a/macros/luatex/generic/luakeys/luakeys.lua
+++ b/macros/luatex/generic/luakeys/luakeys.lua
@@ -1,110 +1,126 @@
--- 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.
+---
+---@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,
}
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.
----
----@see https://stackoverflow.com/a/1283608/10193818
----
----@param target table
----@param source table
----
----@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
+ ---
+ ---@param target table # The target table where all values are copied.
+ ---@param source table # The source table from which all values are copied.
+ ---@param overwrite? boolean # Overwrite the values in the target table if they are present (default true).
+ ---
+ ---@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.
----
----@see http://lua-users.org/wiki/CopyTable
----
----@param orig table
----
----@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
+ ---
+ ---@param source table # The source table to be cloned.
+ ---
+ ---@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.
---
- ---@param value table # A table or any input.
+ ---Remove an element from a table.
---
- ---@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 count = 0
- if type(value) == 'table' then
- for _ in ipairs(value) do
- count = count + 1
+ ---@param source table
+ ---@param value any
+ ---
+ ---@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
- return count
- end,
+ end
+
+ ---
+ ---@param source table
+ ---
+ ---@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.
---
- ---@param value table|any # A table or any input.
+ ---@param value any # A table or any input.
---
---@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_table_size(value)
local count = 0
if type(value) == 'table' then
for _ in pairs(value) do
@@ -112,28 +128,34 @@ local utils = {
end
end
return count
- end,
-
- merge_tables = merge_tables,
-
- clone_table = clone_table,
+ end
- remove_from_array = function(array, element)
- for index, value in pairs(array) do
- if element == value then
- array[index] = nil
- return value
+ ---
+ ---Get the size of an array like table, for example `{ 'one', 'two',
+ ---'three' }` = 3.
+ ---
+ ---@param value any # A table or any input.
+ ---
+ ---@return number # The size of the array like table. 0 if the input is no table or the table is empty.
+ local function get_array_size(value)
+ local count = 0
+ if type(value) == 'table' then
+ for _ in ipairs(value) do
+ count = count + 1
end
end
- end,
+ return count
+ end
+ ---
---Scan for an optional argument.
---
---@param initial_delimiter? string # The character that marks the beginning of an optional argument (by default `[`).
---@param end_delimiter? string # The character that marks the end of an optional argument (by default `]`).
---
---@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 @@ local utils = {
else
token.put_next(t)
end
- end,
-}
+ end
+
+ ---
+ ---Throw a single error message.
+ ---
+ ---@param message string
+ ---@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.
+ ---
+ ---@param error_messages table
+ ---@param error_code string
+ ---@param args? table
+ local function throw_error_code(error_messages,
+ error_code,
+ args)
+ local template = error_messages[error_code]
+
+ ---
+ ---@param message string
+ ---@param a table
+ ---
+ ---@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
+
+ ---
+ ---@param list table
+ ---@param a table
+ ---
+ ---@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
+
+ ---
+ ---@type string
+ local message
+ ---@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
+
+ ---@alias ColorName 'black'|'red'|'green'|'yellow'|'blue'|'magenta'|'cyan'|'white'|'reset'
+ ---@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()
+
+ ---
+ ---@param code integer
+ ---
+ ---@return string
+ local function format_color_code(code)
+ return string.char(27) .. '[' .. tostring(code) .. 'm'
+ end
+
+ ---
+ ---@private
+ ---
+ ---@param color ColorName # A color name.
+ ---@param mode? ColorMode
+ ---@param background? boolean # Colorize the background not the text.
+ ---
+ ---@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
+
+ ---
+ ---@param text any
+ ---@param color ColorName # A color name.
+ ---@param mode? ColorMode
+ ---@param background? boolean # Colorize the background not the text.
+ ---
+ ---@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,
+
+ ---
+ ---@param text any
+ ---
+ ---@return string
+ red = function(text)
+ return colorize(text, 'red')
+ end,
+
+ ---
+ ---@param text any
+ ---
+ ---@return string
+ green = function(text)
+ return colorize(text, 'green')
+ end,
+
+ ---@return string
+ yellow = function(text)
+ return colorize(text, 'yellow')
+ end,
+
+ ---
+ ---@param text any
+ ---
+ ---@return string
+ blue = function(text)
+ return colorize(text, 'blue')
+ end,
+
+ ---
+ ---@param text any
+ ---
+ ---@return string
+ magenta = function(text)
+ return colorize(text, 'magenta')
+ end,
+
+ ---
+ ---@param text any
+ ---
+ ---@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()
+ ---@private
+ local opts = { level = 0 }
+
+ ---@private
+ local function print_message(message, ...)
+ print(string.format(message, ...))
+ end
+
+ ---
+ ---Set the log level.
+ ---
+ ---@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).
+ ---
+ ---@param message string
+ ---@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).
+ ---
+ ---@param message string
+ ---@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).
+ ---
+ ---@param message string
+ ---@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).
+ ---
+ ---@param message string
+ ---@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).
+ ---
+ ---@param message string
+ ---@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
+---@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.
+ ---
+ ---@param result table # A table to be converted into a key-value string.
+ ---
+ ---@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.
+ ---
+ ---@see https://stackoverflow.com/a/54593224/10193818
+ ---
+ ---@param result table # A table to stringify.
+ ---@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.
+ ---
+ ---@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.
+ ---
+ ---@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 @@ local namespace = {
choices = true,
data_type = true,
default = true,
+ description = true,
exclusive_group = true,
l3_tl_set = true,
macro = true,
@@ -220,1151 +835,1116 @@ local namespace = {
required = true,
sub_keys = true,
},
-}
-
---- The default options.
-local default_options = clone_table(namespace.opts)
-local function throw_error(message)
- if type(tex.error) == 'function' then
- tex.error(message)
- else
- error(message)
- end
-end
+ 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!',
+
+ 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',
+
+ ---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()',
+ },
+ },
+ },
+}
---- Normalize the parse options.
----
----@param opts? table # Options in a raw format. The table may be empty or some keys are not set.
---
----@return table
-local function normalize_opts(opts)
- if type(opts) ~= 'table' then
- opts = {}
- end
- for key, _ in pairs(opts) do
- if 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
+---@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
+ ---
+ ---@param error_code string
+ ---@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.
----
----@param result table # A table to be converted into a key-value string.
----
----@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.
+ ---
+ ---@param opts? table # Options in a raw format. The table may be empty or some keys are not set.
+ ---
+ ---@return table
+ local function normalize_opts(opts)
+ if type(opts) ~= 'table' then
+ opts = {}
end
- for key, 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.
----
----@see https://stackoverflow.com/a/54593224/10193818
----
----@param result table # A table to stringify.
----@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.
----
----@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
+ ---@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)
+ ---
+ ---@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`).
+ ---@param opts? table # Whether the dimensions should be converted to scaled points (by default `false`).
+ ---
+ ---@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.
---
----@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)
----
----@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`).
----@param opts? table # Whether the dimensions should be converted to scaled points (by default `false`).
----
----@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.
+ ---
+ ---@param input string
+ ---
+ ---@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.
- ---
- ---@param input string
- ---
- ---@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.
+ ---
+ ---@param result table # The result table to which an additional key-value pair or value should to be added
+ ---@param arg1 any # The key or the value.
+ ---@param arg2? any # Always the value.
+ ---
+ ---@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.
- --
- ---@param result table # The result table to which an additional key-value pair or value should to be added
- ---@param arg1 any # The key or the value.
- ---@param arg2? any # Always the value.
- ---
- ---@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 '}'
- 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,
-
- -- 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'),
-
- -- 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,
-
- -- boolean_true / boolean_false
- boolean =
- (
- Variable('boolean_true') * CaptureConstant(true) +
- Variable('boolean_false') * CaptureConstant(false)
+ ---list_item*
+ list = CaptureFolding(
+ CaptureTable('') * Variable('list_item')^0,
+ add_to_table
),
- boolean_true = line_up_pattern(opts.true_aliases),
-
- boolean_false = line_up_pattern(opts.false_aliases),
-
- -- for is.dimension()
- dimension_only = Variable('dimension') * -1,
-
- dimension = (
- Variable('tex_number') * white_space^0 *
- Variable('unit')
- ) / capture_dimension,
-
- -- for is.number()
- number_only = Variable('number') * -1,
-
- -- 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 = Set('-+'),
-
- fractional = Pattern('.') * Variable('integer')^1,
-
- integer = Range('09')^1,
-
- -- 'bp' / 'BP' / 'cc' / etc.
- -- https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua
- -- 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_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
- })
+ ---'{' 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,
+
+ ---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'),
+
+ ---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,
+
+ ---boolean_true / boolean_false
+ boolean =
+ (
+ Variable('boolean_true') * CaptureConstant(true) +
+ Variable('boolean_false') * CaptureConstant(false)
+ ),
+
+ boolean_true = line_up_pattern(opts.true_aliases),
+
+ boolean_false = line_up_pattern(opts.false_aliases),
+
+ ---for is.dimension()
+ dimension_only = Variable('dimension') * -1,
+
+ dimension = (
+ Variable('tex_number') * white_space^0 *
+ Variable('unit')
+ ) / capture_dimension,
+
+ ---for is.number()
+ number_only = Variable('number') * -1,
+
+ ---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 = Set('-+'),
+
+ fractional = Pattern('.') * Variable('integer')^1,
+
+ integer = Range('09')^1,
+
+ ---'bp' / 'BP' / 'cc' / etc.
+ ---https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua
+ ---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_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
+ })
-- LuaFormatter on
-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
- return true
- end
- local parser = generate_parser('boolean_only')
- local result = parser:match(tostring(value))
- return result ~= nil
- 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,
+ list = function(value)
+ if type(value) ~= 'table' then
+ return false
+ end
- number = function(value)
- if value == nil then
- return false
- end
- if type(value) == 'number' then
+ for k, _ in pairs(value) do
+ if type(k) ~= 'number' then
+ return false
+ end
+ end
return true
- end
- local parser = generate_parser('number_only')
- local result = parser:match(tostring(value))
- return result ~= nil
- end,
-
- string = function(value)
- return type(value) == 'string'
- end,
-
- any = function(value)
- return true
- end,
-}
-
---- Apply the key-value-pair definitions (defs) on an input table in a
---- recursive fashion.
----
----@param defs table # A table containing all definitions.
----@param opts table # The parse options table.
----@param input table # The current input table.
----@param output table # The current output table.
----@param unknown table # Always the root unknown table.
----@param key_path table # An array of key names leading to the current
----@param input_root table # The root input table input and output table.
-local function apply_definitions(defs,
- opts,
- input,
- output,
- unknown,
- key_path,
- input_root)
- local exclusive_groups = {}
-
- local function add_to_key_path(key_path, key)
- local new_key_path = {}
-
- for index, value in ipairs(key_path) do
- new_key_path[index] = value
- end
+ end,
- table.insert(new_key_path, key)
- return new_key_path
- end
+ any = function(value)
+ return true
+ 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
- end
- return true
- end
+ ---
+ ---Apply the key-value-pair definitions (defs) on an input table in a
+ ---recursive fashion.
+ ---
+ ---@param defs table # A table containing all definitions.
+ ---@param opts table # The parse options table.
+ ---@param input table # The current input table.
+ ---@param output table # The current output table.
+ ---@param unknown table # Always the root unknown table.
+ ---@param key_path table # An array of key names leading to the current
+ ---@param input_root table # The root input table input and output table.
+ local function apply_definitions(defs,
+ opts,
+ input,
+ output,
+ unknown,
+ key_path,
+ input_root)
+ local exclusive_groups = {}
+
+ local function add_to_key_path(key_path, key)
+ local new_key_path = {}
+
+ for index, value in ipairs(key_path) do
+ new_key_path[index] = value
+ end
- 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)
+ table.insert(new_key_path, key)
+ return new_key_path
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))
- end
- used_alias_key = alias
- alias_value = v
- end
- end
- if alias_value ~= nil then
- return alias_value
+ 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
- end,
+ return true
+ end
- always_present = function(value, key, def)
- if value == nil and def.always_present then
+ 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
- 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
+ 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
end
- if not is_in_choices then
- throw_error('The value “' .. value ..
- '” does not exist in the choices: ' ..
- table.concat(def.choices, ', ') .. '!')
+ if alias_value ~= nil then
+ return alias_value
end
- 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
- else
- converted = true
+ 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
+ end
end
- -- dimension
- elseif def.data_type == 'dimension' then
- if is.dimension(value) then
- converted = value
+ if not is_in_choices then
+ throw_error('E004', { value = value, choices = def.choices })
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,
+
+ data_type = function(value, key, def)
+ if value == nil then
+ return
+ end
+ if def.data_type ~= nil then
+ local converted
+ ---boolean
+ if def.data_type == 'boolean' then
+ if value == 0 or value == '' or not value then
+ converted = false
+ else
+ 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
+ throw_error('E005', { data_type = def.data_type })
end
- -- number
- elseif def.data_type == 'number' then
- if is.number(value) then
- converted = tonumber(value)
+ if converted == nil then
+ throw_error('E006', {
+ value = value,
+ key = key,
+ data_type = def.data_type,
+ })
+ else
+ return converted
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
+ exclusive_group = function(value, key, def)
+ if value == nil then
+ return
end
- end
- end,
-
- l3_tl_set = function(value, key, def)
- if value == nil then
- return
- end
- if def.l3_tl_set ~= nil then
- tex.print(l3_code_cctab,
- '\\tl_set:Nn \\g_' .. def.l3_tl_set .. '_tl')
- tex.print('{' .. value .. '}')
- end
- end,
+ 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
+ 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,
+ 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,
- 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
+ ---@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)
+
+ if true_count > 1 then
+ throw_error('E021', { key = true_key })
+ end
+
+ if false_count > 1 then
+ throw_error('E021', { key = false_key })
+ 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(
- 'Wrong data type in the “pick” attribute: ' ..
- tostring(pick_type) ..
- '. Allowed are: any, boolean, dimension, integer, number, string.')
+ 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
- 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
+
+ ---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
- end
- end
- end,
- required = function(value, key, def)
- if def.required ~= nil and def.required and value == nil then
- throw_error(string.format('Missing required key “%s”!', key))
- end
- end,
+ ---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
- 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 _, 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
- 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,
+
+ required = function(value, key, def)
+ if def.required ~= nil and def.required and value == nil then
+ throw_error('E012', { key = key })
end
- 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
+ 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,
+ }
- 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({}) }
+ ---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 type(def) ~= 'table' then
- throw_error('Key definition must be a table!')
+ if unknown == nil then
+ unknown = {}
+ end
+ if key_path == nil then
+ key_path = {}
end
- for attr, _ in pairs(def) do
- if namespace.attrs[attr] == nil then
- throw_error('Unknown definition attribute: ' .. tostring(attr))
+ 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
- if key == nil then
- throw_error('Key name couldn’t be detected!')
- end
+ if type(def) ~= 'table' then
+ throw_error('E013', { data_type = tostring(def), key = index }) ---key is nil
+ 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
+ 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
end
- end
- output[key] = value
- end
+ if key == nil then
+ throw_error('E015')
+ 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] = {}
+ 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
+ end
+ end
end
- current_unknown = current_unknown[key]
+
+ output[key] = value
end
- -- Copy all unknown key-value-pairs to the current unknown table.
- for key, value in pairs(input) do
- current_unknown[key] = 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
+
+ ---Copy all unknown key-value-pairs to the current unknown table.
+ for key, value in pairs(input) do
+ current_unknown[key] = value
+ end
end
+
+ return output, unknown
end
- return output, unknown
-end
+ ---
+ ---Parse a LaTeX/TeX style key-value string into a Lua table.
+ ---
+ ---@param kv_string string # A string in the TeX/LaTeX style key-value format as described above.
+ ---@param opts? table # A table containing options.
+ ---
+ ---@return table result # The final result of all individual parsing and normalization steps.
+ ---@return table unknown # A table with unknown, undefinied key-value pairs.
+ ---@return table raw # The unprocessed, raw result of the LPeg parser.
+ local function parse(kv_string, opts)
+ if kv_string == nil then
+ return {}, {}, {}
+ end
---- Parse a LaTeX/TeX style key-value string into a Lua table.
----
----@param kv_string string # A string in the TeX/LaTeX style key-value format as described above.
----@param opts? table # A table containing the settings:
---- `convert_dimensions`, `unpack`, `naked_as_value`, `converter`,
---- `debug`, `preprocess`, `postprocess`.
---
----@return table result # The final result of all individual parsing and normalization steps.
----@return table unknown # A table with unknown, undefinied key-value pairs.
----@return table raw # The unprocessed, raw result of the LPeg parser.
-local function parse(kv_string, opts)
- if kv_string == nil then
- return {}, {}, {}
- end
+ 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')
+
+ ---
+ ---Normalize the result table of the LPeg parser. This normalization
+ ---tasks are performed on the raw input table coming directly from
+ ---the PEG parser:
+ --
+ ---@param result table # The raw input table coming directly from the PEG parser
+ ---@param opts table # Some options.
+ local function apply_opts(result, opts)
+ local callbacks = {
+ unpack = function(key, value)
+ if type(value) == 'table' and utils.get_array_size(value) == 1 and
+ utils.get_table_size(value) == 1 and type(value[1]) ~=
+ 'table' then
+ return key, value[1]
+ end
+ return key, value
+ end,
- apply_hooks('before_opts')
+ 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('E017', {
+ unknown = style,
+ styles = { 'lower', 'snake', 'upper' },
+ })
+ end
+ end
+ 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:
- --
- ---@param result table # The raw input table coming directly from the PEG parser
- ---@param opts table # Some options.
- local function apply_opts(result, opts)
- local callbacks = {
- unpack = function(key, value)
- if type(value) == 'table' and utils.get_array_size(value) == 1 and
- utils.get_table_size(value) == 1 and type(value[1]) ~= 'table' then
- return key, value[1]
- end
- return key, value
- end,
+ 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,
+ }
- process_naked = function(key, value)
- if type(key) == 'number' and type(value) == 'string' then
- return value, opts.default
- end
- return key, value
- end,
+ if opts.unpack then
+ result = utils.visit_tree(result, callbacks.unpack)
+ end
- format_key = function(key, value)
- if type(key) == 'string' then
- for _, style in ipairs(opts.format_keys) do
- if style == 'lower' then
- key = key:lower()
- elseif style == 'snake' then
- key = key:gsub('[^%w]+', '_')
- elseif style == 'upper' then
- key = key:upper()
- else
- throw_error('Unknown style to format keys: ' ..
- tostring(style) ..
- ' Allowed styles are: lower, snake, upper')
- end
- end
- end
- return key, value
- end,
+ if not opts.naked_as_value and opts.defs == false then
+ result = utils.visit_tree(result, callbacks.process_naked)
+ 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
+ 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.invert_flag then
+ result = utils.visit_tree(result, callbacks.apply_invert_flag)
+ end
- if opts.unpack then
- result = visit_tree(result, callbacks.unpack)
+ return result
end
+ result = apply_opts(result, opts)
+
+ ---All unknown keys are stored in this table
+ local unknown = nil
+ if type(opts.defs) == 'table' then
+ apply_hooks('before_defs')
+ result, unknown = apply_definitions(opts.defs, opts, result, {},
+ {}, {}, utils.clone_table(result))
+ end
+
+ apply_hooks()
- if not opts.naked_as_value and opts.defs == false then
- result = visit_tree(result, callbacks.process_naked)
+ if opts.defaults ~= nil and type(opts.defaults) == 'table' then
+ utils.merge_tables(result, opts.defaults, false)
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)
+ if opts.debug then
+ visualizers.debug(result)
end
- if opts.invert_flag then
- result = visit_tree(result, callbacks.apply_invert_flag)
+ if opts.accumulated_result ~= nil and type(opts.accumulated_result) ==
+ 'table' then
+ utils.merge_tables(opts.accumulated_result, result, true)
end
- return result
- end
- result = apply_opts(result, opts)
-
- -- All unknown keys are stored in this table
- local unknown = nil
- if type(opts.defs) == 'table' then
- apply_hooks('before_defs')
- result, unknown = apply_definitions(opts.defs, opts, result, {}, {},
- {}, clone_table(result))
+ ---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()
-
- if opts.defaults ~= nil and type(opts.defaults) == 'table' then
- merge_tables(result, opts.defaults)
- end
+ ---
+ ---A table to store parsed key-value results.
+ local result_store = {}
- if opts.debug then
- debug(result)
- end
+ return {
+ new = main,
- -- 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
+ version = { 0, 12, 0 },
---- Store results
--- @section
+ ---@see parse
+ parse = parse,
---- A table to store parsed key-value results.
-local result_store = {}
+ define = function(defs, opts)
+ return function(kv_string, inner_opts)
+ local options
---- Exports
--- @section
+ 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
-local export = {
- version = { 0, 11, 0 },
+ if options == nil then
+ options = {}
+ end
- ---Get a new instance of the luakeys module.
- ---
- ---@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,
+ options.defs = defs
- namespace = namespace,
+ return parse(kv_string, options)
+ end
+ end,
- ---This function is used in the documentation.
- ---
- ---@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,
+ ---@see default_opts
+ opts = default_opts,
- print_default = function(from, name)
- tex.print(tostring(namespace[from][name]))
- end,
+ error_messages = error_messages,
- --- @see default_options
- opts = default_options,
+ ---@see visualizers.render
+ render = visualizers.render,
- --- @see stringify
- stringify = stringify,
+ ---@see visualizers.stringify
+ stringify = visualizers.stringify,
- --- @see parse
- parse = parse,
+ ---@see visualizers.debug
+ debug = visualizers.debug,
- define = function(defs, opts)
- return function(kv_string, inner_opts)
- local options
+ ---
+ ---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.
+ ---
+ ---@param identifier string # The identifier under which the result is saved.
+ ---
+ ---@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,
- 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
+ ---The function `get(identifier): table` retrieves a saved result
+ ---from the result store.
+ ---
+ ---@param identifier string # The identifier under which the result was saved.
+ ---
+ ---@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 options == nil then
- options = {}
- end
+ is = is,
- options.defs = defs
+ utils = utils,
- return parse(kv_string, options)
- end
- end,
+ ---
+ ---Exported but intentionally undocumented functions
+ ---
- --- @see render
- render = render,
+ namespace = utils.clone_table(namespace),
- --- @see debug
- debug = debug,
+ ---
+ ---This function is used in the documentation.
+ ---
+ ---@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,
- --- 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.
- --
- ---@param identifier string # The identifier under which the result is saved.
- --
- ---@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,
+ print_default = function(from, name)
+ tex.print(tostring(namespace[from][name]))
+ end,
- --- The function `get(identifier): table` retrieves a saved result
- -- from the result store.
- --
- ---@param identifier string # The identifier under which the result was saved.
- ---
- ---@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,
+ ---
+ ---@param exported_table table
+ depublish_functions = function(exported_table)
+ local function warn_global_import()
+ throw_error('E023')
+ end
- is = is,
+ 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,
+ }
- utils = utils,
-}
+end
-return export
+return main
diff --git a/macros/luatex/generic/luakeys/luakeys.pdf b/macros/luatex/generic/luakeys/luakeys.pdf
new file mode 100644
index 0000000000..5d53f1a31a
--- /dev/null
+++ b/macros/luatex/generic/luakeys/luakeys.pdf
Binary files differ
diff --git a/macros/luatex/generic/luakeys/luakeys.sty b/macros/luatex/generic/luakeys/luakeys.sty
index 8cb59cf5ef..4b5432faa3 100644
--- a/macros/luatex/generic/luakeys/luakeys.sty
+++ b/macros/luatex/generic/luakeys/luakeys.sty
@@ -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@classoptionslist}}
diff --git a/macros/luatex/generic/luakeys/luakeys.tex b/macros/luatex/generic/luakeys/luakeys.tex
index c3c69a0ba3..f993e7c83f 100644
--- a/macros/luatex/generic/luakeys/luakeys.tex
+++ b/macros/luatex/generic/luakeys/luakeys.tex
@@ -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
+}