48,404
edits
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
local export = {} | local export = {} | ||
local | local fun_is_callable_module = "Module:fun/isCallable" | ||
local languages_module = "Module:languages" | |||
local parameters_module = "Module:parameters" | local parameters_module = "Module:parameters" | ||
local string_char_module = "Module:string/char" | |||
local string_utilities_module = "Module:string utilities" | |||
local table_insert_if_not_module = "Module:table/insertIfNot" | |||
local assert = assert | |||
local concat = table.concat | |||
local dump = mw.dumpObject | |||
local error = error | |||
local insert = table.insert | |||
local ipairs = ipairs | |||
local list_to_text = mw.text.listToText | local list_to_text = mw.text.listToText | ||
local | local pairs = pairs | ||
local | local require = require | ||
local | local sort = table.sort | ||
local | local type = type | ||
local ugsub = mw.ustring.gsub | |||
local function convert_val(...) | |||
local function | convert_val = require(parameters_module).convert_val | ||
return convert_val(...) | |||
return | |||
end | end | ||
local function get_lang(...) | |||
get_lang = require(languages_module).getByCode | |||
return get_lang(...) | |||
end | |||
local function insert_if_not(...) | |||
insert_if_not = require(table_insert_if_not_module) | |||
return insert_if_not(...) | |||
end | |||
local function is_callable(...) | |||
is_callable = require(fun_is_callable_module) | |||
return is_callable(...) | |||
end | |||
local function split(...) | |||
split = require(string_utilities_module).split | |||
return split(...) | |||
end | |||
local function u(...) | |||
u = require(string_char_module) | |||
return u(...) | |||
end | |||
local function umatch(...) | |||
umatch = require(string_utilities_module).match | |||
return umatch(...) | |||
end | |||
--[==[ intro: | --[==[ intro: | ||
| Line 63: | Line 102: | ||
]==] | ]==] | ||
function export.parse_balanced_segment_run(segment_run, open, close) | function export.parse_balanced_segment_run(segment_run, open, close) | ||
return | return split(segment_run, "(%b" .. open .. close .. ")") | ||
end | end | ||
| Line 69: | Line 108: | ||
--[=[ | --[=[ | ||
function export.parse_balanced_segment_run(segment_run, open, close) | function export.parse_balanced_segment_run(segment_run, open, close) | ||
local break_on_open_close = | local break_on_open_close = split(segment_run, "([%" .. open .. "%" .. close .. "])") | ||
local text_and_specs = {} | local text_and_specs = {} | ||
local level = 0 | local level = 0 | ||
| Line 76: | Line 115: | ||
if i % 2 == 0 then | if i % 2 == 0 then | ||
if seg == open then | if seg == open then | ||
insert(seg_group, seg) | |||
level = level + 1 | level = level + 1 | ||
else | else | ||
assert(seg == close) | assert(seg == close) | ||
insert(seg_group, seg) | |||
level = level - 1 | level = level - 1 | ||
if level < 0 then | if level < 0 then | ||
error("Unmatched " .. close .. " sign: '" .. segment_run .. "'") | error("Unmatched " .. close .. " sign: '" .. segment_run .. "'") | ||
elseif level == 0 then | elseif level == 0 then | ||
insert(text_and_specs, concat(seg_group)) | |||
seg_group = {} | seg_group = {} | ||
end | end | ||
end | end | ||
elseif level > 0 then | elseif level > 0 then | ||
insert(seg_group, seg) | |||
else | else | ||
insert(text_and_specs, seg) | |||
end | end | ||
end | end | ||
| Line 124: | Line 163: | ||
local open_items = {} | local open_items = {} | ||
for _, open_close in ipairs(delimiter_pairs) do | for _, open_close in ipairs(delimiter_pairs) do | ||
local open, close = | local open, close = open_close[1], open_close[2] | ||
open = | open = open:gsub("([%[%]%%%%-])", "%%%1") | ||
close = | close = close:gsub("([%[%]%%%%-])", "%%%1") | ||
insert(open_close_items, open) | |||
insert(open_close_items, close) | |||
insert(open_items, open) | |||
open = "[" .. open .. "]" | open = "[" .. open .. "]" | ||
close = "[" .. close .. "]" | close = "[" .. close .. "]" | ||
open_to_close_map[open] = close | open_to_close_map[open] = close | ||
insert(escaped_delimiter_pairs, {open, close}) | |||
end | end | ||
local open_close_pattern = "([" . | local open_close_pattern = "([" .. concat(open_close_items) .. "])" | ||
local open_pattern = "([" . | local open_pattern = "([" .. concat(open_items) .. "])" | ||
local break_on_open_close = | local break_on_open_close = split(segment_run, open_close_pattern) | ||
local text_and_specs = {} | local text_and_specs = {} | ||
local level = 0 | local level = 0 | ||
| Line 145: | Line 184: | ||
for i, seg in ipairs(break_on_open_close) do | for i, seg in ipairs(break_on_open_close) do | ||
if i % 2 == 0 then | if i % 2 == 0 then | ||
insert(seg_group, seg) | |||
if level == 0 then | if level == 0 then | ||
if not | if not umatch(seg, open_pattern) then | ||
local errmsg = "Unmatched close sign " .. seg .. ": '" .. segment_run .. "'" | local errmsg = "Unmatched close sign " .. seg .. ": '" .. segment_run .. "'" | ||
if no_error_on_unmatched then | if no_error_on_unmatched then | ||
| Line 157: | Line 196: | ||
assert(open_at_level_zero == nil) | assert(open_at_level_zero == nil) | ||
for _, open_close in ipairs(escaped_delimiter_pairs) do | for _, open_close in ipairs(escaped_delimiter_pairs) do | ||
local open | local open = open_close[1] | ||
if | if umatch(seg, open) then | ||
open_at_level_zero = open | open_at_level_zero = open | ||
break | break | ||
| Line 167: | Line 206: | ||
end | end | ||
level = level + 1 | level = level + 1 | ||
elseif | elseif umatch(seg, open_at_level_zero) then | ||
level = level + 1 | level = level + 1 | ||
elseif | elseif umatch(seg, open_to_close_map[open_at_level_zero]) then | ||
level = level - 1 | level = level - 1 | ||
assert(level >= 0) | assert(level >= 0) | ||
if level == 0 then | if level == 0 then | ||
insert(text_and_specs, concat(seg_group)) | |||
seg_group = {} | seg_group = {} | ||
open_at_level_zero = nil | open_at_level_zero = nil | ||
| Line 179: | Line 218: | ||
end | end | ||
elseif level > 0 then | elseif level > 0 then | ||
insert(seg_group, seg) | |||
else | else | ||
insert(text_and_specs, seg) | |||
end | end | ||
end | end | ||
| Line 222: | Line 261: | ||
end | end | ||
return false | return false | ||
end | |||
--[==[ | |||
Check whether a term appears to have already been passed through `full_link()`. Passing it again will mangle it in | |||
various ways; at best it will have unnecessary lang/script wrapping, which might do nothing but might result in | |||
overly large fonts or other issues. We also check for uses of {{tl|ja-r/args}}, {{tl|ryu-r/args}} or {{tl|ko-l/args}}, | |||
which will be manged by `full_link()`. If this check succeeds, use the text raw instead of passing through | |||
`full_link()`. | |||
]==] | |||
function export.term_already_linked(term) | |||
return term:find("<span") or term:find("{{ja%-r|") or term:find("{{ryu%-r|") or term:find("{{ko%-l|") | |||
end | end | ||
| Line 273: | Line 323: | ||
for i, seg in ipairs(segment_runs) do | for i, seg in ipairs(segment_runs) do | ||
if i % 2 == 0 then | if i % 2 == 0 then | ||
insert(run, seg) | |||
else | else | ||
local parts = | local parts = split(seg, preserve_splitchar and "(" .. splitchar .. ")" or splitchar) | ||
insert(run, parts[1]) | |||
for j=2,#parts do | for j=2,#parts do | ||
insert(grouped_runs, run) | |||
run = {parts[j]} | run = {parts[j]} | ||
end | end | ||
| Line 286: | Line 334: | ||
end | end | ||
if #run > 0 then | if #run > 0 then | ||
insert(grouped_runs, run) | |||
end | end | ||
return grouped_runs | return grouped_runs | ||
| Line 329: | Line 377: | ||
i = i + 2 | i = i + 2 | ||
else | else | ||
insert(joined_runs, run) | |||
i = i + 1 | i = i + 1 | ||
end | end | ||
| Line 338: | Line 386: | ||
function export.strip_spaces(text) | function export.strip_spaces(text) | ||
return | return (ugsub(text, "^%s*(.-)%s*$", "%1")) | ||
end | end | ||
| Line 398: | Line 446: | ||
-- First replace comma with a temporary character in comma+whitespace sequences. | -- First replace comma with a temporary character in comma+whitespace sequences. | ||
local need_unescape = false | local need_unescape = false | ||
for i | for i in ipairs(run) do | ||
if i % 2 == 1 then | if i % 2 == 1 and escape_fun then | ||
local this_need_unescape | local this_need_unescape | ||
run[i], this_need_unescape = escape_fun(run[i]) | run[i], this_need_unescape = escape_fun(run[i]) | ||
| Line 422: | Line 470: | ||
if run:find("\\,") then | if run:find("\\,") then | ||
run = run:gsub("\\,", | -- FIXME: we should probably convert literal \\ to \ to allow people to put a backslash before a comma that | ||
-- should be passed through; but maybe it's enough to use an HTML escape for the comma or backslash. | |||
run = (run:gsub("\\,", tempcomma)) -- discard backslash before comma, doing its duty to protect the comma | |||
escaped = true | escaped = true | ||
end | end | ||
if run:find(",%s") then | if run:find(",%s") then | ||
run = run:gsub(",(%s)", tempcomma .. "%1") | run = (run:gsub(",(%s)", tempcomma .. "%1")) | ||
escaped = true | escaped = true | ||
end | end | ||
| Line 439: | Line 489: | ||
tempcomma = tempcomma or u(0xFFF0) | tempcomma = tempcomma or u(0xFFF0) | ||
return (run:gsub(tempcomma, ",")) | |||
end | end | ||
| Line 474: | Line 523: | ||
]==] | ]==] | ||
function export.split_escaping(text, splitchar, preserve_splitchar, escape_fun, unescape_fun) | function export.split_escaping(text, splitchar, preserve_splitchar, escape_fun, unescape_fun) | ||
if not | if not umatch(text, splitchar) then | ||
return {text} | return {text} | ||
end | end | ||
| Line 490: | Line 539: | ||
unescape_fun) | unescape_fun) | ||
for i = 1, #split_runs, (preserve_splitchar and 2 or 1) do | for i = 1, #split_runs, (preserve_splitchar and 2 or 1) do | ||
split_runs[i] = | split_runs[i] = concat(split_runs[i]) | ||
end | end | ||
return split_runs | return split_runs | ||
| Line 498: | Line 547: | ||
-- First escape sequences we don't want to count for splitting. | -- First escape sequences we don't want to count for splitting. | ||
local need_unescape | local need_unescape | ||
text, need_unescape = escape_fun(text) | if escape_fun then | ||
text, need_unescape = escape_fun(text) | |||
end | |||
local parts = | local parts = split(text, preserve_splitchar and "(" .. splitchar .. ")" or splitchar) | ||
if need_unescape then | if need_unescape then | ||
for i = 1, #parts, (preserve_splitchar and 2 or 1) do | for i = 1, #parts, (preserve_splitchar and 2 or 1) do | ||
| Line 572: | Line 621: | ||
return term, nil | return term, nil | ||
end | end | ||
local parts = | local parts = split(inside, "|") | ||
if #parts > 2 then | if #parts > 2 then | ||
parse_err("Saw more than two parts inside a bracketed link") | parse_err("Saw more than two parts inside a bracketed link") | ||
end | end | ||
return | return parts[1], parts[2] | ||
end | end | ||
return term, nil | return term, nil | ||
| Line 583: | Line 632: | ||
--[==[ | --[==[ | ||
Parse a term that may have a language code (or possibly multiple | Parse a term that may have a language code (or possibly multiple plus-separated language codes, if | ||
is given) preceding it (e.g. {la:minūtia} or {grc:[[σκῶρ|σκατός]]} or {nan-hbl | `data.allow_multiple` is given) preceding it (e.g. {la:minūtia} or {grc:[[σκῶρ|σκατός]]} or | ||
arguments: | {nan-hbl+hak:[[毋]][[知]]}). Return four arguments: | ||
# the term minus the language code; | # the term minus the language code; | ||
# the language object corresponding to the language code (possibly a family object if `allow_family` is given), or | # the language object corresponding to the language code (possibly a family object if `data.allow_family` is given), or | ||
list of such objects if `allow_multiple` is given; | a list of such objects if `data.allow_multiple` is given; | ||
# the link if the term is of the form {[[<var>link</var>|<var>display</var>]]} (it may be generated into that form with | # the link if the term is of the form {[[<var>link</var>|<var>display</var>]]} (it may be generated into that form with | ||
Wikipedia and Wikisource prefixes) or of the form {{[[<var>link</var>]]}, otherwise the full term; | Wikipedia and Wikisource prefixes) or of the form {{[[<var>link</var>]]}, otherwise the full term; | ||
# the display part if the term is of the form {[[<var>link</var>|<var>display</var>]]}, otherwise nil. | # the display part if the term is of the form {[[<var>link</var>|<var>display</var>]]}, otherwise nil. | ||
Etymology-only languages are allowed. This function also correctly handles Wikipedia prefixes (e.g. {w:Abatemarco} | Etymology-only languages are always allowed. This function also correctly handles Wikipedia prefixes (e.g. | ||
or {w:it:Colle Val d'Elsa} or {lw:ru:Филарет}) and Wikisource prefixes (e.g. {s:Twelve O'Clock} or | {w:Abatemarco} or {w:it:Colle Val d'Elsa} or {lw:ru:Филарет}) and Wikisource prefixes (e.g. {s:Twelve O'Clock} or | ||
{s:[[Walden/Chapter XVIII|Walden]]} or {s:fr:Perceval ou le conte du Graal} or {s:ro:[[Domnul Vucea|Mr. Vucea]]} or | {s:[[Walden/Chapter XVIII|Walden]]} or {s:fr:Perceval ou le conte du Graal} or {s:ro:[[Domnul Vucea|Mr. Vucea]]} or | ||
{ls:ko:이상적 부인} or {ls:ko:[[조선 독립의 서#一. 槪論|조선 독립의 서]]}) and converts them into two-part links, | {ls:ko:이상적 부인} or {ls:ko:[[조선 독립의 서#一. 槪論|조선 독립의 서]]}) and converts them into two-part links, | ||
| Line 606: | Line 655: | ||
display parts, you must check for this. | display parts, you must check for this. | ||
` | The calling convention is to pass in a single argument `data` containing the following fields: | ||
* `term`: The term to parse. | |||
argument to the function is the number of stack frames to ignore when calling error(); if you declare your error | * `parse_err`: An optional function of one or two arguments to display an error. (The second argument to the function is | ||
function with only one argument, things will still work fine.) | the number of stack frames to ignore when calling error(); if you declare your error function with only one argument, | ||
things will still work fine.) | |||
* `paramname`: If `parse_err` is omitted, this should be a string naming a parameter to display in the error message, | |||
along with the term in question, and will be used to generate a `parse_err` function using `make_parse_err()`. (If | |||
`paramname` is omitted, just the term itself appears in the error message.) | |||
* `allow_multiple`: Allow multiple plus-separated language codes, e.g. {nan-hbl+hak:[[毋]][[知]]}. See above. | |||
* `allow_family`: Allow family objects to appear in place of language codes. | |||
* `allow_bad`: Don't throw an error on invalid language code prefixes; instead, include the prefix and colon as part of | |||
the term. Note that if a prefix doesn't look like a language code (e.g. if it's a number), the code won't even try to | |||
parse it as a language code, regardless of the `allow_bad` setting, but will always include it in the term. | |||
* `lang_cache`: A table mapping language codes to language objects. If the value is `false`, the language code is | |||
invalid. If specified, the cache will be consulted before calling `getByCode()` in [[Module:languages]], and the | |||
result cached. If not specified, no cache will be used. | |||
]==] | ]==] | ||
function export.parse_term_with_lang( | function export.parse_term_with_lang(data) | ||
local term = data.term | |||
local parse_err = data.parse_err or | |||
data.paramname and export.make_parse_err(("%s=%s"):format(data.paramname, term)) or | |||
local term = | |||
local parse_err = | |||
export.make_parse_err(term) | export.make_parse_err(term) | ||
-- Parse off an initial language code (e.g. 'la:minūtia' or 'grc:[[σκῶρ|σκατός]]'). First check for Wikipedia | -- Parse off an initial language code (e.g. 'la:minūtia' or 'grc:[[σκῶρ|σκατός]]'). First check for Wikipedia | ||
| Line 644: | Line 695: | ||
parse_err("Cannot have embedded brackets following a Wikipedia (w:... or lw:...) link; expand the term to a fully bracketed term w:[[LINK|DISPLAY]] or similar") | parse_err("Cannot have embedded brackets following a Wikipedia (w:... or lw:...) link; expand the term to a fully bracketed term w:[[LINK|DISPLAY]] or similar") | ||
end | end | ||
local lang = wiki_links and | local lang = wiki_links and get_lang(foreign_wiki, parse_err, "allow etym") or nil | ||
local prefixed_link = wiki_prefix .. link | local prefixed_link = wiki_prefix .. link | ||
return ("[[%s|%s]]"):format(prefixed_link, display or link), lang, prefixed_link, display | return ("[[%s|%s]]"):format(prefixed_link, display or link), lang, prefixed_link, display | ||
| Line 685: | Line 736: | ||
local function get_by_code(code, allow_bad) | local function get_by_code(code, allow_bad) | ||
local lang | local lang | ||
if | if data.lang_cache then | ||
lang = | lang = data.lang_cache[code] | ||
end | end | ||
if lang == nil then | if lang == nil then | ||
lang = | lang = get_lang(code, not allow_bad and parse_err or nil, "allow etym", | ||
data.allow_family) | |||
if | if data.lang_cache then | ||
data.lang_cache[code] = lang or false | |||
end | end | ||
end | end | ||
| Line 698: | Line 749: | ||
end | end | ||
if | if data.allow_multiple then | ||
local termlang_spec | local termlang_spec | ||
termlang_spec, actual_term = term:match("^([a-zA-Z.,+-]+):([^ ].*)$") | termlang_spec, actual_term = term:match("^([a-zA-Z.,+-]+):([^ ].*)$") | ||
if termlang_spec then | if termlang_spec then | ||
termlang = | termlang = split(termlang_spec, "[,+]") | ||
local all_possible_code = true | local all_possible_code = true | ||
for _, code in ipairs(termlang) do | for _, code in ipairs(termlang) do | ||
| Line 713: | Line 764: | ||
local saw_nil = false | local saw_nil = false | ||
for i, code in ipairs(termlang) do | for i, code in ipairs(termlang) do | ||
termlang[i] = get_by_code(code, | termlang[i] = get_by_code(code, data.allow_bad) | ||
if not termlang[i] then | if not termlang[i] then | ||
saw_nil = true | saw_nil = true | ||
| Line 731: | Line 782: | ||
if termlang then | if termlang then | ||
if is_possible_lang_code(termlang) then | if is_possible_lang_code(termlang) then | ||
termlang = get_by_code(termlang, | termlang = get_by_code(termlang, data.allow_bad) | ||
if termlang then | if termlang then | ||
term = actual_term | term = actual_term | ||
| Line 780: | Line 831: | ||
** `escape_fun` and `unescape_fun` are as in split_escaping() and split_alternating_runs_escaping() above and | ** `escape_fun` and `unescape_fun` are as in split_escaping() and split_alternating_runs_escaping() above and | ||
control the protected sequences that won't be split. By default, `escape_comma_whitespace` and | control the protected sequences that won't be split. By default, `escape_comma_whitespace` and | ||
`unescape_comma_whitespace` are used, so that comma+whitespace sequences won't be split. | `unescape_comma_whitespace` are used, so that comma+whitespace sequences won't be split. Set to `false` to disable | ||
escaping/unescaping. | |||
** `pre_normalize_modifiers`, if specified, is a function of one argument, which can be used to "normalize" modifiers | ** `pre_normalize_modifiers`, if specified, is a function of one argument, which can be used to "normalize" modifiers | ||
prior to further parsing. This is used, for example, in [[Module:tl-pronunciation]] to convert modifiers of the | prior to further parsing. This is used, for example, in [[Module:tl-pronunciation]] to convert modifiers of the | ||
| Line 848: | Line 900: | ||
a value. (This means that multiple occurrences of a given modifier are allowed if `store` is given, but not | a value. (This means that multiple occurrences of a given modifier are allowed if `store` is given, but not | ||
otherwise.) `store` can be one of the following: | otherwise.) `store` can be one of the following: | ||
** {"insert"}: the converted value is appended to the key's value using { | ** {"insert"}: the converted value is appended to the key's value using {insert()}; if the key has no value, it | ||
is first converted to an empty list; | is first converted to an empty list; | ||
** {"insertIfNot"}: is similar but appends the value using {insertIfNot()} in [[Module:table]]; | ** {"insertIfNot"}: is similar but appends the value using {insertIfNot()} in [[Module:table]]; | ||
** {"insert-flattened"}, the converted value is assumed to be a list and the objects are appended one-by-one into the | ** {"insert-flattened"}, the converted value is assumed to be a list and the objects are appended one-by-one into the | ||
key's existing value using { | key's existing value using {insert()}; | ||
** {"insertIfNot-flattened"} is similar but appends using {insertIfNot()} in [[Module:table]]; (WARNING: When using | ** {"insertIfNot-flattened"} is similar but appends using {insertIfNot()} in [[Module:table]]; (WARNING: When using | ||
{"insert-flattened"} and {"insertIfNot-flattened"}, if there is no existing value for the key, the converted value is | {"insert-flattened"} and {"insertIfNot-flattened"}, if there is no existing value for the key, the converted value is | ||
| Line 905: | Line 957: | ||
local function verify_no_overall() | local function verify_no_overall() | ||
for | for _, mod_props in pairs(props.param_mods) do | ||
if mod_props.overall then | if mod_props.overall then | ||
error("Internal caller error: Can't specify `overall` for a modifier in `param_mods` unless `outer_container` property is given") | error("Internal caller error: Can't specify `overall` for a modifier in `param_mods` unless `outer_container` property is given") | ||
| Line 930: | Line 982: | ||
else | else | ||
verify_no_overall() | verify_no_overall() | ||
end | |||
local escape_fun = props.escape_fun | |||
if escape_fun == nil then | |||
escape_fun = export.escape_comma_whitespace | |||
end | |||
local unescape_fun = props.unescape_fun | |||
if unescape_fun == nil then | |||
unescape_fun = export.unescape_comma_whitespace | |||
end | end | ||
local separated_groups = export.split_alternating_runs_escaping(segments, props.splitchar, | local separated_groups = export.split_alternating_runs_escaping(segments, props.splitchar, | ||
props.preserve_splitchar, | props.preserve_splitchar, escape_fun, unescape_fun) | ||
for j = 1, #separated_groups, (props.preserve_splitchar and 2 or 1) do | for j = 1, #separated_groups, (props.preserve_splitchar and 2 or 1) do | ||
if rejoin_square_brackets_after_split then | if rejoin_square_brackets_after_split then | ||
| Line 948: | Line 1,007: | ||
parsed[props.delimiter_key or "delimiter"] = separated_groups[j - 1][1] | parsed[props.delimiter_key or "delimiter"] = separated_groups[j - 1][1] | ||
end | end | ||
insert(terms, parsed) | |||
end | end | ||
if props.outer_container then | if props.outer_container then | ||
| Line 986: | Line 1,045: | ||
local function get_valid_prefixes() | local function get_valid_prefixes() | ||
local valid_prefixes = {} | local valid_prefixes = {} | ||
for param_mod, | for param_mod, mod_props in pairs(props.param_mods) do | ||
if not mod_props.deprecated then | |||
insert(valid_prefixes, param_mod) | |||
end | |||
end | end | ||
sort(valid_prefixes) | |||
return valid_prefixes | return valid_prefixes | ||
end | end | ||
| Line 1,067: | Line 1,128: | ||
-- makes use of the field `required`, but only if `set` is set.) If this becomes problematic, consider | -- makes use of the field `required`, but only if `set` is set.) If this becomes problematic, consider | ||
-- removing the optimization. | -- removing the optimization. | ||
converted = | converted = convert_val(converted, prefix_parse_err, mod_props) | ||
end | end | ||
local store = props.param_mods[prefix].store | local store = props.param_mods[prefix].store | ||
| Line 1,079: | Line 1,140: | ||
dest[key] = {converted} | dest[key] = {converted} | ||
else | else | ||
insert(dest[key], converted) | |||
end | end | ||
elseif store == "insertIfNot" then | elseif store == "insertIfNot" then | ||
| Line 1,085: | Line 1,146: | ||
dest[key] = {converted} | dest[key] = {converted} | ||
else | else | ||
insert_if_not(dest[key], converted) | |||
end | end | ||
elseif store == "insert-flattened" then | elseif store == "insert-flattened" then | ||
| Line 1,092: | Line 1,153: | ||
else | else | ||
for _, obj in ipairs(converted) do | for _, obj in ipairs(converted) do | ||
insert(dest[key], obj) | |||
end | end | ||
end | end | ||
| Line 1,100: | Line 1,161: | ||
else | else | ||
for _, obj in ipairs(converted) do | for _, obj in ipairs(converted) do | ||
insert_if_not(dest[key], obj) | |||
end | end | ||
end | end | ||
elseif type(store) == "string" then | elseif type(store) == "string" then | ||
prefix_parse_err(("Internal caller error: Unrecognized value '%s' for `store` property"):format(store)) | prefix_parse_err(("Internal caller error: Unrecognized value '%s' for `store` property"):format(store)) | ||
elseif | elseif not is_callable(store) then | ||
prefix_parse_err(("Internal caller error: Unrecognized type for `store` property %s"):format( | prefix_parse_err(("Internal caller error: Unrecognized type for `store` property %s"):format(dump(store))) | ||
else | else | ||
store { | store{ | ||
dest = dest, | dest = dest, | ||
key = key, | key = key, | ||