From 90bcf28fa05846213901e6a0fff80656a13bc701 Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Fri, 6 Jan 2023 03:06:05 +0000 Subject: CTAN sync 202301060306 --- macros/luatex/generic/luakeys/README.md | 33 +- macros/luatex/generic/luakeys/documentation.tex | 2161 ++++++++++++++++++ macros/luatex/generic/luakeys/luakeys-debug.sty | 4 +- macros/luatex/generic/luakeys/luakeys-debug.tex | 4 +- macros/luatex/generic/luakeys/luakeys-doc.pdf | Bin 398632 -> 0 bytes macros/luatex/generic/luakeys/luakeys-doc.tex | 2102 ----------------- macros/luatex/generic/luakeys/luakeys.lua | 2740 ++++++++++++++--------- macros/luatex/generic/luakeys/luakeys.pdf | Bin 0 -> 427721 bytes macros/luatex/generic/luakeys/luakeys.sty | 15 +- macros/luatex/generic/luakeys/luakeys.tex | 9 +- 10 files changed, 3871 insertions(+), 3197 deletions(-) create mode 100644 macros/luatex/generic/luakeys/documentation.tex delete mode 100644 macros/luatex/generic/luakeys/luakeys-doc.pdf delete mode 100644 macros/luatex/generic/luakeys/luakeys-doc.tex create mode 100644 macros/luatex/generic/luakeys/luakeys.pdf (limited to 'macros/luatex/generic') 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/documentation.tex b/macros/luatex/generic/luakeys/documentation.tex new file mode 100644 index 0000000000..42da7c1acc --- /dev/null +++ b/macros/luatex/generic/luakeys/documentation.tex @@ -0,0 +1,2161 @@ +\documentclass{ltxdoc} + +\EnableCrossrefs +\CodelineIndex +\RecordChanges + +\usepackage{mdframed} +\usepackage{minted} +\usepackage{luakeys} +\usepackage{luakeys-debug} +\usepackage{multicol} +\usepackage{luacode} +\usepackage{syntax} +\usepackage{graphicx} + +\definecolor{bg}{rgb}{0.95,0.95,0.95} + +\usemintedstyle{friendly} +\setminted{ + breaklines=true, + fontsize=\footnotesize, + style=manni, +} +\def\lua#1{\mintinline{lua}|#1|} +\def\latex#1{\mintinline{latex}|#1|} + +\NewDocumentCommand { \InputLatex } { O{} m } { + \inputminted[ + linenos=false, + bgcolor=bg, + #1 + ]{latex}{examples/#2} +} + +\NewDocumentCommand { \InputLua } { O{} m } { + \inputminted[ + linenos=false, + bgcolor=bg, + #1 + ]{lua}{examples/#2} +} + +\catcode`_=12 +\def\DefaultOpt#1{% + \texttt{\directlua{ + tex.print(luakeys.print_default('opts', '#1')) + }}% +} + +\def\DefaultOptDescription#1{ +\noindent +The default value of the option “\texttt{#1}” is: +\DefaultOpt{#1}. +} + +\begin{document} + +\providecommand*{\url}{\texttt} + +\title{The \textsf{luakeys} package} +\author{% + Josef Friedrich\\% + \url{josef@friedrich.rocks}\\% + \href{https://github.com/Josef-Friedrich/luakeys} + {github.com/Josef-Friedrich/luakeys}% +} +\date{v0.12.0 from 2023/01/05} + +\maketitle + +\vfill + +\InputLua[firstline=4,lastline=7]{first-page.lua} + +\noindent +Result: + +\begin{center} +\begin{minted}{lua} +{ + ['level1'] = { + ['level2'] = { + ['naked'] = true, + ['dim'] = 1864679, + ['bool'] = false, + ['num'] = -0.001, + ['str'] = 'lua,{}', + } + } +} +\end{minted} +\end{center} + +\vfill + +\strut + +\newpage + +\tableofcontents + +\newpage + +% \section{Einführung} +\section{Introduction} + +\noindent +% |luakeys| ist ein Lua-Modul / Lua\TeX package, das wie die Pakete ... +% Schlüssel-Wert-Optionen analysieren kann. +|luakeys| is a Lua module / Lua\TeX{} package that can parse key-value +options like the \TeX{} packages +\href{https://www.ctan.org/pkg/keyval}{keyval}, +\href{https://www.ctan.org/pkg/kvsetkeys}{kvsetkeys}, +\href{https://www.ctan.org/pkg/kvoptions}{kvoptions}, +\href{https://www.ctan.org/pkg/xkeyval}{xkeyval}, +\href{https://www.ctan.org/pkg/pgfkeys}{pgfkeys} etc. +% +% |luakeys|, erfüllt diese Aufgabe jedoch, indem es die Sprache Lua +% verwendet und nicht auf \TeX{} angewiesen ist. +|luakeys|, however, accomplishes this task by using the Lua language and +doesn’t rely on \TeX{}. +% +% Deshalb kann dieses Paket nur mit der \TeX{}-Engine Lua\TeX{} +% verwendet werden. +Therefore this package can only be used with the +\TeX{} engine Lua\TeX{}. +% +% Da |luakeys| \href{http://www.inf.puc-rio.br/~roberto/lpeg/}{LPeg} +% verwendet, sollte der Parsing-Mechanismus ziemlich robust sein. +Since |luakeys| uses +\href{http://www.inf.puc-rio.br/~roberto/lpeg/}{LPeg}, the parsing +mechanism should be pretty robust. + +% Der Artikel TUGboat +% \href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf} +% {“Implementing key–value input: An introduction” (Volume 30 (2009), No. +% 1)} von \emph{Joseph Wright} und \emph{Christian Feuersänger} gibt einen +% einen guten Überblick über die verfügbaren Key-Value-Pakete. +The TUGboat article +\href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf} +{“Implementing key–value input: An introduction” (Volume 30 (2009), No. +1)} by \emph{Joseph Wright} and \emph{Christian Feuersänger} gives a +good overview of the available key-value packages. +% +% Dieser Artikel geht auf eine auf tex.stackexchange.com von Will +% Robertson: gestellte Frage zurück. +This article is based on a question asked on tex.stackexchange.com by +Will Robertson: +\href{https://tex.stackexchange.com/questions/26771} +{A big list of every keyval package}. +% +% CTAN stellt auch eine Übersichtsseite zum Thema bereit. +CTAN also provides an overview page on the subject of +\href{https://www.ctan.org/topic/keyval} +{Key-Val: packages with key-value argument systems}. + +% Dieses Paket wäre ohne den Artikel +% \href{https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf} +% {"Parsing complex data formats in LuaTEX with LPEG" (Volume 40 (2019), +% No. 2)} nicht möglich gewesen. +This package would not be possible without the article +\href{https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf} +{“Parsing complex data formats in LuaTEX with LPEG” (Volume 40 (2019), +No. 2)}. + +\subsection{Pros of \texttt{luakeys}} + +\begin{itemize} +% \item Schlüssel-Wert-Paare können unabhängig vom Makro-Paket (latex or +% context) analysiert werden. +\item Key-value pairs can be parsed independently of the macro +collection (\LaTeX{} or Con\TeX{}t). +% Sogar in Plain LuaTex können Schlüssel analysiert werden +Even in plain Lua\TeX{} keys can be parsed. + +% \item |luakeys| kann mit ineinander verschachtelten listen an +% Schlüssel-Wert-Paaren umgehen, d. h. es kann mit einer rekursiven +% Datenstruktur an Schlüssel umgehen. +\item |luakeys| can handle nested lists of key-value pairs, i.e. it can +handle a recursive data structure of keys. + +% \item Schlüssel müssen nicht, aber können definiert werden. +\item Keys do not have to be defined, but can they can be defined. +\end{itemize} + +\subsection{Cons of \texttt{luakeys}} + +\begin{itemize} +% \item Das Packet funktioniert nur in der Verbindung mit Lua\TeX. +\item The package works only in combination with Lua\TeX. + +% \item Du musst zwei Sprachen beherrschen: \TeX{} und Lua. +\item You need to know two languages: \TeX{} and Lua. +\end{itemize} + +%----------------------------------------------------------------------- +% +%----------------------------------------------------------------------- + +% \section{Wie das Paket geladen wird} +\section{How the package is loaded} + +%% +% +%% + +\subsection{Using the Lua module \texttt{luakeys.lua}} + +% Die Kernfunktionalität dieses Pakets ist in Lua realisiert. +The core functionality of this package is realized in Lua. +% +% Sie können also |luakeys| auch ohne die Wrapper-Dateien +% \texttt{luakeys.sty} und \texttt{luakeys.tex} verwenden. +So you can use |luakeys| even without using the wrapper files +|luakeys.sty| and |luakeys.tex|. + +\InputLatex{loading/lua.tex} + +%% +% +%% + +\subsection{Using the Lua\LaTeX{} wrapper \texttt{luakeys.sty}} + +% Der Paketmanager MikTeX lädt beispielsweise Pakete erst bei Bedarf +% herunter. +For example, the MiK\TeX{} package manager downloads packages only when +needed. +% +% Es wurde berichtet, dass dieser automatische Download nur mit Hilfe +% der Hüll-Dateien funktioniert. +It has been reported that this automatic download only works with this +wrapper files. +% +% Wahrscheinlich hält MiK\TeX nach einem Auftreten des LaTeX macros +% “\latex{\usepackage{luakeys}} ”Ausschau. +Probably MiK\TeX{} is searching for an occurrence of the \LaTeX{} macro +“\latex{\usepackage{luakeys}}”. +% +% Die Datei \texttt{luakeys.sty} lädt das Lua-Modul in die globale +% Variable \texttt{luakeys}. +The \texttt{luakeys.sty} file loads the Lua module into the global +variable \texttt{luakeys}. + +\InputLatex{loading/tex-latex.tex} + +%% +% +%% + +% \subsection{Verwendung des Plain-Lua\TeX{}-Hüllpakets +% \texttt{luakeys.tex}} +\subsection{Using the plain Lua\TeX{} wrapper \texttt{luakeys.tex}} + +\noindent +% Es macht dasselbe wie das Lua\LaTeX{}-Hüllpacket und lädt das +% Lua-Modul \texttt{luakeys.lua} in die globale Variable +% \texttt{luakeys}. +The file \texttt{luakeys.tex} does the same as the Lua\LaTeX{} wrapper +and loads the Lua module \texttt{luakeys.lua} into the global variable +\texttt{luakeys}. + +\InputLatex{loading/tex-plain.tex} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\section{Lua interface / API} + +% Luakeys exportiert nur eine Funktion, die aufgerufen werden muss, um auf +% die öffentliche API zuzugreifen +Luakeys exports only one function that must be called to access the +public API. +% +% Das Lua-Modul exportiert diese Funktionen und Tabellen: +The Lua module exports this functions and tables: + +\InputLua[firstline=3,lastline=16]{export.lua} + +% Das Projekt verwendet einige wenige Abkürzungen für Variablennamen, +% die hoffentlich für externe Leser eindeutig und bekannt sind. +The project uses a few abbreviations for variable names that are +hopefully unambiguous and familiar to external readers. + +\begin{center} +\begin{tabular}{lll} +Abbreviation & spelled out & Example \\\hline +\lua{kv_string} & Key-value string & \lua{'key=value'} \\ +\lua{opts} & Options (for the parse function) & +\lua{ { no_error = false } } \\ +\lua{defs} & Definitions \\ +\lua{def} & Definition \\ +\lua{attr} & Attributes (of a definition) \\ +\end{tabular} +\end{center} + +\noindent +% Diese nicht abgekürzten Variablennamen werden häufig verwendet. +These unabbreviated variable names are commonly used. +\begin{center} +\begin{tabular}{ll} + +\lua{result} & +% Das Endergebnis aller einzelnen Übersetzungs- und +% Normalisierungsschritte +The final result of all individual parsing and normalization steps. \\ + +\lua{unknown} & +% Ein Tabelle mit unbekannten, nicht definierten Schlüssel-Wert-Paaren +A table with unknown, undefinied key-value pairs. \\ + +\lua{raw} & +% Das unbearbeitete, rohe Ergebnis der LPeg-Syntaxanalyse. +The raw result of the Lpeg grammar parser. \\ +\end{tabular} +\end{center} + +%% +% +%% + +\subsection{Function “\texttt{parse(kv_string, opts): result, unknown, +raw}”} +\label{parse} + +% Die Function parse ist die wichtigste Funktion des Pakets. +The function \lua{parse(kv_string, opts)} is the most important function +of the package. +% +% +% Sie konvertiert eine Schlüssel-Wert-Zeichenkette in eine Lua Tabelle. +It converts a key-value string into a Lua table. + +\InputLatex{functions/parse/tex-latex.tex} + +\noindent +In plain \TeX: + +\InputLatex{functions/parse/tex-plain.tex} + +\subsection{Options to configure the \texttt{parse} function} + +\noindent +% Die Funktion \lua{parse} kann mit einer Optionstabelle aufgerufen +% werden. +The \lua{parse} function can be called with an options table. +% Diese Optionen werden unterstützt: +This options are supported: \catcode`_=12 +\directlua{luakeys.print_names('opts')} + +\InputLua[firstline=7,lastline=99]{opts/all-opts.lua} + +\subsection{Table “\texttt{opts}”} +\label{table-opts} + +\noindent +% Die Optionen können auch global über die exportierte Tabelle +% \lua{opts} gesetzt werden: +The options can also be set globally using the exported table +\lua{opts}: + +\InputLua[firstline=4,lastline=4]{opts/exported-default-opts.lua} + +\InputLua[firstline=10,lastline=11]{opts/exported-default-opts.lua} + +\noindent +% Damit es zu keinen Wechselwirkungen mit anderen Paketen kommt, die auch +% |luakeys| verwenden und die Optionen global setzen, ist es anzuraten, +% die Funktion \lua{get_private_instance()} zum Laden das Paket verwenden. +To avoid interactions with other packages that also use |luakeys| and +set the options globally, it is recommended to use the +\lua{get_private_instance()} function +(\ref{function:get-private-instance}) to load the package. + +%% +% +%% + +\subsubsection{Option “\texttt{accumulated_result}”} + +% Streng genommen handelt es sich hier um keine Option +Strictly speaking, this is not an option. +% +% Mit der Option \lua{accumulated_result} kann eine Ergebnistabelle +% angegeben werden, die bei jedem Aufruf der \lua{parse}-Funktion weiter +% befüllt wird. +The \lua{accumulated_result} “option” can be used to specify a result +table that is filled with each call of the \lua{parse} function. + +\InputLua[firstline=5,lastline=14]{opts/accumulated-result.lua} + +%% +% +%% + +\subsubsection{Option “\texttt{assignment_operator}”} +\label{option:assignment-operator} +\label{options-delimiter} + +The option \lua{assignment_operator} configures the delimiter that +assigns a value to a key. The default value of this option is +\texttt{"="}. + +The code example below demonstrates all six delimiter related options. + +\InputLua[firstline=4,lastline=13]{opts/delimiters.lua} + +\begin{tabular}{ll} +\textbf{Delimiter options} & \textbf{Section} \\ +assignment_operator & \ref{option:assignment-operator}\\ +group_begin & \ref{option:group-begin}\\ +group_end & \ref{option:group-end}\\ +list_separator & \ref{option:list-separator}\\ +quotation_begin & \ref{option:quotation-begin}\\ +quotation_end & \ref{option:quotation-end}\\ +\end{tabular} + +%% +% +%% + +\subsubsection{Option “\texttt{convert_dimensions}”} + +% Wenn Sie die Option \lua{convert_dimensions} auf \lua{true} setzen, +% erkennt |luakeys| die \TeX{}-Dimensionen und konvertiert sie mit Hilfe +% der die Funktion \lua{tex.sp(dim)} in scaled points. +If you set the option \lua{convert_dimensions} to \lua{true}, |luakeys| +detects the \TeX{} dimensions and converts them into scaled points using +the function \lua{tex.sp(dim)}. + +\InputLua[firstline=4,lastline=7]{opts/convert-dimensions/true.lua} + +\noindent +% Standardmäßig werden die Dimensionen nicht in skalierte Punkte +% umgewandelt. +By default the dimensions are not converted into scaled points. + +\InputLua[firstline=4,lastline=9]{opts/convert-dimensions/false.lua} + +\noindent +% Wenn Sie eine skalierte Punktzahl in einen Dimensionszeichenketten +% umwandeln möchten, können Sie das Modul +If you want to convert a scaled points number into a dimension string +you can use the module +\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua} +{lualibs-util-dim.lua}. + +\begin{minted}{lua} +require('lualibs') +tex.print(number.todimen(tex.sp('1cm'), 'cm', '%0.0F%s')) +\end{minted} + +\DefaultOptDescription{convert_dimensions} + +%% +% +%% + +\subsubsection{Option “\texttt{debug}”} + +% Wenn die Option \lua{debug} auf true gesetzt ist, wird die +% Ergebnistabelle in der Konsole ausgegeben. +If the option \lua{debug} is set to true, the result table is printed to +the console. + +\InputLatex{opts/debug-latex.tex} + +\begin{verbatim} +This is LuaHBTeX, Version 1.15.0 (TeX Live 2022) +... +(./debug.aux) (/usr/local/texlive/texmf-dist/tex/latex/base/ts1cmr.fd) +{ + ['three'] = true, + ['two'] = true, + ['one'] = true, +} + [1{/usr/ +local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./debug.aux) +) +... +Transcript written on debug.log. +\end{verbatim} + +\DefaultOptDescription{debug} + +%% +% +%% + +\subsubsection{Option “\texttt{default}”} +\label{option-default} + +% Mit der Option \lua{default} kann angegeben werden, welchen Wert +% nackte Schlüssel (Schlüssel ohne Wert) erhalten. Diese Option hat +% keinen Einfluss auf Schlüssel mit Werten. +The option \lua{default} can be used to specify which value naked keys +(keys without a value) get. This option has no influence on keys with +values. + +\InputLua[firstline=4,lastline=5]{opts/default.lua} + +\noindent +% Standardmäßig erhalten nackte Schlüssel den Wert \lua{true}. +By default, naked keys get the value \lua{true}. + +\InputLua[firstline=11,lastline=12]{opts/default.lua} + +\DefaultOptDescription{default} + +%% +% +%% + +\subsubsection{Option “\texttt{defaults}”} +\label{options-defaults} + +% Mit der Attribut „defaults“ kann nicht nur ein einiger Standardwert +% angegeben werden, sondern eine ganze Tabelle mit Standardwerten. +The option “defaults” can be used to specify not only one default value, +but a whole table of default values. +% Die Ergebnistabelle wird mit der Tabelle bestehend aus Standardwerten +% vereinigt. +The result table is merged into the defaults table. +% Werte aus der Tabelle mit Standardwerten werden von Werten der +% Ergebnistabelle überschrieben. +Values in the defaults table are +overwritten by values in the result table. + +\InputLua[firstline=4,lastline=7]{opts/defaults.lua} + +\DefaultOptDescription{defaults} + +%% +% +%% + +\subsubsection{Option “\texttt{defs}”} + +% Für mehr Information wie Schlüssel definiert werden, lesen sie das +% kapitel 3.2 +For more informations on how keys are defined, see section \ref{define}. +% Wenn sie die Option \lua{defs} verwenden, können sie auf den +% Aufruf der Funktion \lua{define} verzichten. +If you use the \lua{defs} option, you don't need to call the +\lua{define} function. +% +% Anstatt +Instead of ... + +\InputLua[firstline=4,lastline=5]{opts/defs.lua} + +\noindent +% können wir schreiben .. +we can write ... + +\InputLua[firstline=11,lastline=13]{opts/defs.lua} + +\DefaultOptDescription{defs} + +%% +% +%% + +\subsubsection{Option “\texttt{false_aliases}”} +\label{option:false-aliases} + +% Mit den den Optionen \lua{true_aliases} and \lua{false_aliases} können +% die Zeichenketten festgelegt werden, die vom Parser als Wahrheitswerte +% erkannt werden. +The \lua{true_aliases} and \lua{false_aliases} options can be used to +specify the strings that will be recognized as boolean values by the +parser. +% Standardmäßig sind folgende Zeichenketten konfiguriert +The following strings are configured by default. + +\InputLua[firstline=4,lastline=8]{opts/boolean-aliases.lua} + +\InputLua[firstline=14,lastline=18]{opts/boolean-aliases.lua} + +\InputLua[firstline=24,lastline=28]{opts/boolean-aliases.lua} + +% Siehe Abschnitt \label{option:true-aliases} für die entsprechende +% Option. +See section \ref{option:true-aliases} for the corresponding option. + +%% +% +%% + +\subsubsection{Option “\texttt{format_keys}”} + +% Mit Hilfe der Option \lua{format_keys} können die Schlüssel formatiert +% werden. +With the help of the option \lua{format_keys} the keys can be formatted. +% Die Werte dieser Option müssen in einer Tabelle angegeben werden. +The values of this option must be specified in a table. + +\begin{description} +\item[lower] + +% Um alle Schlüssel in \emph{Kleinbuchstaben} umzuwandeln, geben sie in +% der Optionentabelle \lua{lower} an. +To convert all keys to \emph{lowercase}, specify \lua{lower} in the +options table. + +\InputLua[firstline=4,lastline=5]{opts/format-keys.lua} + +\item[snake] + +% Um alle Schlüssel in \emph{snake case} (Die Wörter sind durch +% Unterstriche getrennt) umzuwandeln, geben sie in der Optionentabelle +% \lua{snake} an. +To make all keys \emph{snake case} (The words are separated by +underscores), specify \lua{snake} in the options +table. + +\InputLua[firstline=11,lastline=12]{opts/format-keys.lua} + +\item[upper] + +% Um alle Schlüssel in \emph{Grossbuchstaben} umzuwandeln, geben sie in +% der Optionentabelle \lua{upper} an. +To convert all keys to \emph{uppercase}, specify \lua{upper} in the +options table. + +\InputLua[firstline=18,lastline=19]{opts/format-keys.lua} +\end{description} + +% Sie können auch mehrere Formatierungsarten kombinieren. +You can also combine several types of formatting. + +\InputLua[firstline=25,lastline=26]{opts/format-keys.lua} + +\DefaultOptDescription{format_keys} + +%% +% +%% + +\subsubsection{Option “\texttt{group_begin}”} +\label{option:group-begin} + +The option \lua{group_begin} configures the delimiter that marks the +beginning of a group. The default value of this option is \texttt{"\{"}. +A code example can be found in section \ref{options-delimiter}. + +%% +% +%% + +\subsubsection{Option “\texttt{group_end}”} +\label{option:group-end} + +The option \lua{group_end} configures the delimiter that marks the end +of a group. The default value of this option is \texttt{"\}"}. A code +example can be found in section \ref{options-delimiter}. + +%% +% +%% + +\subsubsection{Option “\texttt{invert_flag}”} + +% Wird ein nackter Schlüssel mit einem vorangestellten Ausrufezeichen versehen, so wird sein Standardwert invertiert. +If a naked key is prefixed with an exclamation mark, its default value +is inverted. +% Statt \lua{true} nimmt der Schlüssel jetzt den Wert \lua{falsch} an. +Instead of \lua{true} the key now takes the value \lua{false}. + +\InputLua[firstline=4,lastline=5]{opts/invert-flat.lua} + +\noindent +% Mit der Option \lua{invert_flag} kann dieses Invertierungszeichen +% geändert werden. +The \lua{invert_flag} option can be used to change this inversion +character. + +\InputLua[firstline=11,lastline=12]{opts/invert-flat.lua} + +\noindent +% Ist der Standardwert für nackte Schlüssel beispielweise auf \lua{false} +% gesetzt, so nehmen die mit dem Umkehrungszeichen versehenen nackten +% Schlüssel den Wert \lua{true} an. +For example, if the default value for naked keys is set to \lua{false}, +the naked keys prefixed with the invert flat take the value \lua{true}. + +\InputLua[firstline=18,lastline=19]{opts/invert-flat.lua} + +\noindent +% Setzen sie die Option \lua{invert_flag} auf \lua{false}, um diese +% automatische Wertumkehrung zu deaktivieren. +Set the \lua{invert_flag} option to \lua{false} to disable this +automatic boolean value inversion. + +\InputLua[firstline=25,lastline=26]{opts/invert-flat.lua} + +%% +% +%% + +\subsubsection{Option “\texttt{hooks}”} + +% Die folgenden Hooks bzw. Callback-Funktionen ermöglichen es in den +% Verarbeitungsprozess der \lua{parse}-Function einzugreifen. +The following hooks or callback functions allow to intervene in the +processing of the \lua{parse} function. +% +% Die Funktionen sind in der Verarbeitungsreihenfolge aufgelistet. +The functions are listed in processing order. +% +% \lua{*_before_opts} bedeutet, dass die Hooks nach der LPeg +% Syntaxanalyse und vor dem Anwenden der Optionen ausgeführt +\lua{*_before_opts} means that the hooks are executed after the LPeg +syntax analysis and before the options are applied. +% +% Die Hooks \lua{*_before_defs} werden vor dem Anwenden der +% Schlüssel-Wert-Definitionen ausgeführt +The \lua{*_before_defs} hooks are executed before applying the key value +definitions. + +\def\TmpSignature#1{ + { + \scriptsize\texttt{ = #1} + } +} + +\def\TmpKeySignature{ + \TmpSignature{function(key, value, depth, current, result): key, value} +} +\def\TmpResultSignature{ + \TmpSignature{function(result): void} +} + +\begin{enumerate} +\item \lua{kv_string} \TmpSignature{function(kv_string): kv_string} +\item \lua{keys_before_opts} \TmpKeySignature +\item \lua{result_before_opts} \TmpResultSignature +\item \lua{keys_before_def} \TmpKeySignature +\item \lua{result_before_def} \TmpResultSignature +\item (\lua{process}) (has to be definied using defs, see \ref{attr-process}) +\item \lua{keys} \TmpKeySignature +\item \lua{result} \TmpResultSignature +\end{enumerate} + +\paragraph{\texttt{kv_string}} + +% Der Hook \lua{kv_string} wird als erste +% der Hook-Funktionen noch vor der LPeg-Syntax-Analyse aufgerufen. +The \lua{kv_string} hook is called as the first of the hook functions +before the LPeg syntax parser is executed. + +\InputLua[firstline=4,lastline=11]{hooks/kv-string.lua} + +\paragraph{\texttt{keys_*}} + +% Die Hooks \lua{keys_*} werden rekursiv auf jeden Schlüssel in der +% aktuellen Ergebnistabelle aufgerufen. +The hooks \lua{keys_*} are called recursively on each key in the current +result table. +% Die Hook-Funktion muss zwei Werte zurückgeben: \lua{key, value} +The hook function must return two values: \lua{key, value}. +% +% Das folgende Beispiel gibt \lua{key} und \lua{value} unverändert +% zurück, sodass die Ergebnistabelle nicht verändert wird. +The following example returns \lua{key} and \lua{value} unchanged, so +the result table is not changed. + +\InputLua[firstline=4,lastline=11]{hooks/keys-unchanged.lua} + +\noindent +% Das nächste Beispiel demonstriert den dritten Parameter \lua{depth} +% der Hook-Funktion. +The next example demonstrates the third parameter \lua{depth} of the +hook function. + +\InputLua[firstline=4,lastline=16]{hooks/keys-depth.lua} + +\paragraph{\texttt{result_*}} + +% Die Hooks \lua{result_*} werden einmal mit der aktuellen +% Ergebnistabelle als Parameter aufgerufen. +The hooks \lua{result_*} are called once with the current result table +as a parameter. + +%% +% +%% + +\subsubsection{Option “\texttt{list_separator}”} +\label{option:list-separator} + +The option \lua{list_separator} configures the delimiter that separates +list items from each other. The default value of this option is +\texttt{","}. A code example can be found in section +\ref{options-delimiter}. + +%% +% +%% + +\subsubsection{Option “\texttt{naked_as_value}”} + +% Mit Hilfe der Option \lua{naked_as_value} werden nackte Schlüssel +% nicht mit einem Standardwert versehen, sondern als Werte in die +% Lua-Tabelle abgelegt. +With the help of the option \lua{naked_as_value}, naked keys are not +given a default value, but are stored as values in a Lua table. + +\InputLua[firstline=4,lastline=5]{opts/naked-as-value.lua} + +\noindent +If we set the option \lua{naked_as_value} to \lua{true}: + +\InputLua[firstline=11,lastline=14]{opts/naked-as-value.lua} + +\DefaultOptDescription{naked_as_value} + +%% +% +%% + +\subsubsection{Option “\texttt{no_error}”} + +% Standardmaßig wirft parse-Funktion einen Fehler, wenn es unbekannte +% Schlüssel gibt. +By default the parse function throws an error if there are unknown keys. +% Mit Hilfe der Option \lua{no_error} kann dies unterbunden werden. +This can be prevented with the help of the \lua{no_error} option. + +\InputLua[firstline=5,lastline=6]{opts/no-error.lua} + +\noindent +If we set the option \lua{no_error} to \lua{true}: + +\InputLua[firstline=9,lastline=10]{opts/no-error.lua} + +\DefaultOptDescription{no_error} + +%% +% +%% + +\subsubsection{Option “\texttt{quotation_begin}”} +\label{option:quotation-begin} + +The option \lua{quotation_begin} configures the delimiter that marks the +beginning of a string. The default value of this option is +\texttt{'"'} (double quotes). A code example can be found in section +\ref{options-delimiter}. + +%% +% +%% + +\subsubsection{Option “\texttt{quotation_end}”} +\label{option:quotation-end} + +The option \lua{quotation_end} configures the delimiter that marks the +end of a string. The default value of this option is \texttt{'"'} +(double quotes). A code example can be found in section +\ref{options-delimiter}. + +%% +% +%% + +\subsubsection{Option “\texttt{true_aliases}”} +\label{option:true-aliases} + +See section \ref{option:false-aliases}. + +%% +% +%% + +\subsubsection{Option “\texttt{unpack}”} + +% Mit Hilfe der Option \lua{unpack} werden alle Tabellen, die nur aus +% einem einzigen nackten Schlüssel bzw. einen einzigen alleinstehenden +% Wert bestehen, aufgelöst. +With the help of the option \lua{unpack}, all tables that consist of +only a single naked key or a single standalone value are unpacked. + +\InputLua[firstline=4,lastline=5]{opts/unpack.lua} + +\InputLua[firstline=11,lastline=12]{opts/unpack.lua} + +\DefaultOptDescription{unpack} + +%% +% +%% + +\subsection{Function “\texttt{define(defs, opts): parse}”} +\label{define} + +The \lua{define} function returns a \lua{parse} function (see +\ref{parse}). +The name of a key can be specified in three ways: + +\begin{enumerate} +\item as a string. +\item as a key in a Lua table. The definition of the corresponding +key-value pair is then stored under this key. +\item by the “name” attribute. +\end{enumerate} + +\InputLua[firstline=4,lastline=16]{functions/define.lua} + +\noindent +% Bei verschachtelten Definitionen können nur die letzten beiden +% Möglichkeiten zur Angabe der Schlüsselnamen verwendet werden. +For nested definitions, only the last two ways of specifying the key +names can be used. + +\InputLua[firstline=26,lastline=33]{functions/define.lua} + +%----------------------------------------------------------------------- +% +%----------------------------------------------------------------------- + +\subsection{Attributes to define a key-value pair} + +% Die Definition eines Schlüssel-Wert-Paares kann mit Hilfe von +% verschiedenen Attributen vorgenommen werden. +The definition of a key-value pair can be made with the help of various +attributes. +% +% Der Name „Attribut“ für eine Option, einen Schlüssel, eine Eigenschaft +% (um nur einige Benennungsmöglichkeiten aufzuzählen) zur +% Schlüssel-Definition, wurde bewusst gewählt, um sie von den Optionen +% der Funktion \lua{parse} zu unterscheiden. +The name \emph{“attribute”} for an option, a key, a property ... (to +list just a few naming possibilities) to define keys, was deliberately +chosen to distinguish them from the options of the \lua{parse} function. +% +% Diese Attribute sind erlaubt. +These attributes are allowed: +\directlua{luakeys.print_names('attrs')}. +% +% Das folgende Codebeispiel listet alle Attribute auf, die verwendet +% werden können, um Schlüssel-Wert-Paare zu definieren. +The code example below lists all the attributes that can be used to +define key-value pairs. + +\InputLua[firstline=5,lastline=62]{defs/all-attrs.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{alias}”} + +With the help of the \lua{alias} attribute, other key names can be used. +The value is always stored under the original key name. A single alias +name can be specified by a string ... + +\InputLua[firstline=4,lastline=7]{defs/attrs/alias.lua} + +\noindent +multiple aliases by a list of strings. + +\InputLua[firstline=13,lastline=16]{defs/attrs/alias.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{always_present}”} + +% Die Option \lua{default} wird nur bei nackten Schlüsseln verwendet. +The \lua{default} attribute is used only for naked keys. + +\InputLua[firstline=4,lastline=5]{defs/attrs/always-present.lua} + +\noindent +% Wird die Option \lua{always_present} auf wahr gesetzt, wird der +% Schlüssel immer ins Ergebnis mit übernommen. +If the attribute \lua{always_present} is set to true, the key is always +included in the result. If no default value is definied, true is taken +as the value. + +\InputLua[firstline=11,lastline=12]{defs/attrs/always-present.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{choices}”} + +% source: Python argparse documentation. +Some key values should be selected from a restricted set of choices. +These can be handled by passing an array table containing choices. + +\InputLua[firstline=4,lastline=5]{defs/attrs/choices.lua} + +\noindent +When the key-value pair is parsed, values will be checked, and an error +message will be displayed if the value was not one of the acceptable +choices: + +\InputLua[firstline=13,lastline=15]{defs/attrs/choices.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{data_type}”} + +% source: Python argparse documentation. +The \lua{data_type} attribute allows type-checking and type conversions +to be performed. +% +% Folgende Datentypen werden unterstützt: +The following data types are supported: +\lua{'boolean'}, +\lua{'dimension'}, +\lua{'integer'}, +\lua{'number'}, +\lua{'string'}, +\lua{'list'}. +% +% Bei den drei Datentypen integer, number, dimension kann eine +% Typenumwandlung scheitern. +A type conversion can fail with the three data types +\lua{'dimension'}, +\lua{'integer'}, +\lua{'number'}. +% +% Dann wird eine Fehlermeldung ausgegeben. +Then an error message is displayed. + +\InputLua[firstline=4,lastline=8]{defs/attrs/data-type.lua} +\InputLua[firstline=11,lastline=15]{defs/attrs/data-type.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{default}”} + +% Verwenden Sie das Attribut „\lua{default}“, um für jeden nackten Schlüssel +% einzeln einen Standardwert bereit zu stellen. +Use the \lua{default} attribute to provide a default value for each naked +key individually. +% +% Mit der globalen \lua{default} Option kann für alle nackten Schlüssel ein +% Standardwert vorgegeben werden. +With the global \lua{default} attribute (\ref{option-default}) a default +value can be specified for all naked keys. + +\InputLua[firstline=4,lastline=9]{defs/attrs/default.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{description}”} + +% Dieses Attribut wird momentan nicht weiterverarbeitet. +This attribute is currently not processed further. +% Es kann als Kommentar dienen. +It can serve as a comment. + +%% +% +%% + +\subsubsection{Attribute “\texttt{exclusive_group}”} + +% Alle Schlüssel, die der gleichen ausschließenden Gruppe angehören, +% dürfen nicht gemeinsam angegeben werden. +All keys belonging to the same exclusive group must not be specified +together. +% +% Nur ein Schlüssel aus dieser Gruppe ist erlaubt. +Only one key from this group is allowed. +% +% Als Name für diese ausschließende Gruppe kann irgend ein beliebiger +% Wert verwendet werden. +Any value can be used as a name for this exclusive group. + +\InputLua[firstline=4,lastline=9]{defs/attrs/exclusive-group.lua} + +\noindent +% Werden mehrer Schlüssel der Gruppe angegeben, so wird eine +% Fehlermeldung geworfen. +If more than one key of the group is specified, an error message is +thrown. + +\InputLua[firstline=21,lastline=23]{defs/attrs/exclusive-group.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{macro}”} + +The attribute \texttt{macro} stores the value in a \TeX{} macro. + +\begin{minted}{lua} +local parse = luakeys.define({ + key = { + macro = 'MyMacro' + } +}) +parse('key=value') +\end{minted} + +\begin{minted}{latex} +\MyMacro % expands to “value” +\end{minted} + +%% +% +%% + +\subsubsection{Attribute “\texttt{match}”} + +% Der Wert des Schlüssel wird zuerst der Lua Funktion +% \lua{string.match(value, match)} übergeben, bevor er dem Schlüssel +% zugewiesen wird. +The value of the key is first passed to the Lua function +\lua{string.match(value, match)} +(\url{http://www.lua.org/manual/5.3/manual.html#pdf-string.match}) +before being assigned to the key. +% +% Du kannst das Attribut \lua{match} deshalb mit einer Pattern-Matching- +% Zeichenkette konfigurieren, wie sie in Lua zu Einsatz kommt. +You can therefore configure the \lua{match} attribute with a pattern +matching string used in Lua. +% +% Werfe einen Blick in das Lua-Handbuch, wie man Patterns schreibt. +Take a look at the Lua manual on how to write patterns +(\url{http://www.lua.org/manual/5.3/manual.html#6.4.1}). + +\InputLua[firstline=4,lastline=7]{defs/attrs/match/birthday.lua} + +\noindent +% Kann das Pattern im Wert nicht gefunden werden, wird eine +% Fehlermeldung ausgegeben. +If the pattern cannot be found in the value, an error message is issued. + +\InputLua[firstline=15,lastline=18]{defs/attrs/match/birthday.lua} + +\noindent +% Der Schlüssel erhält das Ergebnis der Funktion \lua{string.match(value, +% match)}, dass bedeutet, dass der ursprüngliche Wert unter +% Umständen nicht vollständig in den Schlüssel gespeichert wird. +The key receives the result of the function \lua{string.match(value, +match)}, which means that the original value may not be stored +completely in the key. +% +% Im nächsten Beispiel wird der gesamte Eingabewert akzeptiert: +In the next example, the entire input value is accepted: + +\InputLua[firstline=4,lastline=5]{defs/attrs/match/year.lua} + +\noindent +% Das Präfix “waste ” und das Suffix “ rubbisch” der Zeichenketten wird +% verworfen. +The prefix “waste ” and the suffix “ rubbisch” of the string are +discarded. + +\InputLua[firstline=11,lastline=11]{defs/attrs/match/year.lua} + +\noindent +% Da Funktion \lua{string.match(value, match)} immer eine Zeichenkette +% zurückgibt, ist der Wert des Schlüssel auch immer eine Zeichenkette. +Since function \lua{string.match(value, match)} always returns a string, +the value of the key is also always a string. + +%% +% +%% + +\subsubsection{Attribute “\texttt{name}”} + +% Die Option \lua{name} ermöglicht eine alternative Notation von +% Schlüsselnamen. +The \lua{name} attribute allows an alternative notation of key names. +% +% Anstatt ... +Instead of ... + +\InputLua[firstline=4,lastline=8]{defs/attrs/name/as-key.lua} + +\noindent +% ... können wir schreiben: +... we can write: + +\InputLua[firstline=4,lastline=8]{defs/attrs/name/name-attr.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{opposite_keys}”} + +% Die Option \lua{opposite_keys} ermöglicht es, gegensätzliche (nackte) +% Schlüssel in Wahrheitswerte umzuwandeln und diesen Wahrheitswert unter +% einem Zielschlüssel zu speichern. +The \lua{opposite_keys} attribute allows to convert opposite (naked) +keys into a boolean value and store this boolean under a target key. +% +% Lua erlaubt es in Tabellen Wahrheitswerte als Schlüssel zu verwenden. +% Es müssen jedoch eckige Klammern verwendet werden. +Lua allows boolean values to be used as keys in tables. +% +% Die Wahrheitswerte müssen jedoch in eckige Klammern geschrieben werden. +However, the boolean values must be written in square brackets, e. g. +\lua{{ opposite_keys = { [true] = 'show', [false] = 'hide' } }}. +% +% Beispiele für gegensätzliche Schlüssel sind: +Examples of opposing keys are: \lua{show} and \lua{hide}, \lua{dark} and +\lua{light}, \lua{question} and \lua{solution}. +% +% Das untenstehende Beispiel verwendet als gegensätzliches Schlüsselpaar +% die Schlüssel \lua{show} und \lua{hide}. +The example below uses the \lua{show} and \lua{hide} keys as the +opposite key pair. +% +% Wird der Schlüssel \lua{show} von der Funktion \lua{parse} gelesen, +% dann erhält der Zielschlüssel \lua{visibility} den Wert \lua{true}. +If the key \lua{show} is parsed by the \lua{parse} function, then the +target key \lua{visibility} receives the value \lua{true}. + +\InputLua[firstline=4,lastline=7]{defs/attrs/opposite-keys.lua} + +% Wird der Schlüssel \lua{hide} gelesen, dann \lua{falsch}. +\noindent +If the key \lua{hide} is parsed, then \lua{false}. + +\InputLua[firstline=13,lastline=13]{defs/attrs/opposite-keys.lua} + +\noindent +% Gegensätzliche Schlüsselpaare können in einer Kurzschreibweise +% angegeben werden, nämlich als Liste +Opposing key pairs can be specified in a short form, namely as a list: +% Der gegensätzliche Schlüssel, der den Wahrwert repräsentiert, muss in +% dieser Liste zuerst angegeben werden, dann folgt der Falschwert. +The opposite key, which represents the true value, must be specified +first in this list, followed by the false value. + +\InputLua[firstline=19,lastline=21]{defs/attrs/opposite-keys.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{pick}”} + +% Das Attribut \lua{pick} sucht nach einem Wert, der keinem Schlüssel +% zugeordnet ist. +The attribute \lua{pick} searches for a value not assigned to a key. +% Der zuerst gefundene Wert, d. h. der weiter links stehende Wert, wird +% einem Schlüssel zugewiesen. +The first value found, i.e. the one further to the left, is assigned to +a key. + +\InputLua[firstline=4,lastline=6]{defs/attrs/pick/dimension.lua} + +\noindent +% Es wird nur in der aktuellen Ergebnistabelle gesucht und nicht auf +% anderen Ebenen in der rekursiven Datenstruktur. +Only the current result table is searched, not other levels in the +recursive data structure. + +\InputLua[firstline=4,lastline=11]{defs/attrs/pick/different-levels.lua} + +\noindent +% Die Suche nach Werte wird aktiviert, wenn das Attribut \lua{pick} auf +% \lua{true} gesetzt wird. +The search for values is activated when the attribute \lua{pick} is set +to a data type. +% Mit diesen Datentypen kann nach Werten gesucht werden +These data types can be used to search for values: +string, number, dimension, integer, boolean, any. +% Verwendet den Datentyp any um jeden beliebigen Wert zu akzeptieren. +Use the data type “any” to accept any value. +% Wird einem Schlüssel bereits bei der Eingabe ein Wert zugewiesen, dann +% wird nicht weiter nach Werten gesucht. +If a value is already assigned to a key when it is entered, then no +further search for values is performed. +\InputLua[firstline=4,lastline=8]{defs/attrs/pick/value-set.lua} + +\noindent +% Das Attribut \lua{pick} akzeptiert auch mehrere Datentypen, die in +% einer Tabelle angegeben werden. +The \lua{pick} attribute also accepts multiple data types +specified in a table. + +\InputLua[firstline=4,lastline=10]{defs/attrs/pick/multiple-data-types.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{process}”} +\label{attr-process} + +% Das Attribut \lua{process} kann dazu verwendet werden, um eine Funktion +% zu definieren, deren Rückgabewert an den Schlüssel übergeben wird. +The \lua{process} attribute can be used to define a function whose +return value is passed to the key. +% +% Beim Aufruf der Funktion werden vier Parameter übergeben: +Four parameters are passed when the function is called: + +\begin{enumerate} +\item \lua{value}: +% Der zum Schlüssel gehörende aktuelle Wert. +The current value asssociated with the key. + +\item \lua{input}: +% Die Ergebnis-Tabelle, die vor dem Zeitpunkt geklont wurde, als mit dem +% Anwenden der Definitionen begonnen wurde. +The result table cloned before the time the definitions started to be +applied. + +\item \lua{result}: The table in which the final result will be saved. + +\item \lua{unknown}: The table in which the unknown key-value pairs +are stored. +\end{enumerate} + +\noindent +% Das folgende Beispiel demonstriert den Parameter \lua{value}: +The following example demonstrates the \lua{value} parameter: +\InputLua[firstline=4,lastline=14]{defs/attrs/process/parameter-value.lua} + +\noindent +% Das folgende Beispiel demonstriert den Parameter \lua{input}: +The following example demonstrates the \lua{input} parameter: + +\InputLua[firstline=4,lastline=16]{defs/attrs/process/parameter-input.lua} + +\noindent +% Das folgende Beispiel demonstriert den Parameter \lua{result}: +The following example demonstrates the \lua{result} parameter: + +\InputLua[firstline=4,lastline=12]{defs/attrs/process/parameter-result.lua} + +\noindent +% Das folgende Beispiel demonstriert den Parameter \lua{unknown}: +The following example demonstrates the \lua{unknown} parameter: + +\InputLua[firstline=4,lastline=11]{defs/attrs/process/parameter-unknown.lua} +\InputLua[firstline=15,lastline=15]{defs/attrs/process/parameter-unknown.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{required}”} + +% Durch das Attribut \lua{required} kann erzwungen werden, dass ein +% bestimmter Schlüssel angegeben werden muss. +The \lua{required} attribute can be used to enforce that a specific key +must be specified. +% +% Im untenstehenden Beispiel wird der Schlüssel \lua{important} als +% zwingend notwendig definiert. +In the example below, the key \lua{important} is defined as mandatory. + +\InputLua[firstline=4,lastline=5]{defs/attrs/required.lua} + +\noindent +% Fehlt der Schlüssel \lua{important} in der Eingabe, so tritt eine +% Fehlermeldung auf. +If the key \lua{important} is missing in the input, an error message +occurs. + +\InputLua[firstline=13,lastline=14]{defs/attrs/required.lua} + +\noindent +A recursive example: + +\InputLua[firstline=18,lastline=23]{defs/attrs/required.lua} + +\noindent +% Der Schlüssel \lua{important2} auf der Ebene 2 fehlt. +The \lua{important2} key on level 2 is missing. + +\InputLua[firstline=29,lastline=30]{defs/attrs/required.lua} + +\noindent +% Der Schlüssel \lua{important1} auf der untersten Schlüssel-Ebene fehlt. +The \lua{important1} key at the lowest key level is missing. + +\InputLua[firstline=38,lastline=39]{defs/attrs/required.lua} + +%% +% +%% + +\subsubsection{Attribute “\texttt{sub_keys}”} + +% Mit dem Attribut \lua{sub_keys} können ineinander verschachtelte +% Schlüssel-Wert-Paar-Definitionen aufgebaut werden. +The \lua{sub_keys} attribute can be used to build nested key-value pair +definitions. + +\InputLua[firstline=4,lastline=16]{defs/attrs/sub-keys.lua} + +%----------------------------------------------------------------------- +% +%----------------------------------------------------------------------- + +\subsection{Function “\texttt{render(result): string}”} + +The function \lua{render(result)} reverses the function +\lua{parse(kv_string)}. It takes a Lua table and converts this table +into a key-value string. The resulting string usually has a different +order as the input table. + +\InputLua[firstline=4,lastline=10]{functions/render.lua} + +\noindent +In Lua only tables with 1-based consecutive integer keys (a.k.a. array +tables) can be parsed in order. + +\InputLua[firstline=16,lastline=17]{functions/render.lua} + +%% +% +%% + +\subsection{Function “\texttt{debug(result): void}”} + +The function \lua{debug(result)} pretty prints a Lua table to standard +output (stdout). It is a utility function that can be used to debug and +inspect the resulting Lua table of the function \lua{parse}. You have to +compile your \TeX{} document in a console to see the terminal output. + +\InputLua[firstline=4,lastline=5]{functions/debug.lua} + +\noindent +The output should look like this: + +\begin{minted}{md} +{ + ['level1'] = { + ['level2'] = { + ['key'] = 'value', + }, + } +} +\end{minted} + +%% +% +%% + +\subsection{Function “\texttt{save(identifier, result): void}”} + +The function \lua{save(identifier, result)} saves a result (a +table from a previous run of \lua{parse}) under an identifier. +Therefore, it is not necessary to pollute the global namespace to +store results for the later usage. + +%% +% +%% + +\subsection{Function “\texttt{get(identifier): result}”} + +The function \lua{get(identifier)} retrieves a saved result from the +result store. + +%% +% +%% + +\subsection{Table “\texttt{is}”} + +% In der Tabelle \lua{is} werden einige Funktionen zusammengefasst, die +% überprüft ob eine Eingabe einem bestimmen Datentyp entspricht. +In the table \lua{is} some functions are summarized, which check whether +an input corresponds to a certain data type. +% +% Einige Funktionen akzeptieren nicht nur die entsprechenden +% Lua-Datentypen, sondern auch Eingaben als Zeichenketten. +Some functions accept not only the corresponding Lua data types, but also +input as strings. +% +% Beispielsweise wird die Zeichenkette \lua{'true'} von der +% \lua{is.boolean()}-Funktion als Wahrheitswert erkannt. +For example, the string \lua{'true'} is recognized by the +\lua{is.boolean()} function as a boolean value. + +\subsubsection{Function “\texttt{is.boolean(value): boolean}”} +\InputLua[firstline=6,lastline=23]{functions/is/boolean.lua} + +\subsubsection{Function “\texttt{is.dimension(value): boolean}”} +\InputLua[firstline=6,lastline=16]{functions/is/dimension.lua} + +\subsubsection{Function “\texttt{is.integer(value): boolean}”} +\InputLua[firstline=6,lastline=11]{functions/is/integer.lua} + +\subsubsection{Function “\texttt{is.number(value): boolean}”} +\InputLua[firstline=6,lastline=13]{functions/is/number.lua} + +\subsubsection{Function “\texttt{is.string(value): boolean}”} +\InputLua[firstline=6,lastline=12]{functions/is/string.lua} + +\subsubsection{Function “\texttt{is.list(value): boolean}”} +\InputLua[firstline=6,lastline=16]{functions/is/list.lua} + +\subsubsection{Function “\texttt{is.any(value): boolean}”} + +% Die Funktion \lua{is.any(value)} gibt immer wahr zurück und +% akzeptiert deshalb jeden Datentyp. +The function \lua{is.any(value)} always returns \lua{true} and +therefore accepts any data type. + +%% +% +%% + +\subsection{Table “\texttt{utils}”} + +% In der Tabelle \lua{utils} sind einige Hilfsfunktionen gebündelt. +The \lua{utils} table bundles some auxiliary functions. + +\InputLua[firstline=3,lastline=34]{utils/all.lua} + +%% +% +%% + +\subsubsection{Function “\texttt{utils.merge_tables(target, source, overwrite): table}”} + +% Die Funktion \lua{merge_tables} führt zwei Tabellen in die erste +% angegebene Tabelle zusammen. Sie kopiert Schlüssel aus der +% `source` Tabelle in die `target`-Tabelle. Sie gibt die geänderte +% Zieltabelle zurück. +The function \lua{merge_tables} merges two tables into the first +specified table. It copies keys from the `source` table into the +`target` table. It returns the target table. + +% Wird der Parameter \lua{overwrite} auf wahr gesetzten, so werden Werte +% in der Zieltabelle überschrieben +If the \lua{overwrite} parameter is set to \lua{true}, values in the +target table are overwritten. + +\InputLua[firstline=4,lastline=8]{utils/merge-tables.lua} + +\noindent +% Geben sie dem Parameter \lua{overwrite} den Wert \lua{false}, um Werte +% in der Zieltabelle zu überschreiben. +Give the parameter \lua{overwrite} the value \lua{false} to overwrite +values in the target table. + +\InputLua[firstline=14,lastline=18]{utils/merge-tables.lua} + +%% +% +%% + +\subsubsection{Function “\texttt{utils.scan_oarg(initial_delimiter?, end_delimiter?): string}”} + +% Plain \TeX{} kennt keine optionalen Argumente (oarg). +Plain \TeX{} does not know optional arguments (oarg). +% +% Die Funktion ermöglicht es nicht nur in \LaTeX{}, sondern auch in +% Plain \TeX{} nach optionalen Argumenten zu suchen. +The function \\ \lua{utils.scan_oarg(initial_delimiter?, +end_delimiter?): string} allows to search for optional arguments not only +in \LaTeX{} but also in Plain \TeX. +% +% Die Funktion basiert auf der Token-Bibliothek. +The function uses the token library built into Lua\TeX{}. +% +% Die beiden Parameter \lua{initial_delimiter} und \lua{end_delimiter} +% können weggelassen werden. +The two parameters \lua{initial_delimiter} and \lua{end_delimiter} can +be omitted. +% +% Dann werden eckige Klammern als Begrenzungszeichen angenommen. +Then square brackets are assumed to be delimiters. +% +% Dieser Lua-Code \lua{utils.scan_oarg('(', ')')} sucht beispielsweise +% nach an einem optionalen Argument in runden Klammern. +For example, this Lua code \lua{utils.scan_oarg('(', ')')} searches for +an optional argument in round brackets +% +% Die Funktion gibt die Zeichenkette zwischen den Begrenzungszeichen +% zurück, oder nil wenn Begrenzungszeichen gefunden werden konnten. +The function returns the string between the delimiters or \lua{nil} if +no delimiters could be found. +% +% Die Begrenzungszeichen sind im Ergebnis nicht enthalten. +The delimiters themselves are not included in the result. +% +% Nach dem \latex{\directlua{}} darf das Makro, indem +% \lua{utils.scan_oarg} eingesetzt wird, zu keinen Zeichen expandieren. +After the \latex{\directlua{}}, the macro using \lua{utils.scan_oarg} +must not expand to any characters. + +\InputLatex{utils/scan-oarg-plain.tex} + +%% +% +%% + +\subsection{Table “version”} + +% Das luakeys Projekt verwendet semantic versioning +The luakeys project uses semantic versioning. +% Die drei Versionenzahlen des Semantic Versioning Schemas werden in +% einer Tabelle in der Reihenfolge MAJOR, MINOR, PATCH als Ganzzahlen +% abgelegt. +The three version numbers of the semantic versioning scheme are stored +in a table as integers in the order MAJOR, MINOR, PATCH. +% Mit Hilfe dieser Tabelle kann überprüft werden, ob die richtige +% Version installiert ist. +This table can be used to check whether the correct version is +installed. + +\InputLua[firstline=4,lastline=10]{version.lua} + +%----------------------------------------------------------------------- +% +%----------------------------------------------------------------------- + +\section{Syntax of the recognized key-value format} + +%% +% +%% + +\subsection{An attempt to put the syntax into words} + +A key-value pair is definied by an equal sign (\texttt{key=value}). +Several key-value pairs or keys without values (naked keys) are lined up +with commas (\texttt{key=value,naked}) and build a key-value list. Curly +brackets can be used to create a recursive data structure of nested +key-value lists (\texttt{level1=\{level2=\{key=value,naked\}\}}). + +%% +% +%% + +\subsection{An (incomplete) attempt to put the syntax into the Extended Backus-Naur Form} + +\begin{grammar} + ::= \{ \} + + ::= `{' `}' + + ::= ( | | ) [ `,' ] + + ::= `=' ( | ) + + ::= + \alt + \alt + \alt + \alt + + ::= + + ::= ( [ ] | ) + + ::= `.' + + ::= `-' | `+' + + ::= \{ \} + + ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9' + + ::= `bp' | `BP' + \alt `cc' | `CC' + \alt `cm' | `CM' + \alt `dd' | `DD' + \alt `em' | `EM' + \alt `ex' | `EX' + \alt `in' | `IN' + \alt `mm' | `MM' + \alt `mu' | `MU' + \alt `nc' | `NC' + \alt `nd' | `ND' + \alt `pc' | `PC' + \alt `pt' | `PT' + \alt `px' | `PX' + \alt `sp' | `SP' + + ::= | + + ::= `true' | `TRUE' | `True' + + ::= `false' | `FALSE' | `False' +\end{grammar} + +... to be continued + +%% +% +%% + +\subsection{Recognized data types} + +\subsubsection{boolean} + +The strings \texttt{true}, \texttt{TRUE} and \texttt{True} are converted +into Lua’s boolean type \lua{true}, the strings \texttt{false}, +\texttt{FALSE} and \texttt{False} into \lua{false}. + +\begin{multicols}{2} +\InputLatex[firstline=5,lastline=12]{luakeysdebug/boolean-latex.tex} + +\columnbreak + +\begin{minted}{lua} +{ + ['lower case true'] = true, + ['upper case true'] = true, + ['title case true'] = true, + ['lower case false'] = false, + ['upper case false'] = false + ['title case false'] = false, +} +\end{minted} +\end{multicols} + +%% +% +%% + +\subsubsection{number} + +\begin{multicols}{2} +\InputLatex[firstline=5,lastline=13]{luakeysdebug/number-latex.tex} + +\columnbreak + +\begin{minted}{lua} +{ + ['num0'] = 42, + ['num1'] = 42, + ['num2'] = -42, + ['num3'] = 4.2, + ['num4'] = 0.42, + ['num5'] = 0.42, + ['num6'] = '0 . 42', -- string +} +\end{minted} +\end{multicols} + +%% +% +%% + +\subsubsection{dimension} + +% Luakeys versucht alle Einheiten zu erkennen, die in der TeX-Welt +% verwendet werden. +|luakeys| tries to recognize all units used in the \TeX{} world. +% +% Nach dem Lua\TeX-Quellcode . +According to the Lua\TeX{} source code +(\href{https://github.com/TeX-Live/luatex/blob/51db1985f5500dafd2393aa2e403fefa57d3cb76/source/texk/web2c/luatexdir/lua/ltexlib.c#L434-L625} +{source/texk/web2c/luatexdir/lua/ltexlib.c}) +% +% und nach dem Dimensionen-Modul der lulibs-Bibliothek +and the dimension module of the lualibs library +(\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua} +{lualibs-util-dim.lua}), +% +% müssten alle Einheiten erkannt werden +all units should be recognized. +\begin{multicols}{3} +\tiny +\begin{center} +\begin{tabular}{rl} +% \textbf{Unit name} +& \textbf{Description} \\\hline +bp & big point \\ +cc & cicero \\ +cm & centimeter \\ +dd & didot \\ +em & horizontal measure of \emph{M} \\ +ex & vertical measure of \emph{x} \\ +in & inch \\ +mm & milimeter \\ +mu & math unit \\ +nc & new cicero \\ +nd & new didot \\ +pc & pica \\ +pt & point \\ +px & x height current font \\ +sp & scaledpoint \\ +\end{tabular} +\end{center} + +\columnbreak + +\InputLatex[firstline=5,lastline=21]{luakeysdebug/dimension/all-latex.tex} + +\columnbreak + +\begin{minted}{lua} +{ + ['bp'] = 65781, + ['cc'] = 841489, + ['cm'] = 1864679, + ['dd'] = 70124, + ['em'] = 655360, + ['ex'] = 282460, + ['in'] = 4736286, + ['mm'] = 186467, + ['mu'] = 65536, + ['nc'] = 839105, + ['nd'] = 69925, + ['pc'] = 786432, + ['pt'] = 65536, + ['px'] = 65781, + ['sp'] = 1, +} +\end{minted} +\end{multicols} + +\noindent +% Im nächsten Beispiel werden die unterschiedlichen Notationsformen der +% Dimensionen illustriert. +The next example illustrates the different notations of the dimensions. + +\begin{multicols}{2} +\InputLatex[firstline=5,lastline=12]{luakeysdebug/dimension/notations-latex.tex} + +\columnbreak + +\begin{minted}{lua} +{ + ['upper'] = 1864679, + ['lower'] = 1864679, + ['space'] = 1864679, + ['plus'] = 1864679, + ['minus'] = -1864679, + ['nodim'] = '1 c m', -- string +} +\end{minted} +\end{multicols} + +%% +% +%% + +\subsubsection{string} + +% Es gibt zwei Möglichkeiten Zeichenketten anzugeben: +There are two ways to specify strings: +% +% Mit oder ohne doppelte Anführungszeichen. +With or without double quotes. +% +% Wenn der Text Kommas, geschweifte Klammern oder Gleichheitszeichen +% enthalten soll, müssen doppelte Anführungszeichen verwendet werden. +If the text have to contain commas, curly braces or equal signs, then +double quotes must be used. + +\InputLua[firstline=4,lastline=17]{data-types/string.lua} + +\subsubsection{Naked keys} + +% Nackte Schlüssel sind Schlüssel ohne Wert. +Naked keys are keys without a value. +% +% Mit der Option \lua{naked_as_value} können sie als Werte in ein Feld +% übernommen werden. +Using the option \lua{naked_as_value} they can be converted into values +and stored into an array. +% +% In Lua ist ein Feld eine Tabelle mit numerischen Indizes (der erste +% Index ist 1). +In Lua an array is a table with numeric indexes (The first index is 1). + +\InputLatex[firstline=5,lastline=12]{luakeysdebug/naked-keys-latex.tex} + +\noindent +% Alle erkannten Datentypen können als eigenständige Werte verwendet +% werden. +All recognized data types can be used as standalone values. + +\InputLatex[firstline=14,lastline=19]{luakeysdebug/naked-keys-latex.tex} + +%----------------------------------------------------------------------- +% +%----------------------------------------------------------------------- + +\clearpage + +\section{Examples} + +%% +% +%% + +\subsection{Extend and modify keys of existing macros} + +Extend the includegraphics macro with a new key named \latex{caption} +and change the accepted values of the \latex{width} key. A number +between 0 and 1 is allowed and converted into +\latex{width=0.5\linewidth} + +\InputLua{extend-includegraphics/extend-keys.lua} +\InputLatex{extend-includegraphics/extend-keys-latex.tex} + +%% +% +%% + +\subsection{Process document class options} + +% Auf die Optionen einer \LaTeX{} Dokumentenklasse kann über das Macro +% \latex{\LuakeysGetClassOptions} zu gegriffen werden. +The options of a \LaTeX{} document class can be accessed via the +\latex{\LuakeysGetClassOptions} macro. +% +% \latex{\LuakeysGetClassOptions} ist ein anderer Name für +\latex{\LuakeysGetClassOptions} is an alias for + +\begin{center} +\latex{\luaescapestring{\@raw@classoptionslist}}. +\end{center} + +\InputLatex{class-options/test-class.cls} +\InputLatex{class-options/use-test-class-latex.tex} + +%% +% +%% + +\subsection{Process package options} + +\noindent +% Auf die Optionen eines \LaTeX{}-Pakets kann über das Macro +% \latex{\LuakeysGetPackageOptions} zu gegriffen werden. +The options of a \LaTeX{} package can be accessed via the +\latex{\LuakeysGetPackageOptions} macro. +% \latex{\LuakeysGetPackageOptions} ist ein anderer Name für +\latex{\LuakeysGetPackageOptions} is an alias for + +\begin{center} +\latex{\luaescapestring{\@ptionlist{\@currname.\@currext}}}. +\end{center} + +\InputLatex{package-options/test-package.sty} +\InputLatex{package-options/use-test-package-latex.tex} + +%----------------------------------------------------------------------- +% +%----------------------------------------------------------------------- + +\clearpage + +\section{Debug packages} + +Two small debug packages are included in |luakeys|. One debug package +can be used in \LaTeX{} (luakeys-debug.sty) and one can be used in plain +\TeX{} (luakeys-debug.tex). Both packages provide only one command: +|\luakeysdebug{kv-string}| + +\begin{minted}{latex} +\luakeysdebug{one,two,three} +\end{minted} + +\noindent +Then the following output should appear in the document: +\bigskip + +\luakeysdebug{one,two,three} + +%% +% +%% + +\subsection{For plain \TeX: luakeys-debug.tex} + +An example of how to use the command in plain \TeX: + +\begin{minted}{latex} +\input luakeys-debug.tex +\luakeysdebug{one,two,three} +\bye +\end{minted} + +%% +% +%% + +\subsection{For \LaTeX: luakeys-debug.sty} + +An example of how to use the command in \LaTeX: + +\begin{minted}{latex} +\documentclass{article} +\usepackage{luakeys-debug} +\begin{document} +\luakeysdebug[ + unpack=false, + convert dimensions=false +]{one,two,three} +\end{document} +\end{minted} + +\section{Activity diagramm of the parsing process} + +\begin{center} +\includegraphics[width=0.8\linewidth]{Activity-Diagramm.eps} +\end{center} + +%----------------------------------------------------------------------- +% +%----------------------------------------------------------------------- + +\clearpage + +\section{Implementation} + +%% +% +%% + +\subsection{luakeys.lua} + +\inputminted[linenos=true]{lua}{luakeys.lua} + +%% +% +%% + +\clearpage + +\subsection{luakeys.tex} + +\inputminted[linenos=true]{latex}{luakeys.tex} + +%% +% +%% + +\clearpage + +\subsection{luakeys.sty} + +\inputminted[linenos=true]{latex}{luakeys.sty} + +%% +% +%% + +\clearpage + +\subsection{luakeys-debug.tex} + +\inputminted[linenos=true]{latex}{luakeys-debug.tex} + +%% +% +%% + +\clearpage + +\subsection{luakeys-debug.sty} + +\inputminted[linenos=true]{latex}{luakeys-debug.sty} + +\changes{v0.1.0}{2021/01/18}{Inital release} +\changes{v0.2.0}{2021/09/19}{ +* Allow all recognized data types as keys. +* Allow TeX macros in the values. +* New public Lua functions: save(identifier, result), get(identifier). +} +\changes{v0.3.0}{2021/11/05}{ +* Add a LuaLaTeX wrapper “luakeys.sty”. +* Add a plain LuaTeX wrapper “luakeys.tex”. +* Rename the previous documentation file “luakeys.tex” to luakeys-doc.tex”. +} +\changes{v0.4.0}{2021/12/31}{ +* Parser: Add support for nested tables (for example {{'a', 'b'}}). +* Parser: Allow only strings and numbers as keys. +* Parser: Remove support from Lua numbers with exponents (for example '5e+20'). +* Switch the Lua testing framework to busted. +} +\changes{v0.5.0}{2022/04/04}{ +* Add possibility to change options globally. +* New option: standalone_as_true. +* Add a recursive converter callback / hook to process the parse tree. +* New option: case_insensitive_keys. +} +\changes{v0.6.0}{2022/06/09}{ +* New feature: keys now can be defined using the function + “define(defs, opts)” or “define(kv_string, { defs = { key = { ... } } })” +* Rename the global options table from “default_options” to “opts”. +* New option “format_keys”. +* Remove option “case_insensitive_keys”. Use + “format_keys = \{ lower \}” to achieve the same effect. +* The default value of the option “convert_dimension” is now false. +* The option “standalone_as_true” is renamed to “naked_as_value”. + The boolean value of the option must be changed to the opposite to. + produce the previous effect. +* The function “print()” is now called “debug()”. +} +\changes{v0.7.0}{2022/07/06}{ +* The project now uses semantic versioning. +* New definition attribute “pick” to pick standalone values and assign + them to a key. +* New function “utils.scan_oarg()” to search for an optional argument, + that means scan for tokens that are enclosed in square brackets. +* Extend and improve the documentation. +} +\changes{v0.8.0}{2022/11/17}{ +* Add 6 new options to change the delimiters: “assignment_operator”, + “group_begin”, “group_end”, “list_separator”, “quotation_begin”, + “quotation_end”. +* Extend the documentation about the option “format_keys”. +} +\changes{v0.9.0}{2022/11/21}{ +* The definition attibute “pick” accepts a new data type: “any”. +* The attribute value “true” for the attribute “pick” is deprecated. +* The attribute “pick” accepts now multiple data types specified in + a table. +* Add a new function called “any(value)” in the “is” table that accepts + any data type. +} +\changes{v0.10.0}{2022/12/16}{ +* Add support for an invert flat that flips the value of naked keys. +* Add new options to specify which strings are recognized as Boolean + values. +} +\changes{v0.11.0}{2022/12/23}{ +* Add a new function called “get_private_instance()” to load a private + version of the luakeys module. +} +\changes{v0.12.0}{2023/01/05}{ +Added +* Macros \cmd{\LuakeysGetPackageOptions}, \cmd{\LuakeysGetClassOptions}. +* Option “accumulated_result”. +* Data type “list” to the attribute “data_type”. +* Attribute “description”. +* Tables “utils.log” and “utils.ansi_color”. +* Table “errors_message” to set custom messages. +* Short form syntax for the definition attribute “opposite_keys”. +Changed +* Breaking change! luakeys exports now a function instead of a table. + Use “require('luakeys')()” or “luakeys.new()” instead of + “require('luakeys')”. +* Breaking change! “luakeys.parse()”, “luakeys.define()”, “luakeys.save()” + and “luakeys.get()” can’t be used anymore from the global variable luakeys. +* New name for the function “new()” instead of “get_private_instance()". +} + +\pagebreak +\PrintChanges +\pagebreak +\PrintIndex +\end{document} 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 Binary files a/macros/luatex/generic/luakeys/luakeys-doc.pdf and /dev/null differ diff --git a/macros/luatex/generic/luakeys/luakeys-doc.tex b/macros/luatex/generic/luakeys/luakeys-doc.tex deleted file mode 100644 index 90041ec581..0000000000 --- a/macros/luatex/generic/luakeys/luakeys-doc.tex +++ /dev/null @@ -1,2102 +0,0 @@ -\documentclass{ltxdoc} - -\EnableCrossrefs -\CodelineIndex -\RecordChanges - -\usepackage{mdframed} -\usepackage{minted} -\usepackage{luakeys} -\usepackage{luakeys-debug} -\usepackage{multicol} -\usepackage{luacode} -\usepackage{syntax} -\usepackage{graphicx} - -\usemintedstyle{friendly} -\BeforeBeginEnvironment{minted}{\begin{mdframed}} -\AfterEndEnvironment{minted}{\end{mdframed}} -\setminted{ - breaklines=true, - fontsize=\footnotesize, - style=manni, -} -\def\lua#1{\mintinline{lua}|#1|} -\def\latex#1{\mintinline{latex}|#1|} - -\NewDocumentCommand { \InputLatex } { O{} m } { - \begin{mdframed} - \inputminted[linenos=false,#1]{latex}{examples/#2} - \end{mdframed} -} - -\NewDocumentCommand { \InputLua } { O{} m } { - \begin{mdframed} - \inputminted[linenos=false,#1]{lua}{examples/#2} - \end{mdframed} -} - -\catcode`_=12 -\def\DefaultOpt#1{% - \texttt{\directlua{ - tex.print(luakeys.print_default('opts', '#1')) - }}% -} - -\def\DefaultOptDescription#1{ -\noindent -The default value of the option “\texttt{#1}” is: -\DefaultOpt{#1}. -} - -\begin{document} - -\providecommand*{\url}{\texttt} - -\title{The \textsf{luakeys} package} -\author{% - Josef Friedrich\\% - \url{josef@friedrich.rocks}\\% - \href{https://github.com/Josef-Friedrich/luakeys} - {github.com/Josef-Friedrich/luakeys}% -} -\date{0.11.0 from 2022/12/23} - -\maketitle - -\vfill - -\InputLua[firstline=4,lastline=7]{first-page.lua} - -\noindent -Result: - -\begin{center} -\begin{minted}{lua} -{ - ['level1'] = { - ['level2'] = { - ['naked'] = true, - ['dim'] = 1864679, - ['bool'] = false, - ['num'] = -0.001, - ['str'] = 'lua,{}', - } - } -} -\end{minted} -\end{center} - -\vfill - -\strut - -\newpage - -\tableofcontents - -\newpage - -% \section{Einführung} -\section{Introduction} - -\noindent -% |luakeys| ist ein Lua-Modul / Lua\TeX package, das wie die Pakete ... -% Schlüssel-Wert-Optionen analysieren kann. -|luakeys| is a Lua module / Lua\TeX package that can parse key-value -options like the \TeX{} packages -\href{https://www.ctan.org/pkg/keyval}{keyval}, -\href{https://www.ctan.org/pkg/kvsetkeys}{kvsetkeys}, -\href{https://www.ctan.org/pkg/kvoptions}{kvoptions}, -\href{https://www.ctan.org/pkg/xkeyval}{xkeyval}, -\href{https://www.ctan.org/pkg/pgfkeys}{pgfkeys} etc. -% -% |luakeys|, erfüllt diese Aufgabe jedoch, indem es die Sprache Lua -% verwendet und nicht auf \TeX{} angewiesen ist. -|luakeys|, however, accomplishes this task by using the Lua language and -doesn’t rely on \TeX{}. -% -% Deshalb kann dieses Paket nur mit der \TeX{}-Engine Lua\TeX{} -% verwendet werden. -Therefore this package can only be used with the -\TeX{} engine Lua\TeX{}. -% -% Da |luakeys| \href{http://www.inf.puc-rio.br/~roberto/lpeg/}{LPeg} -% verwendet, sollte der Parsing-Mechanismus ziemlich robust sein. -Since |luakeys| uses -\href{http://www.inf.puc-rio.br/~roberto/lpeg/}{LPeg}, the parsing -mechanism should be pretty robust. - -% Der Artikel TUGboat -% \href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf} -% {“Implementing key–value input: An introduction” (Volume 30 (2009), No. -% 1)} von \emph{Joseph Wright} und \emph{Christian Feuersänger} gibt einen -% einen guten Überblick über die verfügbaren Key-Value-Pakete. -The TUGboat article -\href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf} -{“Implementing key–value input: An introduction” (Volume 30 (2009), No. -1)} by \emph{Joseph Wright} and \emph{Christian Feuersänger} gives a -good overview of the available key-value packages. - -% Dieses Paket wäre ohne den Artikel -% \href{https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf} -% {"Parsing complex data formats in LuaTEX with LPEG" (Volume 40 (2019), -% No. 2)} nicht möglich gewesen. -This package would not be possible without the article -\href{https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf} -{“Parsing complex data formats in LuaTEX with LPEG” (Volume 40 (2019), -No. 2)}. - -\subsection{Pros of \texttt{luakeys}} - -\begin{itemize} -% \item Schlüssel-Wert-Paare können unabhängig vom Makro-Paket (latex or -% context) analysiert werden. -\item Key-value pairs can be parsed independently of the macro -collection (\LaTeX{} or Con\TeX{}t). - -% \item Sogar in Plain LuaTex können Schlüssel analysiert werden -\item Even in plain Lua\TeX{} keys can be parsed. - -% \item |luakeys| kann mit ineinander verschachtelten listen an -% Schlüssel-Wert-Paaren umgehen, d. h. es kann mit einer rekursiven -% Datenstruktur an Schlüssel umgehen. -\item |luakeys| can handle nested lists of key-value pairs, i.e. it can -handle a recursive data structure of keys. - -% \item Schlüssel müssen nicht, aber können definiert werden. -\item Keys do not have to be defined, but can they can be defined. -\end{itemize} - -\subsection{Cons of \texttt{luakeys}} - -\begin{itemize} -% \item Das Packet funktioniert nur in der Verbindung mit Lua\TeX. -\item The package works only in combination with Lua\TeX. - -% \item Du musst zwei Sprachen beherrschen: \TeX{} und Lua. -\item You need to know two languages: \TeX{} and Lua. -\end{itemize} - -%----------------------------------------------------------------------- -% -%----------------------------------------------------------------------- - -% \section{Wie das Paket geladen wird} -\section{How the package is loaded} - -%% -% -%% - -\subsection{Using the Lua module \texttt{luakeys.lua}} - -% Die Kernfunktionalität dieses Pakets ist in Lua realisiert. -The core functionality of this package is realized in Lua. -% -% Sie können also |luakeys| auch ohne die Wrapper-Dateien -% \texttt{luakeys.sty} und \texttt{luakeys.tex} verwenden. -So you can use |luakeys| even without using the wrapper files -|luakeys.sty| and |luakeys.tex|. - -\InputLatex{loading/lua.tex} - -%% -% -%% - -\subsection{Using the Lua\LaTeX{} wrapper \texttt{luakeys.sty}} - -% Der Paketmanager MikTeX lädt beispielsweise Pakete erst bei Bedarf -% herunter. -For example, the MiK\TeX{} package manager downloads packages only when -needed. -% -% Es wurde berichtet, dass dieser automatische Download nur mit Hilfe -% der Hüll-Dateien funktioniert. -It has been reported that this automatic download only works with this -wrapper files. -% -% Wahrscheinlich hält MiK\TeX nach einem Auftreten des LaTeX macros -% “\latex{\usepackage{luakeys}} ”Ausschau. -Probably MiK\TeX{} is searching for an occurrence of the \LaTeX{} macro -“\latex{\usepackage{luakeys}}”. -% -% Die mitgelieferte Lua\LaTeX{}-Datei ist recht klein: -The supplied Lua\LaTeX{} file is quite small: - -\begin{minted}{latex} -\NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{luakeys} -\directlua{luakeys = require('luakeys')} -\end{minted} - -\noindent -% Es lädt das Lua-Modul in die globale Variable \texttt{luakeys}. -It loads the Lua module into the global variable \texttt{luakeys}. - -\InputLatex{loading/tex-latex.tex} - -%% -% -%% - -% \subsection{Verwendung des Plain-Lua\TeX{}-Hüllpakets -% \texttt{luakeys.tex}} -\subsection{Using the plain Lua\TeX{} wrapper \texttt{luakeys.tex}} - -% Noch kleiner ist die Datei \texttt{luakeys.tex}. -Even smaller is the file \texttt{luakeys.tex}. -% -% Sie besteht aus nur einer Zeile: -It consists of only one line: - -\begin{minted}{latex} -\directlua{luakeys = require('luakeys')} -\end{minted} - -\noindent -% Es macht dasselbe wie das Lua\LaTeX{}-Hüllpacket und lädt das -% Lua-Modul \texttt{luakeys.lua} in die globale Variable -% \texttt{luakeys}. -It does the same as the Lua\LaTeX{} wrapper and loads the Lua module -\texttt{luakeys.lua} into the global variable \texttt{luakeys}. - -\InputLatex{loading/tex-plain.tex} - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -% -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -\section{Lua interface / API} - -% Das Lua-Modul exportiert diese Funktionen und Tabellen: -The Lua module exports this functions and tables: - -\InputLua[firstline=3,lastline=15]{export.lua} - -\noindent -% Diese Dokumentation stellt nur die öffentlichen Funktionen und -% Tabellen vor. -This documentation presents only the public functions and tables. -% -% Um mehr über die privaten, nicht exportierten Funktionen zu erfahren, -% lesen Sie bitte die -% \href{https://josef-friedrich.github.io/luakeys/}{Quellcode -% Dokumentation}, die mit -% \href{http://stevedonovan.github.io/ldoc/}{LDoc} erstellt wurde. -To learn more about the private, not exported functions, please -read the \href{https://josef-friedrich.github.io/luakeys/}{source code -documentation}, which was created with -\href{http://stevedonovan.github.io/ldoc/}{LDoc}. - -\subsection{Lua indentifier names} - -% Das Projekt verwendet einige wenige Abkürzungen für Variablennamen, -% die hoffentlich für externe Leser eindeutig und bekannt sind. -The project uses a few abbreviations for variable names that are -hopefully unambiguous and familiar to external readers. - -\begin{center} -\begin{tabular}{lll} -Abbreviation & spelled out & Example \\\hline -\lua{kv_string} & Key-value string & \lua{'key=value'} \\ -\lua{opts} & Options (for the parse function) & -\lua{ { no_error = false } } \\ -\lua{defs} & Definitions \\ -\lua{def} & Definition \\ -\lua{attr} & Attributes (of a definition) \\ -\end{tabular} -\end{center} - -\noindent -% Diese nicht abgekürzten Variablennamen werden häufig verwendet. -These unabbreviated variable names are commonly used. -\begin{center} -\begin{tabular}{ll} - -\lua{result} & -% Das Endergebnis aller einzelnen Übersetzungs- und -% Normalisierungsschritte -The final result of all individual parsing and normalization steps. \\ - -\lua{unknown} & -% Ein Tabelle mit unbekannten, nicht definierten Schlüssel-Wert-Paaren -A table with unknown, undefinied key-value pairs. \\ - -\lua{raw} & -% Das unbearbeitete, rohe Ergebnis der LPeg-Syntaxanalyse. -The raw result of the Lpeg grammar parser. \\ -\end{tabular} -\end{center} - -%% -% -%% - -\subsection{Function “\texttt{get_private_instance(): table}”} -\label{function:get-private-instance} - -% Über die Tabelle \lua{opts} können die Standardoptionen global -% geändert werden. -The default options can be changed globally via the \lua{opts} table -(\ref{table-opts}). -% Um zu verhindern, dass andere Module die Standardeinstellungen -% ändern, kann mit Hilfe der Funktion \lua{get_private_instance()} eine neue -% Instanz des Moduls \lua{luakeys} erzeugt werden. -To prevent another modules from changing the default settings, a new -instance of the \lua{luakeys} module can be created using the -\lua{get_private_instance()} function. - -\InputLua[firstline=3,lastline=4]{functions/get-private-instance.lua} - -\noindent -% Die Funktion \lua{require()} gibt standardmäßig beim mehrmaligen -% Aufruf immer die gleiche Tabelle zurück. -The \lua{require()} function always returns the same table -when called multiple times. - -\InputLua[firstline=14,lastline=17]{functions/get-private-instance.lua} - -%% -% -%% - -\subsection{Function “\texttt{parse(kv_string, opts): result, unknown, -raw}”} -\label{parse} - -% Die Function parse ist die wichtigste Funktion des Pakets. -The function \lua{parse(kv_string, opts)} is the most important function -of the package. -% -% -% Sie konvertiert eine Schlüssel-Wert-Zeichenkette in eine Lua Tabelle. -It converts a key-value string into a Lua table. - -\InputLatex{functions/parse/tex-latex.tex} - -\noindent -In plain \TeX: - -\InputLatex{functions/parse/tex-plain.tex} - -\subsection{Options to configure the \texttt{parse} function} - -\noindent -% Die Funktion \lua{parse} kann mit einer Optionstabelle aufgerufen -% werden. -The \lua{parse} function can be called with an options table. -% Diese Optionen werden unterstützt: -This options are supported: \catcode`_=12 -\directlua{luakeys.print_names('opts')} - -\InputLua[firstline=5,lastline=94]{opts/all-opts.lua} - -\subsection{Table “\texttt{opts}”} -\label{table-opts} - -\noindent -% Die Optionen können auch global über die exportierte Tabelle -% \lua{opts} gesetzt werden: -The options can also be set globally using the exported table -\lua{opts}: - -\InputLua[firstline=4,lastline=4]{opts/exported-default-opts.lua} - -\InputLua[firstline=10,lastline=11]{opts/exported-default-opts.lua} - -\noindent -% Damit es zu keinen Wechselwirkungen mit anderen Paketen kommt, die auch -% |luakeys| verwenden und die Optionen global setzen, ist es anzuraten, -% die Funktion \lua{get_private_instance()} zum Laden das Paket verwenden. -To avoid interactions with other packages that also use |luakeys| and -set the options globally, it is recommended to use the -\lua{get_private_instance()} function -(\ref{function:get-private-instance}) to load the package. - -%% -% -%% - -\subsubsection{Option “\texttt{assignment_operator}”} -\label{option:assignment-operator} -\label{options-delimiter} - -The option \lua{assignment_operator} configures the delimiter that -assigns a value to a key. The default value of this option is -\texttt{"="}. - -The code example below demonstrates all six delimiter related options. - -\InputLua[firstline=4,lastline=13]{opts/delimiters.lua} - -\begin{tabular}{ll} -\textbf{Delimiter options} & \textbf{Section} \\ -assignment_operator & \ref{option:assignment-operator}\\ -group_begin & \ref{option:group-begin}\\ -group_end & \ref{option:group-end}\\ -list_separator & \ref{option:list-separator}\\ -quotation_begin & \ref{option:quotation-begin}\\ -quotation_end & \ref{option:quotation-end}\\ -\end{tabular} - -%% -% -%% - -\subsubsection{Option “\texttt{convert_dimensions}”} - -% Wenn Sie die Option \lua{convert_dimensions} auf \lua{true} setzen, -% erkennt |luakeys| die \TeX{}-Dimensionen und konvertiert sie mit Hilfe -% der die Funktion \lua{tex.sp(dim)} in scaled points. -If you set the option \lua{convert_dimensions} to \lua{true}, |luakeys| -detects the \TeX{} dimensions and converts them into scaled points using -the function \lua{tex.sp(dim)}. - -\InputLua[firstline=4,lastline=7]{opts/convert-dimensions/true.lua} - -\noindent -% Standardmäßig werden die Dimensionen nicht in skalierte Punkte -% umgewandelt. -By default the dimensions are not converted into scaled points. - -\InputLua[firstline=4,lastline=9]{opts/convert-dimensions/false.lua} - -\noindent -% Wenn Sie eine skalierte Punktzahl in einen Dimensionszeichenketten -% umwandeln möchten, können Sie das Modul -If you want to convert a scaled points number into a dimension string -you can use the module -\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua} -{lualibs-util-dim.lua}. - -\begin{minted}{lua} -require('lualibs') -tex.print(number.todimen(tex.sp('1cm'), 'cm', '%0.0F%s')) -\end{minted} - -\DefaultOptDescription{convert_dimensions} - -%% -% -%% - -\subsubsection{Option “\texttt{debug}”} - -% Wenn die Option \lua{debug} auf true gesetzt ist, wird die -% Ergebnistabelle in der Konsole ausgegeben. -If the option \lua{debug} is set to ture, the result table is printed to -the console. - -\InputLatex{opts/debug-latex.tex} - -\begin{verbatim} -This is LuaHBTeX, Version 1.15.0 (TeX Live 2022) -... -(./debug.aux) (/usr/local/texlive/texmf-dist/tex/latex/base/ts1cmr.fd) -{ - ['three'] = true, - ['two'] = true, - ['one'] = true, -} - [1{/usr/ -local/texlive/2022/texmf-var/fonts/map/pdftex/updmap/pdftex.map}] (./debug.aux) -) -... -Transcript written on debug.log. -\end{verbatim} - -\DefaultOptDescription{debug} - -%% -% -%% - -\subsubsection{Option “\texttt{default}”} -\label{option-default} - -% Mit der Option \lua{default} kann angegeben werden, welchen Wert -% nackte Schlüssel (Schlüssel ohne Wert) erhalten. Diese Option hat -% keinen Einfluss auf Schlüssel mit Werten. -The option \lua{default} can be used to specify which value naked keys -(keys without a value) get. This option has no influence on keys with -values. - -\InputLua[firstline=4,lastline=5]{opts/default.lua} - -\noindent -% Standardmäßig erhalten nackte Schlüssel den Wert \lua{true}. -By default, naked keys get the value \lua{true}. - -\InputLua[firstline=11,lastline=12]{opts/default.lua} - -\DefaultOptDescription{default} - -%% -% -%% - -\subsubsection{Option “\texttt{defaults}”} -\label{options-defaults} - -% Mit der Attribut „defaults“ kann nicht nur ein einiger Standardwert -% angegeben werden, sondern eine ganze Tabelle mit Standardwerten. -The option “defaults” can be used to specify not only one default value, -but a whole table of default values. -% Die Ergebnistabelle wird mit der Tabelle bestehend aus Standardwerten -% vereinigt. -The result table is merged into the defaults table. -% Werte aus der Tabelle mit Standardwerten werden von Werten der -% Ergebnistabelle überschrieben. -Values in the defaults table are -overwritten by values in the result table. - -\InputLua[firstline=4,lastline=7]{opts/defaults.lua} - -\DefaultOptDescription{defaults} - -%% -% -%% - -\subsubsection{Option “\texttt{defs}”} - -% Für mehr Information wie Schlüssel definiert werden, lesen sie das -% kapitel 3.2 -For more informations on how keys are defined, see section \ref{define}. -% Wenn sie die Option \lua{defs} verwenden, können sie auf den -% Aufruf der Funktion \lua{define} verzichten. -If you use the \lua{defs} option, you don't need to call the -\lua{define} function. -% -% Anstatt -Instead of ... - -\InputLua[firstline=4,lastline=5]{opts/defs.lua} - -\noindent -% können wir schreiben .. -we can write ... - -\InputLua[firstline=11,lastline=13]{opts/defs.lua} - -\DefaultOptDescription{defs} - -%% -% -%% - -\subsubsection{Option “\texttt{false_aliases}”} -\label{option:false-aliases} - -% Mit den den Optionen \lua{true_aliases} and \lua{false_aliases} können -% die Zeichenketten festgelegt werden, die vom Parser als Wahrheitswerte -% erkannt werden. -The \lua{true_aliases} and \lua{false_aliases} options can be used to -specify the strings that will be recognized as boolean values by the -parser. -% Standardmäßig sind folgende Zeichenketten konfiguriert -The following strings are configured by default. - -\InputLua[firstline=4,lastline=8]{opts/boolean-aliases.lua} - -\InputLua[firstline=14,lastline=18]{opts/boolean-aliases.lua} - -\InputLua[firstline=24,lastline=28]{opts/boolean-aliases.lua} - -% Siehe Abschnitt \label{option:true-aliases} für die entsprechende -% Option. -See section \ref{option:true-aliases} for the corresponding option. - -%% -% -%% - -\subsubsection{Option “\texttt{format_keys}”} - -% Mit Hilfe der Option \lua{format_keys} können die Schlüssel formatiert -% werden. -With the help of the option \lua{format_keys} the keys can be formatted. -% Die Werte dieser Option müssen in einer Tabelle angegeben werden. -The values of this option must be specified in a table. - -\begin{description} -\item[lower] - -% Um alle Schlüssel in \emph{Kleinbuchstaben} umzuwandeln, geben sie in -% der Optionentabelle \lua{lower} an. -To convert all keys to \emph{lowercase}, specify \lua{lower} in the -options table. - -\InputLua[firstline=4,lastline=5]{opts/format-keys.lua} - -\item[snake] - -% Um alle Schlüssel in \emph{snake case} (Die Wörter sind durch -% Unterstriche getrennt) umzuwandeln, geben sie in der Optionentabelle -% \lua{snake} an. -To make all keys \emph{snake case} (The words are separated by -underscores), specify \lua{snake} in the options -table. - -\InputLua[firstline=11,lastline=12]{opts/format-keys.lua} - -\item[upper] - -% Um alle Schlüssel in \emph{Grossbuchstaben} umzuwandeln, geben sie in -% der Optionentabelle \lua{upper} an. -To convert all keys to \emph{uppercase}, specify \lua{upper} in the -options table. - -\InputLua[firstline=18,lastline=19]{opts/format-keys.lua} -\end{description} - -% Sie können auch mehrere Formatierungsarten kombinieren. -You can also combine several types of formatting. - -\InputLua[firstline=25,lastline=26]{opts/format-keys.lua} - -\DefaultOptDescription{format_keys} - -%% -% -%% - -\subsubsection{Option “\texttt{group_begin}”} -\label{option:group-begin} - -The option \lua{group_begin} configures the delimiter that marks the -beginning of a group. The default value of this option is \texttt{"\{"}. -A code example can be found in section \ref{options-delimiter}. - -%% -% -%% - -\subsubsection{Option “\texttt{group_end}”} -\label{option:group-end} - -The option \lua{group_end} configures the delimiter that marks the end -of a group. The default value of this option is \texttt{"\}"}. A code -example can be found in section \ref{options-delimiter}. - -%% -% -%% - -\subsubsection{Option “\texttt{invert_flag}”} - -% Wird ein nackter Schlüssel mit einem vorangestellten Ausrufezeichen versehen, so wird sein Standardwert invertiert. -If a naked key is prefixed with an exclamation mark, its default value -is inverted. -% Statt \lua{true} nimmt der Schlüssel jetzt den Wert \lua{falsch} an. -Instead of \lua{true} the key now takes the value \lua{false}. - -\InputLua[firstline=4,lastline=5]{opts/invert-flat.lua} - -\noindent -% Mit der Option \lua{invert_flag} kann dieses Invertierungszeichen -% geändert werden. -The \lua{invert_flag} option can be used to change this inversion -character. - -\InputLua[firstline=11,lastline=12]{opts/invert-flat.lua} - -\noindent -% Ist der Standardwert für nackte Schlüssel beispielweise auf \lua{false} -% gesetzt, so nehmen die mit dem Umkehrungszeichen versehenen nackten -% Schlüssel den Wert \lua{true} an. -For example, if the default value for naked keys is set to \lua{false}, -the naked keys prefixed with the invert flat take the value \lua{true}. - -\InputLua[firstline=18,lastline=19]{opts/invert-flat.lua} - -\noindent -% Setzen sie die Option \lua{invert_flag} auf \lua{false}, um diese -% automatische Wertumkehrung zu deaktivieren. -Set the \lua{invert_flag} option to \lua{false} to disable this -automatic boolean value inversion. - -\InputLua[firstline=25,lastline=26]{opts/invert-flat.lua} - -%% -% -%% - -\subsubsection{Option “\texttt{hooks}”} - -% Die folgenden Hooks bzw. Callback-Funktionen ermöglichen es in den -% Verarbeitungsprozess der \lua{parse}-Function einzugreifen. -The following hooks or callback functions allow to intervene in the -processing of the \lua{parse} function. -% -% Die Funktionen sind in der Verarbeitungsreihenfolge aufgelistet. -The functions are listed in processing order. -% -% \lua{*_before_opts} bedeutet, dass die Hooks nach der LPeg -% Syntaxanalyse und vor dem Anwenden der Optionen ausgeführt -\lua{*_before_opts} means that the hooks are executed after the LPeg -syntax analysis and before the options are applied. -% -% Die Hooks \lua{*_before_defs} werden vor dem Anwenden der -% Schlüssel-Wert-Definitionen ausgeführt -The \lua{*_before_defs} hooks are executed before applying the key value -definitions. - -\def\TmpSignature#1{ - { - \scriptsize\texttt{ = #1} - } -} - -\def\TmpKeySignature{ - \TmpSignature{function(key, value, depth, current, result): key, value} -} -\def\TmpResultSignature{ - \TmpSignature{function(result): void} -} - -\begin{enumerate} -\item \lua{kv_string} \TmpSignature{function(kv_string): kv_string} -\item \lua{keys_before_opts} \TmpKeySignature -\item \lua{result_before_opts} \TmpResultSignature -\item \lua{keys_before_def} \TmpKeySignature -\item \lua{result_before_def} \TmpResultSignature -\item (\lua{process}) (has to be definied using defs, see \ref{attr-process}) -\item \lua{keys} \TmpKeySignature -\item \lua{result} \TmpResultSignature -\end{enumerate} - -\paragraph{\texttt{kv_string}} - -% Der Hook \lua{kv_string} wird als erste -% der Hook-Funktionen noch vor der LPeg-Syntax-Analyse aufgerufen. -The \lua{kv_string} hook is called as the first of the hook functions -before the LPeg syntax parser is executed. - -\InputLua[firstline=4,lastline=11]{hooks/kv-string.lua} - -\paragraph{\texttt{keys_*}} - -% Die Hooks \lua{keys_*} werden rekursiv auf jeden Schlüssel in der -% aktuellen Ergebnistabelle aufgerufen. -The hooks \lua{keys_*} are called recursively on each key in the current -result table. -% Die Hook-Funktion muss zwei Werte zurückgeben: \lua{key, value} -The hook function must return two values: \lua{key, value}. -% -% Das folgende Beispiel gibt \lua{key} und \lua{value} unverändert -% zurück, sodass die Ergebnistabelle nicht verändert wird. -The following example returns \lua{key} and \lua{value} unchanged, so -the result table is not changed. - -\InputLua[firstline=4,lastline=11]{hooks/keys-unchanged.lua} - -\noindent -% Das nächste Beispiel demonstriert den dritten Parameter \lua{depth} -% der Hook-Funktion. -The next example demonstrates the third parameter \lua{depth} of the -hook function. - -\InputLua[firstline=4,lastline=16]{hooks/keys-depth.lua} - -\paragraph{\texttt{result_*}} - -% Die Hooks \lua{result_*} werden einmal mit der aktuellen -% Ergebnistabelle als Parameter aufgerufen. -The hooks \lua{result_*} are called once with the current result table -as a parameter. - -%% -% -%% - -\subsubsection{Option “\texttt{list_separator}”} -\label{option:list-separator} - -The option \lua{list_separator} configures the delimiter that separates -list items from each other. The default value of this option is -\texttt{","}. A code example can be found in section -\ref{options-delimiter}. - -%% -% -%% - -\subsubsection{Option “\texttt{naked_as_value}”} - -% Mit Hilfe der Option \lua{naked_as_value} werden nackte Schlüssel -% nicht mit einem Standardwert versehen, sondern als Werte in die -% Lua-Tabelle abgelegt. -With the help of the option \lua{naked_as_value}, naked keys are not -given a default value, but are stored as values in a Lua table. - -\InputLua[firstline=4,lastline=5]{opts/naked-as-value.lua} - -\noindent -If we set the option \lua{naked_as_value} to \lua{true}: - -\InputLua[firstline=11,lastline=14]{opts/naked-as-value.lua} - -\DefaultOptDescription{naked_as_value} - -%% -% -%% - -\subsubsection{Option “\texttt{no_error}”} - -% Standardmaßig wirft parse-Funktion einen Fehler, wenn es unbekannte -% Schlüssel gibt. -By default the parse function throws an error if there are unknown keys. -% Mit Hilfe der Option \lua{no_error} kann dies unterbunden werden. -This can be prevented with the help of the \lua{no_error} option. - -\InputLua[firstline=5,lastline=6]{opts/no-error.lua} - -\noindent -If we set the option \lua{no_error} to \lua{true}: - -\InputLua[firstline=9,lastline=10]{opts/no-error.lua} - -\DefaultOptDescription{no_error} - -%% -% -%% - -\subsubsection{Option “\texttt{quotation_begin}”} -\label{option:quotation-begin} - -The option \lua{quotation_begin} configures the delimiter that marks the -beginning of a string. The default value of this option is -\texttt{'"'} (double quotes). A code example can be found in section -\ref{options-delimiter}. - -%% -% -%% - -\subsubsection{Option “\texttt{quotation_end}”} -\label{option:quotation-end} - -The option \lua{quotation_end} configures the delimiter that marks the -end of a string. The default value of this option is \texttt{'"'} -(double quotes). A code example can be found in section -\ref{options-delimiter}. - -%% -% -%% - -\subsubsection{Option “\texttt{true_aliases}”} -\label{option:true-aliases} - -See section \ref{option:false-aliases}. - -%% -% -%% - -\subsubsection{Option “\texttt{unpack}”} - -% Mit Hilfe der Option \lua{unpack} werden alle Tabellen, die nur aus -% einem einzigen nackten Schlüssel bzw. einen einzigen alleinstehenden -% Wert bestehen, aufgelöst. -With the help of the option \lua{unpack}, all tables that consist of -only a single naked key or a single standalone value are unpacked. - -\InputLua[firstline=4,lastline=5]{opts/unpack.lua} - -\InputLua[firstline=11,lastline=12]{opts/unpack.lua} - -\DefaultOptDescription{unpack} - -%% -% -%% - -\subsection{Function “\texttt{define(defs, opts): parse}”} -\label{define} - -The \lua{define} function returns a \lua{parse} function (see -\ref{parse}). -The name of a key can be specified in three ways: - -\begin{enumerate} -\item as a string. -\item as a key in a Lua table. The definition of the corresponding -key-value pair is then stored under this key. -\item by the “name” attribute. -\end{enumerate} - -\InputLua[firstline=4,lastline=16]{functions/define.lua} - -\noindent -% Bei verschachtelten Definitionen können nur die letzten beiden -% Möglichkeiten zur Angabe der Schlüsselnamen verwendet werden. -For nested definitions, only the last two ways of specifying the key -names can be used. - -\InputLua[firstline=26,lastline=33]{functions/define.lua} - -%----------------------------------------------------------------------- -% -%----------------------------------------------------------------------- - -\subsection{Attributes to define a key-value pair} - -% Die Definition eines Schlüssel-Wert-Paares kann mit Hilfe von -% verschiedenen Attributen vorgenommen werden. -The definition of a key-value pair can be made with the help of various -attributes. -% -% Der Name „Attribut“ für eine Option, einen Schlüssel, eine Eigenschaft -% (um nur einige Benennungsmöglichkeiten aufzuzählen) zur -% Schlüssel-Definition, wurde bewusst gewählt, um sie von den Optionen -% der Funktion \lua{parse} zu unterscheiden. -The name \emph{“attribute”} for an option, a key, a property ... (to -list just a few naming possibilities) to define keys, was deliberately -chosen to distinguish them from the options of the \lua{parse} function. -% -% Diese Attribute sind erlaubt. -These attributes are allowed: -\directlua{luakeys.print_names('attrs')}. -% -% Das folgende Codebeispiel listet alle Attribute auf, die verwendet -% werden können, um Schlüssel-Wert-Paare zu definieren. -The code example below lists all the attributes that can be used to -define key-value pairs. - -\InputLua[firstline=5,lastline=46]{defs/all-attrs.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{alias}”} - -With the help of the \lua{alias} attribute, other key names can be used. -The value is always stored under the original key name. A single alias -name can be specified by a string ... - -\InputLua[firstline=4,lastline=7]{defs/attrs/alias.lua} - -\noindent -multiple aliases by a list of strings. - -\InputLua[firstline=13,lastline=16]{defs/attrs/alias.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{always_present}”} - -% Die Option \lua{default} wird nur bei nackten Schlüsseln verwendet. -The \lua{default} attribute is used only for naked keys. - -\InputLua[firstline=4,lastline=5]{defs/attrs/always-present.lua} - -\noindent -% Wird die Option \lua{always_present} auf wahr gesetzt, wird der -% Schlüssel immer ins Ergebnis mit übernommen. -If the attribute \lua{always_present} is set to true, the key is always -included in the result. If no default value is definied, true is taken -as the value. - -\InputLua[firstline=11,lastline=12]{defs/attrs/always-present.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{choices}”} - -% source: Python argparse documentation. -Some key values should be selected from a restricted set of choices. -These can be handled by passing an array table containing choices. - -\InputLua[firstline=4,lastline=5]{defs/attrs/choices.lua} - -\noindent -When the key-value pair is parsed, values will be checked, and an error -message will be displayed if the value was not one of the acceptable -choices: - -\InputLua[firstline=13,lastline=15]{defs/attrs/choices.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{data_type}”} - -% source: Python argparse documentation. -The \lua{data_type} attribute allows type-checking and type conversions to -be performed. -% -% Folgende Datentypen werden unterstützt: -The following data types are supported: -\lua{'boolean'}, -\lua{'dimension'}, -\lua{'integer'}, -\lua{'number'}, -\lua{'string'}. -% -% Bei den drei Datentypen integer, number, dimension kann eine -% Typenumwandlung scheitern. -A type conversion can fail with the three data types -\lua{'dimension'}, -\lua{'integer'}, -\lua{'number'}. -% -% Dann wird eine Fehlermeldung ausgegeben. -Then an error message is displayed. - -\InputLua[firstline=4,lastline=8]{defs/attrs/data-type.lua} -\InputLua[firstline=11,lastline=15]{defs/attrs/data-type.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{default}”} - -% Verwenden Sie das Attribut „\lua{default}“, um für jeden nackten Schlüssel -% einzeln einen Standardwert bereit zu stellen. -Use the \lua{default} attribute to provide a default value for each naked -key individually. -% -% Mit der globalen \lua{default} Option kann für alle nackten Schlüssel ein -% Standardwert vorgegeben werden. -With the global \lua{default} attribute (\ref{option-default}) a default -value can be specified for all naked keys. - -\InputLua[firstline=4,lastline=9]{defs/attrs/default.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{exclusive_group}”} - -% Alle Schlüssel, die der gleichen ausschließenden Gruppe angehören, -% dürfen nicht gemeinsam angegeben werden. -All keys belonging to the same exclusive group must not be specified -together. -% -% Nur ein Schlüssel aus dieser Gruppe ist erlaubt. -Only one key from this group is allowed. -% -% Als Name für diese ausschließende Gruppe kann irgend ein beliebiger -% Wert verwendet werden. -Any value can be used as a name for this exclusive group. - -\InputLua[firstline=4,lastline=9]{defs/attrs/exclusive-group.lua} - -\noindent -% Werden mehrer Schlüssel der Gruppe angegeben, so wird eine -% Fehlermeldung geworfen. -If more than one key of the group is specified, an error message is -thrown. - -\InputLua[firstline=21,lastline=23]{defs/attrs/exclusive-group.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{opposite_keys}”} - -% Die Option \lua{opposite_keys} ermöglicht es, gegensätzliche (nackte) -% Schlüssel in Wahrheitswerte umzuwandeln und diesen Wahrheitswert unter -% einem Zielschlüssel zu speichern. -The \lua{opposite_keys} attribute allows to convert opposite (naked) keys -into a boolean value and store this boolean under a target key. -% -% Lua erlaubt es in Tabellen Wahrheitswerte als Schlüssel zu verwenden. -% Es müssen jedoch eckige Klammern verwendet werden. -Lua allows boolean values to be used as keys in tables. -% -% Die Wahrheitswerte müssen jedoch in eckige Klammern geschrieben werden. -However, the boolean values must be written in square brackets, e. g. -\lua{{ opposite_keys = { [true] = 'show', [false] = 'hide' } }}. -% -% Beispiele für gegensätzliche Schlüssel sind: -Examples of opposing keys are: \lua{show} and \lua{hide}, \lua{dark} and -\lua{light}, \lua{question} and \lua{solution}. -% -% Das untenstehende Beispiel verwendet als gegensätzliches Schlüsselpaar -% die Schlüssel \lua{show} und \lua{hide}. -The example below uses the \lua{show} and \lua{hide} keys as the -opposite key pair. -% -% Wird der Schlüssel \lua{show} von der Funktion \lua{parse} gelesen, -% dann erhält der Zielschlüssel \lua{visibility} den Wert \lua{true}. -If the key \lua{show} is parsed by the \lua{parse} function, then the -target key \lua{visibility} receives the value \lua{true}. - -\InputLua[firstline=4,lastline=7]{defs/attrs/opposite-keys.lua} - -% Wird der Schlüssel \lua{hide} gelesen, dann \lua{falsch}. -\noindent -If the key \lua{hide} is parsed, then \lua{false}. - -\InputLua[firstline=13,lastline=13]{defs/attrs/opposite-keys.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{macro}”} - -The attribute \texttt{macro} stores the value in a \TeX{} macro. - -\begin{minted}{lua} -local parse = luakeys.define({ - key = { - macro = 'MyMacro' - } -}) -parse('key=value') -\end{minted} - -\begin{minted}{latex} -\MyMacro % expands to “value” -\end{minted} - -%% -% -%% - -\subsubsection{Attribute “\texttt{match}”} - -% Der Wert des Schlüssel wird zuerst der Lua Funktion -% \lua{string.match(value, match)} übergeben, bevor er dem Schlüssel -% zugewiesen wird. -The value of the key is first passed to the Lua function -\lua{string.match(value, match)} -(\url{http://www.lua.org/manual/5.3/manual.html#pdf-string.match}) -before being assigned to the key. -% -% Du kannst das Attribut \lua{match} deshalb mit einer Pattern-Matching- -% Zeichenkette konfigurieren, wie sie in Lua zu Einsatz kommt. -You can therefore configure the \lua{match} attribute with a pattern -matching string used in Lua. -% -% Werfe einen Blick in das Lua-Handbuch, wie man Patterns schreibt. -Take a look at the Lua manual on how to write patterns -(\url{http://www.lua.org/manual/5.3/manual.html#6.4.1}). - -\InputLua[firstline=4,lastline=7]{defs/attrs/match/birthday.lua} - -\noindent -% Kann das Pattern im Wert nicht gefunden werden, wird eine -% Fehlermeldung ausgegeben. -If the pattern cannot be found in the value, an error message is issued. - -\InputLua[firstline=15,lastline=18]{defs/attrs/match/birthday.lua} - -\noindent -% Der Schlüssel erhält das Ergebnis der Funktion \lua{string.match(value, -% match)}, dass bedeutet, dass der ursprüngliche Wert unter -% Umständen nicht vollständig in den Schlüssel gespeichert wird. -The key receives the result of the function \lua{string.match(value, -match)}, which means that the original value may not be stored -completely in the key. -% -% Im nächsten Beispiel wird der gesamte Eingabewert akzeptiert: -In the next example, the entire input value is accepted: - -\InputLua[firstline=4,lastline=5]{defs/attrs/match/year.lua} - -\noindent -% Das Präfix “waste ” und das Suffix “ rubbisch” der Zeichenketten wird -% verworfen. -The prefix “waste ” and the suffix “ rubbisch” of the string are -discarded. - -\InputLua[firstline=11,lastline=11]{defs/attrs/match/year.lua} - -\noindent -% Da Funktion \lua{string.match(value, match)} immer eine Zeichenkette -% zurückgibt, ist der Wert des Schlüssel auch immer eine Zeichenkette. -Since function \lua{string.match(value, match)} always returns a string, -the value of the key is also always a string. - -%% -% -%% - -\subsubsection{Attribute “\texttt{name}”} - -% Die Option \lua{name} ermöglicht eine alternative Notation von -% Schlüsselnamen. -The \lua{name} attribute allows an alternative notation of key names. -% -% Anstatt ... -Instead of ... - -\InputLua[firstline=4,lastline=8]{defs/attrs/name/as-key.lua} - -\noindent -% ... können wir schreiben: -... we can write: - -\InputLua[firstline=4,lastline=8]{defs/attrs/name/name-attr.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{pick}”} - -% Das Attribut \lua{pick} sucht nach einem Wert, der keinem Schlüssel -% zugeordnet ist. -The attribute \lua{pick} searches for a value not assigned to a key. -% Der zuerst gefundene Wert, d. h. der weiter links stehende Wert, wird -% einem Schlüssel zugewiesen. -The first value found, i.e. the one further to the left, is assigned to -a key. - -\InputLua[firstline=4,lastline=6]{defs/attrs/pick/dimension.lua} - -\noindent -% Es wird nur in der aktuellen Ergebnistabelle gesucht und nicht auf -% anderen Ebenen in der rekursiven Datenstruktur. -Only the current result table is searched, not other levels in the -recursive data structure. - -\InputLua[firstline=4,lastline=11]{defs/attrs/pick/different-levels.lua} - -\noindent -% Die Suche nach Werte wird aktiviert, wenn das Attribut \lua{pick} auf -% \lua{true} gesetzt wird. -The search for values is activated when the attribute \lua{pick} is set -to a data type. -% Mit diesen Datentypen kann nach Werten gesucht werden -These data types can be used to search for values: -\directlua{ - local types = {} - for t, fn in pairs(luakeys.is) do - table.insert(types, t) - end - tex.print(table.concat(types, ', ')) -}. -% Verwendet den Datentyp any um jeden beliebigen Wert zu akzeptieren. -Use the data type “any” to accept any value. -% Wird einem Schlüssel bereits bei der Eingabe ein Wert zugewiesen, dann -% wird nicht weiter nach Werten gesucht. -If a value is already assigned to a key when it is entered, then no -further search for values is performed. -\InputLua[firstline=4,lastline=8]{defs/attrs/pick/value-set.lua} - -\noindent -% Das Attribut \lua{pick} akzeptiert auch mehrere Datentypen, die in -% einer Tabelle angegeben werden. -The \lua{pick} attribute also accepts multiple data types -specified in a table. - -\InputLua[firstline=4,lastline=10]{defs/attrs/pick/multiple-data-types.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{process}”} -\label{attr-process} - -% Das Attribut \lua{process} kann dazu verwendet werden, um eine Funktion -% zu definieren, deren Rückgabewert an den Schlüssel übergeben wird. -The \lua{process} attribute can be used to define a function whose -return value is passed to the key. -% -% Beim Aufruf der Funktion werden vier Parameter übergeben: -Four parameters are passed when the function is called: - -\begin{enumerate} -\item \lua{value}: -% Der zum Schlüssel gehörende aktuelle Wert. -The current value asssociated with the key. - -\item \lua{input}: -% Die Ergebnis-Tabelle, die vor dem Zeitpunkt geklont wurde, als mit dem -% Anwenden der Definitionen begonnen wurde. -The result table cloned before the time the definitions started to be -applied. - -\item \lua{result}: The table in which the final result will be saved. - -\item \lua{unknown}: The table in which the unknown key-value pairs -are stored. -\end{enumerate} - -\noindent -% Das folgende Beispiel demonstriert den Parameter \lua{value}: -The following example demonstrates the \lua{value} parameter: -\InputLua[firstline=4,lastline=14]{defs/attrs/process/parameter-value.lua} - -\noindent -% Das folgende Beispiel demonstriert den Parameter \lua{input}: -The following example demonstrates the \lua{input} parameter: - -\InputLua[firstline=4,lastline=16]{defs/attrs/process/parameter-input.lua} - -\noindent -% Das folgende Beispiel demonstriert den Parameter \lua{result}: -The following example demonstrates the \lua{result} parameter: - -\InputLua[firstline=4,lastline=12]{defs/attrs/process/parameter-result.lua} - -\noindent -% Das folgende Beispiel demonstriert den Parameter \lua{unknown}: -The following example demonstrates the \lua{unknown} parameter: - -\InputLua[firstline=4,lastline=11]{defs/attrs/process/parameter-unknown.lua} -\InputLua[firstline=15,lastline=15]{defs/attrs/process/parameter-unknown.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{required}”} - -% Durch das Attribut \lua{required} kann erzwungen werden, dass ein -% bestimmter Schlüssel angegeben werden muss. -The \lua{required} attribute can be used to enforce that a specific key -must be specified. -% -% Im untenstehenden Beispiel wird der Schlüssel \lua{important} als -% zwingend notwendig definiert. -In the example below, the key \lua{important} is defined as mandatory. - -\InputLua[firstline=4,lastline=5]{defs/attrs/required.lua} - -\noindent -% Fehlt der Schlüssel \lua{important} in der Eingabe, so tritt eine -% Fehlermeldung auf. -If the key \lua{important} is missing in the input, an error message -occurs. - -\InputLua[firstline=13,lastline=14]{defs/attrs/required.lua} - -\noindent -A recursive example: - -\InputLua[firstline=18,lastline=23]{defs/attrs/required.lua} - -\noindent -% Der Schlüssel \lua{important2} auf der Ebene 2 fehlt. -The \lua{important2} key on level 2 is missing. - -\InputLua[firstline=29,lastline=30]{defs/attrs/required.lua} - -\noindent -% Der Schlüssel \lua{important1} auf der untersten Schlüssel-Ebene fehlt. -The \lua{important1} key at the lowest key level is missing. - -\InputLua[firstline=38,lastline=39]{defs/attrs/required.lua} - -%% -% -%% - -\subsubsection{Attribute “\texttt{sub_keys}”} - -% Mit dem Attribut \lua{sub_keys} können ineinander verschachtelte -% Schlüssel-Wert-Paar-Definitionen aufgebaut werden. -The \lua{sub_keys} attribute can be used to build nested key-value pair -definitions. - -\InputLua[firstline=4,lastline=16]{defs/attrs/sub-keys.lua} - -%----------------------------------------------------------------------- -% -%----------------------------------------------------------------------- - -\subsection{Function “\texttt{render(result): string}”} - -The function \lua{render(result)} reverses the function -\lua{parse(kv_string)}. It takes a Lua table and converts this table -into a key-value string. The resulting string usually has a different -order as the input table. - -\InputLua[firstline=4,lastline=10]{functions/render.lua} - -\noindent -In Lua only tables with 1-based consecutive integer keys (a.k.a. array -tables) can be parsed in order. - -\InputLua[firstline=16,lastline=17]{functions/render.lua} - -%% -% -%% - -\subsection{Function “\texttt{debug(result): void}”} - -The function \lua{debug(result)} pretty prints a Lua table to standard -output (stdout). It is a utility function that can be used to debug and -inspect the resulting Lua table of the function \lua{parse}. You have to -compile your \TeX{} document in a console to see the terminal output. - -\InputLua[firstline=4,lastline=5]{functions/debug.lua} - -\noindent -The output should look like this: - -\begin{minted}{md} -{ - ['level1'] = { - ['level2'] = { - ['key'] = 'value', - }, - } -} -\end{minted} - -%% -% -%% - -\subsection{Function “\texttt{save(identifier, result): void}”} - -The function \lua{save(identifier, result)} saves a result (a -table from a previous run of \lua{parse}) under an identifier. -Therefore, it is not necessary to pollute the global namespace to -store results for the later usage. - -%% -% -%% - -\subsection{Function “\texttt{get(identifier): result}”} - -The function \lua{get(identifier)} retrieves a saved result from the -result store. - -%% -% -%% - -\subsection{Table “\texttt{is}”} - -% In der Tabelle \lua{is} werden einige Funktionen zusammengefasst, die -% überprüft ob eine Eingabe einem bestimmen Datentyp entspricht. -In the table \lua{is} some functions are summarized, which check whether -an input corresponds to a certain data type. -% -% Alle Funktionen akzeptieren nicht nur die entsprechenden Lua-Datentypen, -% sondern auch Eingaben als Zeichenketten. -All functions accept not only the corresponding Lua data types, but also -input as strings. -% -% Beispielsweise wird die Zeichenkette \lua{'true'} von der -% \lua{is.boolean()}-Funktion als Wahrheitswert erkannt. -For example, the string \lua{'true'} is recognized by the -\lua{is.boolean()} function as a boolean value. - -\subsubsection{Function “\texttt{is.boolean(value): boolean}”} -\InputLua[firstline=6,lastline=23]{functions/is/boolean.lua} - -\subsubsection{Function “\texttt{is.dimension(value): boolean}”} -\InputLua[firstline=6,lastline=16]{functions/is/dimension.lua} - -\subsubsection{Function “\texttt{is.integer(value): boolean}”} -\InputLua[firstline=6,lastline=11]{functions/is/integer.lua} - -\subsubsection{Function “\texttt{is.number(value): boolean}”} -\InputLua[firstline=6,lastline=13]{functions/is/number.lua} - -\subsubsection{Function “\texttt{is.string(value): boolean}”} -\InputLua[firstline=6,lastline=12]{functions/is/string.lua} - -\subsubsection{Function “\texttt{is.any(value): boolean}”} - -% Die Funktion \lua{is.any(value)} gibt immer wahr zurück und -% akzeptiert deshalb jeden Datentyp. -The function \lua{is.any(value)} always returns \lua{true} and -therefore accepts any data type. - -%% -% -%% - -\subsection{Table “\texttt{utils}”} - -\subsubsection{Function “\texttt{utils.scan_oarg(initial_delimiter?, end_delimiter?): string}”} - -% Plain \TeX{} kennt keine optionalen Argumente (oarg). -Plain \TeX{} does not know optional arguments (oarg). -% -% Die Funktion ermöglicht es nicht nur in \LaTeX{}, sondern auch in -% Plain \TeX{} nach optionalen Argumenten zu suchen. -The function \\ \lua{utils.scan_oarg(initial_delimiter?, -end_delimiter?): string} allows to search for optional arguments not only -in \LaTeX{} but also in Plain \TeX. -% -% Die Funktion basiert auf der Token-Bibliothek. -The function uses the token library built into Lua\TeX{}. -% -% Die beiden Parameter \lua{initial_delimiter} und \lua{end_delimiter} -% können weggelassen werden. -The two parameters \lua{initial_delimiter} and \lua{end_delimiter} can -be omitted. -% -% Dann werden eckige Klammern als Begrenzungszeichen angenommen. -Then square brackets are assumed to be delimiters. -% -% Dieser Lua-Code \lua{utils.scan_oarg('(', ')')} sucht beispielsweise -% nach an einem optionalen Argument in runden Klammern. -For example, this Lua code \lua{utils.scan_oarg('(', ')')} searches for -an optional argument in round brackets -% -% Die Funktion gibt die Zeichenkette zwischen den Begrenzungszeichen -% zurück, oder nil wenn Begrenzungszeichen gefunden werden konnten. -The function returns the string between the delimiters or \lua{nil} if -no delimiters could be found. -% -% Die Begrenzungszeichen sind im Ergebnis nicht enthalten. -The delimiters themselves are not included in the result. -% -% Nach dem \latex{\directlua{}} darf das Makro, indem -% \lua{utils.scan_oarg} eingesetzt wird, zu keinen Zeichen expandieren. -After the \latex{\directlua{}}, the macro using \lua{utils.scan_oarg} -must not expand to any characters. - -\InputLatex{utils/scan-oarg-plain.tex} - -%% -% -%% - -\subsection{Table “version”} - -% Das luakeys Projekt verwendet semantic versioning -The luakeys project uses semantic versioning. -% Die drei Versionenzahlen des Semantic Versioning Schemas werden in -% einer Tabelle in der Reihenfolge MAJOR, MINOR, PATCH als Ganzzahlen -% abgelegt. -The three version numbers of the semantic versioning scheme are stored -in a table as integers in the order MAJOR, MINOR, PATCH. -% Mit Hilfe dieser Tabelle kann überprüft werden, ob die richtige -% Version installiert ist. -This table can be used to check whether the correct version is -installed. - -\InputLua[firstline=4,lastline=10]{version.lua} - -%----------------------------------------------------------------------- -% -%----------------------------------------------------------------------- - -\section{Syntax of the recognized key-value format} - -%% -% -%% - -\subsection{An attempt to put the syntax into words} - -A key-value pair is definied by an equal sign (\texttt{key=value}). -Several key-value pairs or keys without values (naked keys) are lined up -with commas (\texttt{key=value,naked}) and build a key-value list. Curly -brackets can be used to create a recursive data structure of nested -key-value lists (\texttt{level1=\{level2=\{key=value,naked\}\}}). - -%% -% -%% - -\subsection{An (incomplete) attempt to put the syntax into the Extended Backus-Naur Form} - -\begin{grammar} - ::= \{ \} - - ::= `{' `}' - - ::= ( | | ) [ `,' ] - - ::= `=' ( | ) - - ::= - \alt - \alt - \alt - \alt - - ::= - - ::= ( [ ] | ) - - ::= `.' - - ::= `-' | `+' - - ::= \{ \} - - ::= `0' | `1' | `2' | `3' | `4' | `5' | `6' | `7' | `8' | `9' - - ::= `bp' | `BP' - \alt `cc' | `CC' - \alt `cm' | `CM' - \alt `dd' | `DD' - \alt `em' | `EM' - \alt `ex' | `EX' - \alt `in' | `IN' - \alt `mm' | `MM' - \alt `mu' | `MU' - \alt `nc' | `NC' - \alt `nd' | `ND' - \alt `pc' | `PC' - \alt `pt' | `PT' - \alt `px' | `PX' - \alt `sp' | `SP' - - ::= | - - ::= `true' | `TRUE' | `True' - - ::= `false' | `FALSE' | `False' -\end{grammar} - -... to be continued - -%% -% -%% - -\subsection{Recognized data types} - -\subsubsection{boolean} - -The strings \texttt{true}, \texttt{TRUE} and \texttt{True} are converted -into Lua’s boolean type \lua{true}, the strings \texttt{false}, -\texttt{FALSE} and \texttt{False} into \lua{false}. - -\begin{multicols}{2} -\InputLatex[firstline=5,lastline=12]{luakeysdebug/boolean-latex.tex} - -\columnbreak - -\begin{minted}{lua} -{ - ['lower case true'] = true, - ['upper case true'] = true, - ['title case true'] = true, - ['lower case false'] = false, - ['upper case false'] = false - ['title case false'] = false, -} -\end{minted} -\end{multicols} - -%% -% -%% - -\subsubsection{number} - -\begin{multicols}{2} -\InputLatex[firstline=5,lastline=13]{luakeysdebug/number-latex.tex} - -\columnbreak - -\begin{minted}{lua} -{ - ['num0'] = 42, - ['num1'] = 42, - ['num2'] = -42, - ['num3'] = 4.2, - ['num4'] = 0.42, - ['num5'] = 0.42, - ['num6'] = '0 . 42', -- string -} -\end{minted} -\end{multicols} - -%% -% -%% - -\subsubsection{dimension} - -% Luakeys versucht alle Einheiten zu erkennen, die in der TeX-Welt -% verwendet werden. -|luakeys| tries to recognize all units used in the \TeX{} world. -% -% Nach dem Lua\TeX-Quellcode . -According to the Lua\TeX{} source code -(\href{https://github.com/TeX-Live/luatex/blob/51db1985f5500dafd2393aa2e403fefa57d3cb76/source/texk/web2c/luatexdir/lua/ltexlib.c#L434-L625} -{source/texk/web2c/luatexdir/lua/ltexlib.c}) -% -% und nach dem Dimensionen-Modul der lulibs-Bibliothek -and the dimension module of the lualibs library -(\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua} -{lualibs-util-dim.lua}), -% -% müssten alle Einheiten erkannt werden -all units should be recognized. -\begin{multicols}{3} -\tiny -\begin{center} -\begin{tabular}{rl} -% \textbf{Unit name} -& \textbf{Description} \\\hline -bp & big point \\ -cc & cicero \\ -cm & centimeter \\ -dd & didot \\ -em & horizontal measure of \emph{M} \\ -ex & vertical measure of \emph{x} \\ -in & inch \\ -mm & milimeter \\ -mu & math unit \\ -nc & new cicero \\ -nd & new didot \\ -pc & pica \\ -pt & point \\ -px & x height current font \\ -sp & scaledpoint \\ -\end{tabular} -\end{center} - -\columnbreak - -\InputLatex[firstline=5,lastline=21]{luakeysdebug/dimension/all-latex.tex} - -\columnbreak - -\begin{minted}{lua} -{ - ['bp'] = 65781, - ['cc'] = 841489, - ['cm'] = 1864679, - ['dd'] = 70124, - ['em'] = 655360, - ['ex'] = 282460, - ['in'] = 4736286, - ['mm'] = 186467, - ['mu'] = 65536, - ['nc'] = 839105, - ['nd'] = 69925, - ['pc'] = 786432, - ['pt'] = 65536, - ['px'] = 65781, - ['sp'] = 1, -} -\end{minted} -\end{multicols} - -\noindent -% Im nächsten Beispiel werden die unterschiedlichen Notationsformen der -% Dimensionen illustriert. -The next example illustrates the different notations of the dimensions. - -\begin{multicols}{2} -\InputLatex[firstline=5,lastline=12]{luakeysdebug/dimension/notations-latex.tex} - -\columnbreak - -\begin{minted}{lua} -{ - ['upper'] = 1864679, - ['lower'] = 1864679, - ['space'] = 1864679, - ['plus'] = 1864679, - ['minus'] = -1864679, - ['nodim'] = '1 c m', -- string -} -\end{minted} -\end{multicols} - -%% -% -%% - -\subsubsection{string} - -% Es gibt zwei Möglichkeiten Zeichenketten anzugeben: -There are two ways to specify strings: -% -% Mit oder ohne doppelte Anführungszeichen. -With or without double quotes. -% -% Wenn der Text Kommas, geschweifte Klammern oder Gleichheitszeichen -% enthalten soll, müssen doppelte Anführungszeichen verwendet werden. -If the text have to contain commas, curly braces or equal signs, then -double quotes must be used. - -\InputLua[firstline=4,lastline=17]{data-types/string.lua} - -\subsubsection{Naked keys} - -% Nackte Schlüssel sind Schlüssel ohne Wert. -Naked keys are keys without a value. -% -% Mit der Option \lua{naked_as_value} können sie als Werte in ein Feld -% übernommen werden. -Using the option \lua{naked_as_value} they can be converted into values -and stored into an array. -% -% In Lua ist ein Feld eine Tabelle mit numerischen Indizes (der erste -% Index ist 1). -In Lua an array is a table with numeric indexes (The first index is 1). - -\InputLatex[firstline=5,lastline=12]{luakeysdebug/naked-keys-latex.tex} - -\noindent -% VAlle erkannten Datentypen können als eigenständige Werte verwendet -% werden. -All recognized data types can be used as standalone values. - -\InputLatex[firstline=14,lastline=19]{luakeysdebug/naked-keys-latex.tex} - -%----------------------------------------------------------------------- -% -%----------------------------------------------------------------------- - -\clearpage - -\section{Examples} - -%% -% -%% - -\subsection{Extend and modify keys of existing macros} - -Extend the includegraphics macro with a new key named \latex{caption} -and change the accepted values of the \latex{width} key. A number -between 0 and 1 is allowed and converted into -\latex{width=0.5\linewidth} - -\InputLua{extend-includegraphics/extend-keys.lua} -\InputLatex{extend-includegraphics/extend-keys-latex.tex} - -%% -% -%% - -\subsection{Process document class options} - -% Auf die Optionen einer \LaTeX{} Dokumentenklasse kann über das Macro -% \latex{\@classoptionslist} zu gegriffen werden. -The options of a \LaTeX{} document class can be accessed via the -\latex{\@classoptionslist} macro. -% -% Die Zeichenkette des Makros \latex{\@classoptionslist} ist bereits -% expandiert und normalisiert. -The string of the macro \latex{\@classoptionslist} is already expanded -and normalized. -% -% Neben Leerzeichen werden auch die geschweiften Klammern entfernt. -In addition to spaces, the curly brackets are also removed. -% Es ist deshalb nicht möglich, in einander geschachtelte hierarische -% Schlüssel-Wert-Paare über die Option zu verarbeiten. -It is therefore not possible to process nested hierarchical key-value -pairs via the option. - -\InputLatex{class-options/test-class.cls} -\InputLatex{class-options/use-test-class-latex.tex} - -\begin{minted}{lua} -{ - [1] = '12pt', - [2] = 'landscape', -} -\end{minted} - -%----------------------------------------------------------------------- -% -%----------------------------------------------------------------------- - -\clearpage - -\section{Debug packages} - -Two small debug packages are included in |luakeys|. One debug package -can be used in \LaTeX{} (luakeys-debug.sty) and one can be used in plain -\TeX{} (luakeys-debug.tex). Both packages provide only one command: -|\luakeysdebug{kv-string}| - -\begin{minted}{latex} -\luakeysdebug{one,two,three} -\end{minted} - -\noindent -Then the following output should appear in the document: -\bigskip - -\luakeysdebug{one,two,three} - -%% -% -%% - -\subsection{For plain \TeX: luakeys-debug.tex} - -An example of how to use the command in plain \TeX: - -\begin{minted}{latex} -\input luakeys-debug.tex -\luakeysdebug{one,two,three} -\bye -\end{minted} - -%% -% -%% - -\subsection{For \LaTeX: luakeys-debug.sty} - -An example of how to use the command in \LaTeX: - -\begin{minted}{latex} -\documentclass{article} -\usepackage{luakeys-debug} -\begin{document} -\luakeysdebug[ - unpack=false, - convert dimensions=false -]{one,two,three} -\end{document} -\end{minted} - -\section{Activity diagramm of the parsing process} - -\begin{center} -\includegraphics[width=0.8\linewidth]{Activity-Diagramm.eps} -\end{center} - -%----------------------------------------------------------------------- -% -%----------------------------------------------------------------------- - -\clearpage - -\section{Implementation} - -%% -% -%% - -\subsection{luakeys.lua} - -\inputminted[linenos=true]{lua}{luakeys.lua} - -%% -% -%% - -\clearpage - -\subsection{luakeys.tex} - -\inputminted[linenos=true]{latex}{luakeys.tex} - -%% -% -%% - -\clearpage - -\subsection{luakeys.sty} - -\inputminted[linenos=true]{latex}{luakeys.sty} - -%% -% -%% - -\clearpage - -\subsection{luakeys-debug.tex} - -\inputminted[linenos=true]{latex}{luakeys-debug.tex} - -%% -% -%% - -\clearpage - -\subsection{luakeys-debug.sty} - -\inputminted[linenos=true]{latex}{luakeys-debug.sty} - -\changes{0.1.0}{2021/01/18}{Inital release} -\changes{0.2.0}{2021/09/19}{ -* Allow all recognized data types as keys. -* Allow TeX macros in the values. -* New public Lua functions: save(identifier, result), get(identifier). -} -\changes{0.3.0}{2021/11/05}{ -* Add a LuaLaTeX wrapper “luakeys.sty”. -* Add a plain LuaTeX wrapper “luakeys.tex”. -* Rename the previous documentation file “luakeys.tex” to luakeys-doc.tex”. -} -\changes{0.4.0}{2021/12/31}{ -* Parser: Add support for nested tables (for example {{'a', 'b'}}). -* Parser: Allow only strings and numbers as keys. -* Parser: Remove support from Lua numbers with exponents (for example '5e+20'). -* Switch the Lua testing framework to busted. -} -\changes{0.5.0}{2022/04/04}{ -* Add possibility to change options globally. -* New option: standalone_as_true. -* Add a recursive converter callback / hook to process the parse tree. -* New option: case_insensitive_keys. -} -\changes{0.6.0}{2022/06/09}{ -* New feature: keys now can be defined using the function - “define(defs, opts)” or “define(kv_string, { defs = { key = { ... } } })” -* Rename the global options table from “default_options” to “opts”. -* New option “format_keys”. -* Remove option “case_insensitive_keys”. Use - “format_keys = \{ lower \}” to achieve the same effect. -* The default value of the option “convert_dimension” is now false. -* The option “standalone_as_true” is renamed to “naked_as_value”. - The boolean value of the option must be changed to the opposite to. - produce the previous effect. -* The function “print()” is now called “debug()”. -} -\changes{0.7.0}{2022/07/06}{ -* The project now uses semantic versioning. -* New definition attribute “pick” to pick standalone values and assign - them to a key. -* New function “utils.scan_oarg()” to search for an optional argument, - that means scan for tokens that are enclosed in square brackets. -* Extend and improve the documentation. -} -\changes{0.8.0}{2022/11/17}{ -* Add 6 new options to change the delimiters: “assignment_operator”, - “group_begin”, “group_end”, “list_separator”, “quotation_begin”, - “quotation_end”. -* Extend the documentation about the option “format_keys”. -} -\changes{0.9.0}{2022/11/21}{ -* The definition attibute “pick” accepts a new data type: “any”. -* The attribute value “true” for the attribute “pick” is deprecated. -* The attribute “pick” accepts now multiple data types specified in - a table. -* Add a new function called “any(value)” in the “is” table that accepts - any data type. -} -\changes{0.10.0}{2022/12/16}{ -* Add support for an invert flat that flips the value of naked keys. -* Add new options to specify which strings are recognized as Boolean - values. -} -\changes{0.11.0}{2022/12/23}{ -* Add a new function called “get_private_instance()” to load a private - version of the luakeys module. -} - -\pagebreak -\PrintChanges -\pagebreak -\PrintIndex -\end{document} 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, }, + + 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()', + }, + }, + }, } ---- The default options. -local default_options = clone_table(namespace.opts) +--- +---@return table # The public interface of the module. +local function main() -local function throw_error(message) - if type(tex.error) == 'function' then - tex.error(message) - else - error(message) - end -end + ---The default options. + local default_opts = utils.clone_table(namespace.opts) ---- 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 = {} + local error_messages = utils.clone_table(namespace.error_messages) + + --- + ---@param error_code string + ---@param args? table + local function throw_error(error_code, args) + utils.throw_error_code(error_messages, error_code, args) end - for key, _ in pairs(opts) do - if namespace.opts[key] == nil then - throw_error('Unknown parse option: ' .. tostring(key) .. '!') + + --- + ---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 - 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] + 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 + opts[name] = default_opts[name] + end end - end - for hook in pairs(opts.hooks) do - if namespace.hooks[hook] == nil then - throw_error('Unknown hook: ' .. tostring(hook) .. '!') + 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 - return opts -end -local l3_code_cctab = 10 + local l3_code_cctab = 10 ---- Convert back to strings --- @section + --- + ---Parser / Lpeg related + ---@section ---- The function `render(tbl)` reverses the function ---- `parse(kv_string)`. It takes a Lua table and converts this table ---- into a key-value string. The resulting string usually has a ---- different order as the input table. In Lua only tables with ---- 1-based consecutive integer keys (a.k.a. array tables) can be ---- parsed in order. ---- ----@param 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) + ---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 - 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) + 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 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 + 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 } + 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 - local alias_value - local used_alias_key - -- To get an error if the key and an alias is present - if value ~= nil then - alias_value = value - used_alias_key = key - end - for _, alias in ipairs(def.alias) do - local v = find_value(alias, def) - if v ~= nil then - if alias_value ~= nil then - throw_error(string.format( - 'Duplicate aliases “%s” and “%s” for key “%s”!', - used_alias_key, alias, key)) - end - used_alias_key = alias - alias_value = v - end - end - if alias_value ~= nil then - return alias_value - end - end, + 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.unpack then - result = visit_tree(result, callbacks.unpack) + if opts.invert_flag then + result = utils.visit_tree(result, callbacks.apply_invert_flag) + end + + return result end + result = apply_opts(result, opts) + + ---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 Binary files /dev/null and b/macros/luatex/generic/luakeys/luakeys.pdf 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 +} -- cgit v1.2.3