diff --git a/inner-parens-to-brackets/LICENSE.txt b/inner-parens-to-brackets/LICENSE.txt new file mode 100644 index 0000000..551b50b --- /dev/null +++ b/inner-parens-to-brackets/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright © 2024 Bastien Dumont + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/inner-parens-to-brackets/README.md b/inner-parens-to-brackets/README.md new file mode 100644 index 0000000..0a4ab7a --- /dev/null +++ b/inner-parens-to-brackets/README.md @@ -0,0 +1,4 @@ +This filter converts nested parentheses to brackets (and conversely). + +I use it mostly to post-process citeproc output. + diff --git a/inner-parens-to-brackets/inner-parens-to-brackets.lua b/inner-parens-to-brackets/inner-parens-to-brackets.lua new file mode 100644 index 0000000..124666e --- /dev/null +++ b/inner-parens-to-brackets/inner-parens-to-brackets.lua @@ -0,0 +1,91 @@ +-- inner-parens-to-brackets.lua +-- A Pandoc Lua filter that converts nested parentheses to brackets (and conversely). +-- Copyright 2024 Bastien Dumont (bastien.dumont [at] posteo.net) +-- This file is under the MIT License: see LICENSE for more details. + + +local previously_signalled = {} + +local function signal_if_new(id, msg) + if not previously_signalled[id] then + io.stdout:write('INFO: ' .. msg .. '\n') + previously_signalled[id] = true + end +end + +function Para(para) + local whole_para = pandoc.utils.stringify(para) + local parens_trace = {} + local i_str = 0 + local isolate_parens = { + -- In case there are several level of parentheses in the same Str object + -- (could theoretically happen due to non-breaking spaces). + -- Also simplifies check_and_replace() below. + Str = function(str) + local text = str.text + if string.match(text, '[][()]') then + local substrings = {} + for a, b, c in string.gmatch(text, '([^][()]*)([][()]?)([^][()]*)') do + for _, sub in ipairs({ a, b, c }) do + if sub ~= '' then table.insert(substrings, pandoc.Str(sub)) end + end + end + return substrings + end + end + } + local check_and_replace = { + traverse = 'topdown', + Str = function(str) + i_str = i_str + 1 + local text = str.text + if text == '(' then + if #parens_trace > 0 then + if parens_trace[#parens_trace] == '(' then + str.text = '[' + signal_if_new(whole_para .. i_str, + 'Replacing a left parenthesis with a bracket ' .. + 'in the following item: ' .. whole_para) + end + end + table.insert(parens_trace, str.text) + elseif text == ')' then + if #parens_trace > 0 then + if parens_trace[#parens_trace] == '[' then + str.text = ']' + signal_if_new(whole_para .. i_str, + 'Replacing a right parenthesis with a bracket ' .. + 'in the following item: ' .. whole_para) + end + end + table.remove(parens_trace) + elseif text == '[' then + if #parens_trace > 0 then + if parens_trace[#parens_trace] == '[' then + str.text = '(' + signal_if_new(whole_para .. i_str, + 'Replacing a left bracket with a parenthesis ' .. + 'in the following item: ' .. whole_para) + end + end + table.insert(parens_trace, str.text) + elseif text == ']' then + if #parens_trace > 0 then + if parens_trace[#parens_trace] == '(' then + str.text = ')' + signal_if_new(whole_para .. i_str, + 'Replacing a right bracket with a parenthesis ' .. + 'in the following item: ' .. whole_para) + end + end + table.remove(parens_trace) + end + return str + end + } + local processed_para = para:walk(isolate_parens):walk(check_and_replace) + if #parens_trace > 0 then + io.stdout:write('WARNING: Unbalanced parentheses or brackets found in ' .. whole_para .. '\n') + end + return processed_para +end