summaryrefslogtreecommitdiff
path: root/macros/luatex/generic
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2021-01-19 03:01:36 +0000
committerNorbert Preining <norbert@preining.info>2021-01-19 03:01:36 +0000
commit07549467cd23db09b2150c50bc9f93f10f08764a (patch)
tree38a43c82095b328a9dc8be00dd5bc86781eb6966 /macros/luatex/generic
parent62b141a25b6f63cb4cc626d1a005ecf67c19e5cd (diff)
CTAN sync 202101190301
Diffstat (limited to 'macros/luatex/generic')
-rw-r--r--macros/luatex/generic/luakeys/README.md52
-rw-r--r--macros/luatex/generic/luakeys/luakeys-debug.sty39
-rw-r--r--macros/luatex/generic/luakeys/luakeys-debug.tex33
-rw-r--r--macros/luatex/generic/luakeys/luakeys.lua483
-rw-r--r--macros/luatex/generic/luakeys/luakeys.pdfbin0 -> 140586 bytes
-rw-r--r--macros/luatex/generic/luakeys/luakeys.tex544
6 files changed, 1151 insertions, 0 deletions
diff --git a/macros/luatex/generic/luakeys/README.md b/macros/luatex/generic/luakeys/README.md
new file mode 100644
index 0000000000..cb4c429d4c
--- /dev/null
+++ b/macros/luatex/generic/luakeys/README.md
@@ -0,0 +1,52 @@
+# luakeys
+
+`luakeys` is a Lua module that can parse key-value options like the
+TeX packages [keyval](https://www.ctan.org/pkg/keyval),
+[kvsetkeys](https://www.ctan.org/pkg/kvsetkeys),
+[kvoptions](https://www.ctan.org/pkg/kvoptions),
+[xkeyval](https://www.ctan.org/pkg/xkeyval),
+[pgfkeys](https://www.ctan.org/pkg/pgfkeys) etc. do. `luakeys`,
+however, accompilshes this task entirely, by using the Lua language and
+doesn’t rely on TeX. Therefore this package can only be used with the
+TeX engine LuaTeX. Since `luakeys` uses
+[LPeg](http://www.inf.puc-rio.br/~roberto/lpeg/), the parsing
+mechanism should be pretty robust.
+
+## License
+
+Copyright 2021 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.
+
+## Tasks
+
+### Installing
+
+```
+make install
+```
+
+### Testing
+
+```
+luarocks install luaunit
+lua test/tests.lua
+```
+
+or
+
+```
+make test
+```
diff --git a/macros/luatex/generic/luakeys/luakeys-debug.sty b/macros/luatex/generic/luakeys/luakeys-debug.sty
new file mode 100644
index 0000000000..77dfa0063e
--- /dev/null
+++ b/macros/luatex/generic/luakeys/luakeys-debug.sty
@@ -0,0 +1,39 @@
+%% luakeys-debug.sty
+%% Copyright 2021 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-debug.sty
+% and luakeys-debug.tex.
+
+\NeedsTeXFormat{LaTeX2e}
+\ProvidesPackage{luakeys-debug}[2021/01/18 Debug package for luakeys.]
+
+%\input luakeys-debug.tex
+
+\directlua{
+ luakeys = require('luakeys')
+}
+
+\newcommand{\luakeysdebug}[2][]{
+ {
+ \tt
+ \parindent=0pt
+ \directlua{
+ local options_raw = luakeys.parse('#1')
+ local result = luakeys.parse('#2', options_raw)
+ tex.print(luakeys.stringify(result, true))
+ luakeys.print(result)
+ }
+ }
+}
diff --git a/macros/luatex/generic/luakeys/luakeys-debug.tex b/macros/luatex/generic/luakeys/luakeys-debug.tex
new file mode 100644
index 0000000000..499b1305f6
--- /dev/null
+++ b/macros/luatex/generic/luakeys/luakeys-debug.tex
@@ -0,0 +1,33 @@
+%% luakeys-debug.tex
+%% Copyright 2021 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-debug.sty
+% and luakeys-debug.tex.
+
+\directlua{
+ luakeys = require('luakeys')
+}
+
+\def\luakeysdebug#1{
+ {
+ \tt
+ \parindent=0pt
+ \directlua{
+ local result = luakeys.parse('#1')
+ tex.print(luakeys.stringify(result, true))
+ luakeys.print(result)
+ }
+ }
+}
diff --git a/macros/luatex/generic/luakeys/luakeys.lua b/macros/luatex/generic/luakeys/luakeys.lua
new file mode 100644
index 0000000000..e4ecaab7f7
--- /dev/null
+++ b/macros/luatex/generic/luakeys/luakeys.lua
@@ -0,0 +1,483 @@
+-- luakeys-debug.tex
+-- Copyright 2021 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-debug.sty
+-- and luakeys-debug.tex.
+
+--- A key-value parser written with Lpeg.
+--
+-- Explanations of some LPeg notation forms:
+--
+-- * `patt ^ 0` = `expression *`
+-- * `patt ^ 1` = `expression +`
+-- * `patt ^ -1` = `expression ?`
+-- * `patt1 * patt2` = `expression1 expression2`: Sequence
+-- * `patt1 + patt2` = `expression1 / expression2`: Ordered choice
+--
+-- * [TUGboat article: Parsing complex data formats in LuaTEX with LPEG](https://tug.org/TUGboat/tb40-2/tb125menke-lpeg.pdf)
+--
+-- @module luakeys
+
+local lpeg = require('lpeg')
+
+if not tex then
+ tex = {}
+
+ -- Dummy function for the tests.
+ tex['sp'] = function (input)
+ return 1234567
+ end
+end
+
+--- Generate the PEG parser using Lpeg.
+--
+-- @treturn userdata The parser
+local function generate_parser(options)
+ -- Optional whitespace
+ local white_space = lpeg.S(' \t\n\r')^0
+
+ --- Match literal string surrounded by whitespace
+ local ws = function(match)
+ return white_space * lpeg.P(match) * white_space
+ end
+
+ local boolean_true =
+ lpeg.P('true') +
+ lpeg.P('TRUE') +
+ lpeg.P('True')
+
+ local boolean_false =
+ lpeg.P('false') +
+ lpeg.P('FALSE') +
+ lpeg.P('False')
+
+ local number = lpeg.P({'number',
+ number =
+ lpeg.V('int') *
+ lpeg.V('frac')^-1 *
+ lpeg.V('exp')^-1,
+
+ int = lpeg.V('sign')^-1 * (
+ lpeg.R('19') * lpeg.V('digits') + lpeg.V('digit')
+ ),
+
+ sign = lpeg.S('+-'),
+ digit = lpeg.R('09'),
+ digits = lpeg.V('digit') * lpeg.V('digits') + lpeg.V('digit'),
+ frac = lpeg.P('.') * lpeg.V('digits'),
+ exp = lpeg.S('eE') * lpeg.V('sign')^-1 * lpeg.V('digits'),
+ })
+
+ --- Define data type dimension.
+ --
+ -- @return Lpeg patterns
+ local function build_dimension_pattern()
+ local sign = lpeg.S('-+')
+ local integer = lpeg.R('09')^1
+ local tex_number = (integer^1 * (lpeg.P('.') * integer^1)^0) + (lpeg.P('.') * integer^1)
+ local unit
+ -- https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua
+ for _, dimension_extension in ipairs({'bp', 'cc', 'cm', 'dd', 'em', 'ex', 'in', 'mm', 'nc', 'nd', 'pc', 'pt', 'sp'}) do
+ if unit then
+ unit = unit + lpeg.P(dimension_extension)
+ else
+ unit = lpeg.P(dimension_extension)
+ end
+ end
+
+ local dimension = (sign^0 * tex_number * unit)
+
+ if options.convert_dimensions then
+ return dimension / tex.sp
+ else
+ return lpeg.C(dimension)
+ end
+ end
+
+ --- Add values to a table in a 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.
+ --
+ -- # Index value
+ --
+ -- If arg2 is nil, then arg1 is the value and is added as an indexed
+ -- (by an integer) value.
+ --
+ -- @tparam table table
+ -- @tparam mixed arg1
+ -- @tparam mixed arg2
+ --
+ -- @treturn table
+ local add_to_table = function(table, arg1, arg2)
+ if arg2 == nil then
+ local index = #table + 1
+ return rawset(table, index, arg1)
+ else
+ return rawset(table, arg1, arg2)
+ end
+ end
+
+ return lpeg.P({
+ 'list',
+
+ list = lpeg.Cf(
+ lpeg.Ct('') * lpeg.V('list_item')^0,
+ add_to_table
+ ),
+
+ list_container =
+ ws('{') * lpeg.V('list') * ws('}'),
+
+ list_item =
+ lpeg.Cg(
+ lpeg.V('key_value_pair') +
+ lpeg.V('value')
+ ) * ws(',')^-1,
+
+ key_value_pair =
+ (lpeg.V('key') * ws('=')) * (lpeg.V('list_container') + lpeg.V('value')),
+
+ -- ./ for tikz style keys
+ key_word = lpeg.R('az', 'AZ', '09', './'),
+
+ key = white_space * lpeg.C(
+ lpeg.V('key_word')^1 *
+ (lpeg.P(' ')^1 * lpeg.V('key_word')^1)^0
+ ) * white_space,
+
+ value =
+ lpeg.V('boolean') +
+ lpeg.V('dimension') +
+ lpeg.V('number') +
+ lpeg.V('string_quoted') +
+ lpeg.V('string_unquoted'),
+
+ boolean =
+ boolean_true * lpeg.Cc(true) +
+ boolean_false * lpeg.Cc(false),
+
+ dimension = build_dimension_pattern(),
+
+ string_quoted =
+ white_space * lpeg.P('"') *
+ lpeg.C((lpeg.P('\\"') + 1 - lpeg.P('"'))^0) *
+ lpeg.P('"') * white_space,
+
+ string_unquoted =
+ white_space *
+ lpeg.C((1 - lpeg.S('{},='))^1) *
+ white_space,
+
+ number =
+ white_space * (number / tonumber) * white_space,
+
+ })
+end
+
+local function trim(input_string)
+ return input_string:gsub('^%s*(.-)%s*$', '%1')
+end
+
+--- Get the size of an array like table `{ 'one', 'two', 'three' }` = 3.
+--
+-- @tparam table value A table or any input.
+--
+-- @treturn number The size of the array like table. 0 if the input is
+-- no table or the table is empty.
+local function get_array_size(value)
+ local count = 0
+ if type(value) == 'table' then
+ for _ in ipairs(value) do count = count + 1 end
+ end
+ return count
+end
+
+--- Get the size of a table `{ one = 'one', 'two', 'three' }` = 3.
+--
+-- @tparam table value A table or any input.
+--
+-- @treturn number The size of the array like table. 0 if the input is
+-- no table or the table is empty.
+local function get_table_size(value)
+ local count = 0
+ if type(value) == 'table' then
+ for _ in pairs(value) do count = count + 1 end
+ end
+ return count
+end
+
+--- Unpack a single valued array table like `{ 'one' }` into `one` or
+-- `{ 1 }` into `into`.
+--
+-- @treturn If the value is a array like table with one non table typed
+-- value in it, the unpacked value, else the unchanged input.
+local function unpack_single_valued_array_table(value)
+ if
+ type(value) == 'table' and
+ get_array_size(value) == 1 and
+ get_table_size(value) == 1 and
+ type(value[1]) ~= 'table'
+ then
+ return value[1]
+ end
+ return value
+end
+
+--- This normalization tasks are performed on the raw input table
+-- coming directly from the PEG parser:
+--
+-- 1. Trim all strings: ` text \n` into `text`
+-- 2. Unpack all single valued array like tables: `{ 'text' }`
+-- into `text`
+--
+-- @tparam table raw The raw input table coming directly from the PEG
+-- parser
+--
+-- @treturn table A normalized table ready for the outside world.
+local function normalize(raw, options)
+ local function normalize_recursive(raw, result, options)
+ for key, value in pairs(raw) do
+ if options.unpack_single_array_values then
+ value = unpack_single_valued_array_table(value)
+ end
+ if type(value) == 'table' then
+ result[key] = normalize_recursive(value, {}, options)
+ elseif type(value) == 'string' then
+ result[key] = trim(value)
+ else
+ result[key] = value
+ end
+ end
+ return result
+ end
+ return normalize_recursive(raw, {}, options)
+end
+
+--- The function `stringify(tbl, for_tex)` converts a Lua table into a
+-- printable string. Stringify a table means to convert the table into
+-- a string. This function is used to realize the `print` function.
+-- `stringify(tbl, true)` (`for_tex = true`) generates a string which
+-- can be embeded into TeX documents. The macro `\luakeysdebug{}` uses
+-- this option. `stringify(tbl, false)` or `stringify(tbl)` generate a
+-- string suitable for the terminal.
+--
+-- @tparam table input A table to stringify.
+--
+-- @tparam boolean for_tex Stringify the table into a text string that
+-- can be embeded inside a TeX document via tex.print(). Curly braces
+-- and whites spaces are escaped.
+--
+-- https://stackoverflow.com/a/54593224/10193818
+local function stringify(input, for_tex)
+ local line_break, start_bracket, end_bracket, indent
+
+ if for_tex then
+ line_break = '\\par'
+ start_bracket = '\\{'
+ end_bracket = '\\}'
+ indent = '\\ \\ '
+ else
+ line_break = '\n'
+ start_bracket = '{'
+ end_bracket = '}'
+ indent = ' '
+ 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
+
+ for key, value in pairs(input) do
+ if (key and type(key) == 'number' or type(key) == 'string') then
+ key = string.format('[\'%s\']', key);
+
+ if (type(value) == 'table') then
+ if (next(value)) then
+ add(depth, key .. ' = ' .. start_bracket);
+ add(0, stringify_inner(value, depth + 1, for_tex));
+ 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(input, 1) .. line_break .. end_bracket
+end
+
+--- For the LaTeX version of the macro
+-- `\luakeysdebug[options]{kv-string}`.
+--
+-- @tparam table options_raw Options in a raw format. The table may be
+-- empty or some keys are not set.
+--
+-- @treturn table
+local function normalize_parse_options (options_raw)
+ if options_raw == nil then
+ options_raw = {}
+ end
+ local options = {}
+
+ if options_raw['unpack single array values'] ~= nil then
+ options['unpack_single_array_values'] = options_raw['unpack single array values']
+ end
+
+ if options_raw['convert dimensions'] ~= nil then
+ options['convert_dimensions'] = options_raw['convert dimensions']
+ end
+
+ if options.convert_dimensions == nil then
+ options.convert_dimensions = true
+ end
+
+ if options.unpack_single_array_values == nil then
+ options.unpack_single_array_values = true
+ end
+
+ return options
+end
+
+return {
+ stringify = stringify,
+
+ --- Parse a LaTeX/TeX style key-value string into a Lua table. With
+ -- this function you should be able to parse key-value strings like
+ -- this example:
+ --
+ -- show,
+ -- hide,
+ -- key with spaces = String without quotes,
+ -- string="String with double quotes: ,{}=",
+ -- dimension = 1cm,
+ -- number = -1.2,
+ -- list = {one,two,three},
+ -- key value list = {one=one,two=two,three=three},
+ -- nested key = {
+ -- nested key 2= {
+ -- key = value,
+ -- },
+ -- },
+ --
+ -- The string above results in this Lua table:
+ --
+ -- {
+ -- 'show',
+ -- 'hide',
+ -- ['key with spaces'] = 'String without quotes',
+ -- string = 'String with double quotes: ,{}=',
+ -- dimension = 1864679,
+ -- number = -1.2,
+ -- list = {'one', 'two', 'three'},
+ -- key value list = {
+ -- one = 'one',
+ -- three = 'three',
+ -- two = 'two'
+ -- },
+ -- ['nested key'] = {
+ -- ['nested key 2'] = {
+ -- key = 'value'
+ -- }
+ -- },
+ -- }
+ --
+ -- @tparam string kv_string A string in the TeX/LaTeX style key-value
+ -- format as described above.
+ --
+ -- @tparam table options A table containing
+ -- settings: `convert_dimensions` `unpack_single_array_values`
+ --
+ -- @treturn table A hopefully properly parsed table you can do
+ -- something useful with.
+ parse = function (kv_string, options)
+ if kv_string == nil then
+ return {}
+ end
+ options = normalize_parse_options()
+
+ local parser = generate_parser(options)
+ return normalize(parser:match(kv_string), options)
+ end,
+
+ --- The function `render(tbl)` reverses the function
+ -- `parse(kv_string)`. It takes a Lua table and converts this table
+ -- into a key-value string. The resulting string usually has a
+ -- different order as the input table. In Lua only tables with
+ -- 1-based consecutive integer keys (a.k.a. array tables) can be
+ -- parsed in order.
+ --
+ -- @tparam table tbl A table to be converted into a key-value string.
+ --
+ -- @treturn string A key-value string that can be passed to a TeX
+ -- macro.
+ render = function (tbl)
+ local function render_inner(tbl)
+ local output = {}
+ local function add(text)
+ table.insert(output, text)
+ end
+ for key, value in pairs(tbl) do
+ if (key and type(key) == 'string') then
+ if (type(value) == 'table') then
+ if (next(value)) then
+ add(key .. '={');
+ add(render_inner(value));
+ add('},');
+ else
+ add(key .. '={},');
+ end
+ else
+ add(key .. '=' .. tostring(value) .. ',');
+ end
+ else
+ add(tostring(value) .. ',')
+ end
+ end
+ return table.concat(output)
+ end
+ return render_inner(tbl)
+ end,
+
+ --- The function `print(tbl)` pretty prints a Lua table to standard
+ -- output (stdout). It is a utility function that can be used to
+ -- debug and inspect the resulting Lua table of the function
+ -- `parse`. You have to compile your TeX document in a console to
+ -- see the terminal output.
+ --
+ -- @tparam table tbl A table to be printed to standard output for
+ -- debugging purposes.
+ print = function(tbl)
+ print(stringify(tbl, false))
+ end,
+
+}
diff --git a/macros/luatex/generic/luakeys/luakeys.pdf b/macros/luatex/generic/luakeys/luakeys.pdf
new file mode 100644
index 0000000000..ceaebbee04
--- /dev/null
+++ b/macros/luatex/generic/luakeys/luakeys.pdf
Binary files differ
diff --git a/macros/luatex/generic/luakeys/luakeys.tex b/macros/luatex/generic/luakeys/luakeys.tex
new file mode 100644
index 0000000000..75ffbc8c70
--- /dev/null
+++ b/macros/luatex/generic/luakeys/luakeys.tex
@@ -0,0 +1,544 @@
+\documentclass{ltxdoc}
+
+\usepackage{hyperref}
+\EnableCrossrefs
+\CodelineIndex
+\RecordChanges
+
+\usepackage{mdframed}
+\usepackage{minted}
+\usepackage{luakeys-debug}
+\usepackage{multicol}
+\usepackage{luacode}
+\usepackage{syntax}
+\usemintedstyle{friendly}
+\BeforeBeginEnvironment{minted}{\begin{mdframed}}
+\AfterEndEnvironment{minted}{\end{mdframed}}
+\setminted{
+ breaklines=true,
+ fontsize=\footnotesize,
+}
+\def\lua#1{\mintinline{lua}|#1|}
+
+\begin{document}
+
+\providecommand*{\url}{\texttt}
+
+\title{The \textsf{luakeys} package}
+\author{%
+ Josef Friedrich\\%
+ \url{josef@friedrich.rocks}\\%
+ \href{https://github.com/Josef-Friedrich/nodetree}{github.com/Josef-Friedrich/luakeys}%
+}
+\date{v0.1 from 2021/01/18}
+
+\maketitle
+
+\vfill
+
+%\luakeysdebug{level1={level2={level3={dim=1cm,bool=true,num=-1e-03,str=lua}}}}
+
+\begin{minted}{lua}
+local luakeys = require('luakeys')
+local kv = luakeys.parse('level1={level2={level3={dim=1cm,bool=true,num=-1e-03,str=lua}}}')
+luakeys.print(kv)
+\end{minted}
+
+\noindent
+Result:
+
+\begin{center}
+\begin{minted}{lua}
+{
+ ['level1'] = {
+ ['level2'] = {
+ ['level3'] = {
+ ['dim'] = 1864679,
+ ['bool'] = true,
+ ['num'] = -0.001
+ ['str'] = 'lua',
+ }
+ }
+ }
+}
+\end{minted}
+\end{center}
+
+\vfill
+
+\strut
+
+\newpage
+
+\tableofcontents
+
+\newpage
+
+\section{Introduction}
+
+\noindent
+|luakeys| is a Lua module that can parse key-value options like the
+\TeX{} packages \href{https://www.ctan.org/pkg/keyval}{keyval},
+\href{https://www.ctan.org/pkg/kvsetkeys}{kvsetkeys},
+\href{https://www.ctan.org/pkg/kvoptions}{kvoptions},
+\href{https://www.ctan.org/pkg/xkeyval}{xkeyval},
+\href{https://www.ctan.org/pkg/pgfkeys}{pgfkeys} etc. do. |luakeys|,
+however, accompilshes this task entirely, by using the Lua language and
+doesn’t rely on \TeX{}. Therefore this package can only be used with the
+\TeX{} engine Lua\TeX{}. Since |luakeys| uses
+\href{http://www.inf.puc-rio.br/~roberto/lpeg/}{LPeg}, the parsing
+mechanism should be pretty robust.
+
+The TUGboat article
+\href{http://www.tug.org/tugboat/tb30-1/tb94wright-keyval.pdf}
+{“Implementing key–value input: An introduction” (Volume 30 (2009), No. 1)}
+by Joseph Wright and Christian Feuersänger gives a good overview of the
+available key-value packages.
+
+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)}.
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Syntax of the recognized key-value format}
+
+%%
+%
+%%
+
+\subsection{A attempt to put the syntax into words}
+
+A key-value pair is definied by an equal sign (\texttt{key=value}).
+Several key-value pairs or values without keys are lined up with commas
+(\texttt{key=value,value}) and build a key-value list. Curly brackets
+can be used to create a recursive data structure of nested key-value
+lists (\texttt{level1=\{level2=\{key=value,value\}\}}).
+
+%%
+%
+%%
+
+\subsection{An (incomplete) attempt to put the syntax into the (Extended) Backus-Naur form}
+
+\begin{grammar}
+<list> ::= <list-item> | <list-item> <list>
+
+<list-item> ::= ( <key-value-pair> | <value-without-key> ) [ `,' ]
+
+<list-container> ::== `{' <list> `}'
+
+<value> ::= <boolean>
+ \alt <dimension>
+ \alt <number>
+ \alt <string-quoted>
+ \alt <string-unquoted>
+\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}
+\begin{minted}{latex}
+\luakeysdebug{
+ 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}
+\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}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsubsection{number}
+
+\begin{multicols}{2}
+\begin{minted}{latex}
+\luakeysdebug{
+ num1 = 4,
+ num2 = -4,
+ num3 = 0.4,
+ num4 = 4.57e-3,
+ num5 = 0.3e12,
+ num6 = 5e+20
+}
+\end{minted}
+\begin{minted}{lua}
+{
+ ['num1'] = 4,
+ ['num2'] = -4,
+ ['num3'] = 0.4,
+ ['num4'] = 0.00457,
+ ['num5'] = 300000000000.0,
+ ['num6'] = 5e+20
+}
+\end{minted}
+\end{multicols}
+
+%%
+%
+%%
+
+\clearpage
+
+\subsubsection{dimension}
+
+|luakeys| detects \TeX{} dimensions and automatically converts the
+dimensions into scaled points using the function \lua{tex.sp(dim)}. Use
+the option \lua{convert_dimensions} of the function
+\lua{parse(kv_string, options)} to disalbe the automatic conversion.
+
+\begin{minted}{lua}
+local result = parse('dim=1cm', {
+ convert_dimensions = false,
+})
+\end{minted}
+
+If you want to convert a scale point into a unit string you can used the module
+\href{https://raw.githubusercontent.com/latex3/lualibs/master/lualibs-util-dim.lua}{lualibs-util-dim.lua}.
+
+\begin{minted}{latex}
+\begin{luacode}
+require('lualibs')
+tex.print(number.todimen(tex.sp('1cm'), 'cm', '%0.0F%s'))
+\end{luacode}
+\end{minted}
+
+\begin{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 \\
+nc & new cicero \\
+nd & new didot \\
+pc & pica \\
+pt & point \\
+sp & scaledpoint \\
+\end{tabular}
+\end{center}
+
+\begin{multicols}{2}
+\begin{minted}{latex}
+\luakeysdebug{
+ bp = 1bp,
+ cc = 1cc,
+ cm = 1cm,
+ dd = 1dd,
+ em = 1em,
+ ex = 1ex,
+ in = 1in,
+ mm = 1mm,
+ nc = 1nc,
+ nd = 1nd,
+ pc = 1pc,
+ pt = 1pt,
+ sp = 1sp,
+}
+\end{minted}
+\begin{minted}{lua}
+{
+ ['bp'] = 65781,
+ ['cc'] = 841489,
+ ['cm'] = 1864679,
+ ['dd'] = 70124,
+ ['em'] = 655360,
+ ['ex'] = 282460,
+ ['in'] = 4736286,
+ ['mm'] = 186467,
+ ['nc'] = 839105,
+ ['nd'] = 69925,
+ ['pc'] = 786432,
+ ['pt'] = 65536,
+ ['sp'] = 1,
+}
+\end{minted}
+\end{multicols}
+
+%%
+%
+%%
+
+\subsubsection{string}
+
+There are two ways to specify strings: With or without quotes. If the
+text have to contain commas or equal signs, then double quotation
+marks must be used.
+
+\begin{multicols}{2}
+\begin{minted}{latex}
+\luakeysdebug{
+ without quotes = no commas and equal signs are allowed,
+ with double quotes = ", and = are allowed",
+}
+\end{minted}
+\begin{minted}{lua}
+{
+ ['without quotes'] = 'no commas and equal signs are allowed',
+ ['with double quotes'] = ', and = are allowed',
+}
+\end{minted}
+\end{multicols}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Exported functions of the Lua module \texttt{luakeys.lua}}
+
+To learn more about the individual functions (local functions), pleasecommand
+read the \href{https://josef-friedrich.github.io/luakeys/}{source code
+documentation}, which was created with
+\href{http://stevedonovan.github.io/ldoc/}{LDoc}. The Lua module exports
+this functions:
+
+\begin{minted}{lua}
+local luakeys = require('luakeys')
+local parse = luakeys.parse
+local render = luakeys.render
+--local print = luakeys.print -- That would overwrite the built-in Lua function
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{\texttt{parse(kv\_string, options)}: table}
+
+The function \lua{parse(input_string, options)} is the main method of
+this module. It parses a key-value string into a Lua table.
+
+\begin{minted}{latex}
+\newcommand{\mykeyvalcmd}[1][]{
+ \directlua{
+ result = luakeys.parse('#1')
+ luakeys.print(result)
+ }
+ #2
+}
+\mykeyvalcmd[one=1]{test}
+\end{minted}
+
+\noindent
+In plain \TeX:
+
+\begin{minted}{latex}
+\def\mykeyvalcommand#1{
+ \directlua{
+ result = luakeys.parse('#1')
+ luakeys.print(result)
+ }
+}
+\mykeyvalcmd{one=1}
+\end{minted}
+
+\noindent
+The function can be called with a options table. This two options are
+supported.
+
+\begin{minted}{lua}
+local result = parse('one,two,three', {
+ convert_dimensions = false,
+ unpack_single_array_value = false
+})
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{\texttt{render(tbl)}: string}
+
+The function \lua{render(tbl)} reverses the function
+\lua{parse(kv_string)}. It takes a Lua table and converts this table
+into a key-value string. The resulting string usually has a different
+order as the input table.
+
+\begin{minted}{lua}
+result = luakeys.parse('one=1,two=2,tree=3,')
+print(luakeys.render(result))
+--- one=1,two=2,tree=3,
+--- or:
+--- two=2,one=1,tree=3,
+--- or:
+--- ...
+\end{minted}
+
+\noindent
+In Lua only tables with 1-based consecutive integer keys (a.k.a. array
+tables) can be parsed in order.
+
+\begin{minted}{lua}
+result = luakeys.parse('one,two,three')
+print(luakeys.render(result))
+--- one,two,three, (always)
+\end{minted}
+
+%%
+%
+%%
+
+\subsection{\texttt{print(tbl): void}}
+
+The function \lua{print(tbl)} pretty prints a Lua table to standard
+output (stdout). It is a utility function that can be used to debug and
+inspect the resulting Lua table of the function \lua{parse}. You have to
+compile your \TeX{} document in a console to see the terminal output.
+
+%\luakeysdebug{level1={level2={key=value}}}
+
+\begin{minted}{lua}
+result = luakeys.parse('level1={level2={key=value}}')
+luakeys.print(result)
+\end{minted}
+
+\noindent
+The output should look like this:
+
+\begin{minted}{md}
+{
+ ['level1'] = {
+ ['level2'] = {
+ ['key'] = 'value',
+ },
+}
+\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 single array values=false,
+ convert dimensions=false
+]{one,two,three}
+\end{document}
+\end{minted}
+
+%-----------------------------------------------------------------------
+%
+%-----------------------------------------------------------------------
+
+\clearpage
+
+\section{Implementation}
+
+%%
+%
+%%
+
+\subsection{luakeys.lua}
+
+\inputminted[linenos=true]{lua}{luakeys.lua}
+
+%%
+%
+%%
+
+\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}{2020/01/17}{Inital release}
+
+\pagebreak
+\PrintChanges
+\pagebreak
+\PrintIndex
+\end{document}