From a4e931576165d0a4a143915f4725399272e06fee Mon Sep 17 00:00:00 2001 From: Norbert Preining Date: Tue, 5 Apr 2022 03:01:24 +0000 Subject: CTAN sync 202204050301 --- macros/luatex/generic/luakeys/luakeys-debug.sty | 2 +- macros/luatex/generic/luakeys/luakeys-doc.pdf | Bin 159798 -> 169502 bytes macros/luatex/generic/luakeys/luakeys-doc.tex | 52 ++- macros/luatex/generic/luakeys/luakeys.lua | 483 +++++++++++++++--------- macros/luatex/generic/luakeys/luakeys.sty | 2 +- 5 files changed, 365 insertions(+), 174 deletions(-) (limited to 'macros/luatex') diff --git a/macros/luatex/generic/luakeys/luakeys-debug.sty b/macros/luatex/generic/luakeys/luakeys-debug.sty index 56e3329ac4..83ae86b08b 100644 --- a/macros/luatex/generic/luakeys/luakeys-debug.sty +++ b/macros/luatex/generic/luakeys/luakeys-debug.sty @@ -17,6 +17,6 @@ % luakeys-debug.sty and luakeys-debug.tex. \NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{luakeys-debug}[2021/12/31 v0.4 Debug package for luakeys.] +\ProvidesPackage{luakeys-debug}[2022/04/04 v0.5 Debug package for luakeys.] \input luakeys-debug.tex diff --git a/macros/luatex/generic/luakeys/luakeys-doc.pdf b/macros/luatex/generic/luakeys/luakeys-doc.pdf index 0f796cc12c..07f14d206b 100644 Binary files a/macros/luatex/generic/luakeys/luakeys-doc.pdf and b/macros/luatex/generic/luakeys/luakeys-doc.pdf differ diff --git a/macros/luatex/generic/luakeys/luakeys-doc.tex b/macros/luatex/generic/luakeys/luakeys-doc.tex index 99dfa6805b..3f7d1b0ea1 100644 --- a/macros/luatex/generic/luakeys/luakeys-doc.tex +++ b/macros/luatex/generic/luakeys/luakeys-doc.tex @@ -30,7 +30,7 @@ \url{josef@friedrich.rocks}\\% \href{https://github.com/Josef-Friedrich/luakeys}{github.com/Josef-Friedrich/luakeys}% } -\date{v0.4 from 2021/12/31} +\date{v0.5 from 2022/04/04} \maketitle @@ -526,16 +526,34 @@ In plain \TeX: \end{minted} \noindent -The function can be called with a options table. This two options are -supported. +The function can be called with an options table. This options are +supported: \begin{minted}{lua} local result = parse('one,two,three', { convert_dimensions = false, - unpack_single_array_value = false + unpack_single_array_value = false, + standalone_as_true = false, + converter = function(key, value, depth, current_table, root_table) + return key, value + end, + case_insensitive_keys = false, }) \end{minted} +\noindent +The options can also be set globally using the exported table +|default_options|: + +\begin{minted}{lua} +luakeys.parse('dim=1cm') -- {dim = 1864679} +luakeys.default_options.convert_dimensions = false +-- or: +-- local defaults = luakeys.default_options +-- defaults.convert_dimensions = false +luakeys.parse('dim=1cm') -- {dim = '1cm'} +\end{minted} + %% % %% @@ -695,6 +713,26 @@ An example of how to use the command in \LaTeX: \clearpage +\subsection{luakeys.tex} + +\inputminted[linenos=true]{latex}{luakeys.tex} + +%% +% +%% + +\clearpage + +\subsection{luakeys.tex} + +\inputminted[linenos=true]{latex}{luakeys.tex} + +%% +% +%% + +\clearpage + \subsection{luakeys-debug.tex} \inputminted[linenos=true]{latex}{luakeys-debug.tex} @@ -726,6 +764,12 @@ An example of how to use the command in \LaTeX: * Parser: Remove support from Lua numbers with exponents (for example '5e+20') * Switch the Lua testing framework to busted } +\changes{v0.5}{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 +} \pagebreak \PrintChanges \pagebreak diff --git a/macros/luatex/generic/luakeys/luakeys.lua b/macros/luatex/generic/luakeys/luakeys.lua index c5f51fcfad..c5ccf6b01d 100644 --- a/macros/luatex/generic/luakeys/luakeys.lua +++ b/macros/luatex/generic/luakeys/luakeys.lua @@ -50,8 +50,89 @@ if not tex then end end ---- A table to store parsed key-value results. -local result_store = {} +--- Option handling +-- @section + +--- This table stores all allowed option keys. +local option_keys = { + 'convert_dimensions', + 'unpack_single_array_values', + 'standalone_as_true', + 'converter', + 'case_insensitive_keys' +} + +--- The default options. +local default_options = { + convert_dimensions = true, + unpack_single_array_values = true, + standalone_as_true = false, +} + +local function throw_error(message) + if type(tex.error) == 'function' then + tex.error(message) + else + error(message) + end +end + +--- Convert a key so that it can be written as a table field without +-- quotes and square brackets (for example `one 2` becomes `one_2`). +-- The key can then reference values from a table using dot notation. +-- (`table["one 2"]` becomes `table.one_2`). +-- +-- @tparam string key The key to be converted. +-- +-- @treturn string The converted key. +local function luafy_key(key) + return key:gsub('[^%w]+', '_') +end + +--- Convert all keys in a table to strings containig only alphanumeric +-- characters and underscores. +-- +-- @param raw_options Some raw options. +-- +-- @treturn table Returns always a table. If the input value is not a +-- an empty table is returned. +local function luafy_options(raw_options) + if type(raw_options) ~= 'table' then + raw_options = {} + end + local options = {} + for key, value in pairs(raw_options) do + options[luafy_key(key)] = value + end + return options +end + +--- All option keys can be written with underscores or with spaces as +-- separators. +-- For the LaTeX version of the macro +-- `\luakeysdebug[options]{kv-string}`. +-- +-- @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) + options_raw = luafy_options(options_raw) + local options = {} + + for _, option_name in ipairs(option_keys) do + if options_raw[option_name] ~= nil then + options[option_name] = options_raw[option_name] + else + options[option_name] = default_options[option_name] + end + end + + return options +end + +--- Parser / Lpeg related +-- @section --- Generate the PEG parser using Lpeg. -- @@ -244,27 +325,55 @@ local function get_table_size(value) end --- Unpack a single valued array table like `{ 'one' }` into `one` or --- `{ 1 }` into `into`. +-- `{ 1 }` into `1`. -- -- @treturn If the value is a array like table with one non table typed -- value in it, the unpacked value, else the unchanged input. -local function unpack_single_valued_array_table(value) +local function unpack_single_valued_array_table(value, options) if type(value) == 'table' and get_array_size(value) == 1 and get_table_size(value) == 1 and type(value[1]) ~= 'table' then - return value[1] + if type(value[1]) == 'string' and options.standalone_as_true then + return value + else + return value[1] + end end return value end ---- This normalization tasks are performed on the raw input table coming --- directly from the PEG parser: +local function visit_parse_tree(parse_tree, callback_func) + if type(parse_tree) ~= 'table' then + throw_error('Parse tree has to be a table') + end + local function visit_parse_tree_recursive(root_table, current_table, result, depth, callback_func) + for key, value in pairs(current_table) do + if type(value) == 'table' then + value = visit_parse_tree_recursive(root_table, value, {}, depth + 1, callback_func) + end + + key, value = callback_func(key, value, depth, current_table, root_table) + + if key ~= nil and value ~= nil then + result[key] = value + end + end + if next(result) ~= nil then + return result + end + end + + return visit_parse_tree_recursive(parse_tree, parse_tree, {}, 1, callback_func) +end + +--- Normalize the result tables of the LPeg parser. This normalization +-- tasks are performed on the raw input table coming directly from the +-- PEG parser: -- --- 1. Trim all strings: ` text \n` into `text` --- 2. Unpack all single valued array like tables: `{ 'text' }` into +-- * Unpack all single valued array like tables: `{ 'text' }` into -- `text` -- -- @tparam table raw The raw input table coming directly from the PEG @@ -278,7 +387,7 @@ 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) + value = unpack_single_valued_array_table(value, options) end if type(value) == 'table' then result[key] = normalize_recursive(value, {}, options) @@ -288,7 +397,132 @@ local function normalize(raw, options) end return result end - return normalize_recursive(raw, {}, options) + raw = normalize_recursive(raw, {}, options) + + if options.standalone_as_true then + raw = visit_parse_tree(raw, function (key, value) + if type(key) == 'number' and type(value) == 'string' then + return value, true + end + return key, value + end) + end + + if options.case_insensitive_keys then + raw = visit_parse_tree(raw, function (key, value) + if type(key) == 'string' then + return key:lower(), value + end + return key, value + end) + end + + return raw +end + +--- Parse a LaTeX/TeX style key-value string into a Lua table. With +-- this function you should be able to parse key-value strings like +-- this example: +-- +-- show, +-- hide, +-- key with spaces = String without quotes, +-- string="String with double quotes: ,{}=", +-- dimension = 1cm, +-- number = -1.2, +-- list = {one,two,three}, +-- key value list = {one=one,two=two,three=three}, +-- nested key = { +-- nested key 2= { +-- key = value, +-- }, +-- }, +-- +-- The string above results in this Lua table: +-- +-- { +-- 'show', +-- 'hide', +-- ['key with spaces'] = 'String without quotes', +-- string = 'String with double quotes: ,{}=', +-- dimension = 1864679, +-- number = -1.2, +-- list = {'one', 'two', 'three'}, +-- key value list = { +-- one = 'one', +-- three = 'three', +-- two = 'two' +-- }, +-- ['nested key'] = { +-- ['nested key 2'] = { +-- key = 'value' +-- } +-- }, +-- } +-- +-- @tparam string kv_string A string in the TeX/LaTeX style key-value +-- format as described above. +-- +-- @tparam table options A table containing +-- settings: `convert_dimensions`, `unpack_single_array_values`, `standalone_as_true`, `converter` +-- +-- @treturn table A hopefully properly parsed table you can do +-- something useful with. +local function parse (kv_string, options) + if kv_string == nil then + return {} + end + options = normalize_parse_options(options) + + local parser = generate_parser(options) + local parse_tree = parser:match(kv_string) + + if options.converter ~= nil and type(options.converter) == 'function' then + parse_tree = visit_parse_tree(parse_tree, options.converter) + end + return normalize(parse_tree, options) +end + +--- Convert back to strings +-- @section + +--- The function `render(tbl)` reverses the function +-- `parse(kv_string)`. It takes a Lua table and converts this table +-- into a key-value string. The resulting string usually has a +-- different order as the input table. In Lua only tables with +-- 1-based consecutive integer keys (a.k.a. array tables) can be +-- parsed in order. +-- +-- @tparam table tbl A table to be converted into a key-value string. +-- +-- @treturn string A key-value string that can be passed to a TeX +-- macro. +local function render (tbl) + local function render_inner(tbl) + local output = {} + local function add(text) + table.insert(output, text) + end + for key, value in pairs(tbl) do + if (key and type(key) == 'string') then + if (type(value) == 'table') then + if (next(value)) then + add(key .. '={') + add(render_inner(value)) + add('},') + 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 `stringify(tbl, for_tex)` converts a Lua table into a @@ -371,171 +605,84 @@ local function stringify(input, for_tex) 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}`. +--- The function `pretty_print(tbl)` pretty prints a Lua table to standard +-- output (stdout). It is a utility function that can be used to +-- debug and inspect the resulting Lua table of the function +-- `parse`. You have to compile your TeX document in a console to +-- see the terminal output. -- --- @tparam table 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 +-- @tparam table tbl A table to be printed to standard output for +-- debugging purposes. +local function pretty_print(tbl) + print(stringify(tbl, false)) +end - if options_raw['convert dimensions'] ~= nil then - options['convert_dimensions'] = options_raw['convert dimensions'] - end +--- Store results +-- @section - if options.convert_dimensions == nil then - options.convert_dimensions = true - end +--- A table to store parsed key-value results. +local result_store = {} - if options.unpack_single_array_values == nil then - options.unpack_single_array_values = true - 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. +-- +-- @tparam string identifier The identifier under which the result is +-- saved. +-- +-- @tparam table result A result to be stored and that was created by +-- the key-value parser. +local function save(identifier, result) + result_store[identifier] = result +end - return options +--- The function `get(identifier): table` retrieves a saved result +-- from the result store. +-- +-- @tparam string identifier The identifier under which the result was +-- saved. +local function get(identifier) + -- if result_store[identifier] == nil then + -- throw_error('No stored result was found for the identifier \'' .. identifier .. '\'') + -- end + return result_store[identifier] end -return { +--- Exports +-- @section + +local export = { + --- @see default_options + default_options = default_options, + + --- @see stringify 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(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, - - --- The function `save(identifier, result): void` saves a result (a - -- table from a previous run of `parse`) under an identifier. - -- Therefore, it is not necessary to pollute the global namespace to - -- store results for the later usage. - -- - -- @tparam string identifier The identifier under which the result is - -- saved. - -- - -- @tparam table result A result to be stored and that was created by - -- the key-value parser. - save = function(identifier, result) - result_store[identifier] = result - end, - - --- The function `get(identifier): table` retrieves a saved result - -- from the result store. - -- - -- @tparam string identifier The identifier under which the result was - -- saved. - get = function(identifier) - return result_store[identifier] - end, + --- @see parse + parse = parse, + + --- @see render + render = render, + + --- @see pretty_print + print = pretty_print, + --- @see save + save = save, + + --- @see get + get = get, } + +-- http://olivinelabs.com/busted/#private +if _TEST then + export.luafy_key = luafy_key + export.luafy_options = luafy_options + export.normalize = normalize + export.normalize_parse_options = normalize_parse_options + export.unpack_single_valued_array_table = unpack_single_valued_array_table + export.visit_parse_tree = visit_parse_tree +end + +return export diff --git a/macros/luatex/generic/luakeys/luakeys.sty b/macros/luatex/generic/luakeys/luakeys.sty index 31d56854f3..2cf22b837a 100644 --- a/macros/luatex/generic/luakeys/luakeys.sty +++ b/macros/luatex/generic/luakeys/luakeys.sty @@ -17,5 +17,5 @@ % luakeys-debug.sty and luakeys-debug.tex. \NeedsTeXFormat{LaTeX2e} -\ProvidesPackage{luakeys}[2021/12/31 v0.4 Parsing key-value options using Lua.] +\ProvidesPackage{luakeys}[2022/04/04 v0.5 Parsing key-value options using Lua.] \directlua{luakeys = require('luakeys')} -- cgit v1.2.3