From fc7e7fbaad05b37aef573e10058209ca0eb8d123 Mon Sep 17 00:00:00 2001 From: Omikhleia Date: Tue, 21 Nov 2023 12:20:51 +0100 Subject: [PATCH 1/2] feat(utilities)!: Refactor and extend AST-handling utilities BREAKING CHANGE: For modules that rely on `SILE.utilities` (`SU`), and in particular raw content handling functions `subContent()`, `walkContent()`, `stripContentPos()`, `hasContent()`, and `contentToString()`, these and similar functions have been moved into `SILE.utilities.ast` (`SU.ast`). The `subContent()` implementation also no longer adds id="stuff" attributes to everything. --- classes/base.lua | 20 +-- classes/book.lua | 2 +- core/font.lua | 4 +- core/languages.lua | 2 +- core/utilities-ast.lua | 213 ++++++++++++++++++++++++++++++ core/utilities.lua | 93 ++++--------- documentation/c10-classdesign.sil | 2 +- documentation/c12-xmlproc.sil | 6 +- inputters/base.lua | 8 +- inputters/xml.lua | 21 +-- packages/autodoc/init.lua | 2 +- packages/bibtex/init.lua | 4 +- packages/inputfilter/init.lua | 10 +- packages/tableofcontents/init.lua | 4 +- typesetters/base.lua | 2 +- 15 files changed, 276 insertions(+), 117 deletions(-) create mode 100644 core/utilities-ast.lua diff --git a/classes/base.lua b/classes/base.lua index 7c7768bd3..90c42066a 100644 --- a/classes/base.lua +++ b/classes/base.lua @@ -334,7 +334,7 @@ function class:registerCommands () self:registerCommand("script", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then + if SU.ast.hasContent(content) then return SILE.processString(content[1], options.format or "lua", nil, packopts) elseif options.src then return SILE.require(options.src) @@ -346,8 +346,8 @@ function class:registerCommands () self:registerCommand("include", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then - local doc = SU.contentToString(content) + if SU.ast.hasContent(content) then + local doc = SU.ast.contentToString(content) return SILE.processString(doc, options.format, nil, packopts) elseif options.src then return SILE.processFile(options.src, options.format, packopts) @@ -358,8 +358,8 @@ function class:registerCommands () self:registerCommand("lua", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then - local doc = SU.contentToString(content) + if SU.ast.hasContent(content) then + local doc = SU.ast.contentToString(content) return SILE.processString(doc, "lua", nil, packopts) elseif options.src then return SILE.processFile(options.src, "lua", packopts) @@ -373,8 +373,8 @@ function class:registerCommands () self:registerCommand("sil", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then - local doc = SU.contentToString(content) + if SU.ast.hasContent(content) then + local doc = SU.ast.contentToString(content) return SILE.processString(doc, "sil") elseif options.src then return SILE.processFile(options.src, "sil", packopts) @@ -385,8 +385,8 @@ function class:registerCommands () self:registerCommand("xml", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then - local doc = SU.contentToString(content) + if SU.ast.hasContent(content) then + local doc = SU.ast.contentToString(content) return SILE.processString(doc, "xml", nil, packopts) elseif options.src then return SILE.processFile(options.src, "xml", packopts) @@ -398,7 +398,7 @@ function class:registerCommands () self:registerCommand("use", function (options, content) local packopts = packOptions(options) if content[1] and string.len(content[1]) > 0 then - local doc = SU.contentToString(content) + local doc = SU.ast.contentToString(content) SILE.processString(doc, "lua", nil, packopts) else if options.src then diff --git a/classes/book.lua b/classes/book.lua index 20a0290d2..44fe28710 100644 --- a/classes/book.lua +++ b/classes/book.lua @@ -105,7 +105,7 @@ function class:registerCommands () number = self.packages.counters:formatMultilevelCounter(self:getMultilevelCounter("sectioning")) end if SU.boolean(options.toc, true) then - SILE.call("tocentry", { level = level, number = number }, SU.subContent(content)) + SILE.call("tocentry", { level = level, number = number }, SU.ast.subContent(content)) end if SU.boolean(options.numbering, true) then if options.msg then diff --git a/core/font.lua b/core/font.lua index eb822817a..10a059e41 100644 --- a/core/font.lua +++ b/core/font.lua @@ -3,7 +3,7 @@ local icu = require("justenoughicu") local lastshaper SILE.registerCommand("font", function (options, content) - if SU.hasContent(content) then SILE.settings:pushState() end + if SU.ast.hasContent(content) then SILE.settings:pushState() end if options.filename then SILE.settings:set("font.filename", options.filename) end if options.family then SILE.settings:set("font.family", options.family) @@ -49,7 +49,7 @@ SILE.registerCommand("font", function (options, content) -- that the post-load hook might want to do. SILE.font.cache(SILE.font.loadDefaults({}), SILE.shaper.getFace) - if SU.hasContent(content) then + if SU.ast.hasContent(content) then SILE.process(content) SILE.settings:popState() if SILE.shaper._name == "harfbuzzWithColor" and lastshaper then diff --git a/core/languages.lua b/core/languages.lua index fd084fdd1..180ff7104 100644 --- a/core/languages.lua +++ b/core/languages.lua @@ -81,7 +81,7 @@ SILE.registerCommand("ftl", function (options, content) fluent:set_locale(locale) if options.src then fluent:load_file(options.src, locale) - elseif SU.hasContent(content) then + elseif SU.ast.hasContent(content) then local input = content[1] fluent:add_messages(input, locale) end diff --git a/core/utilities-ast.lua b/core/utilities-ast.lua new file mode 100644 index 000000000..b1c4e6d25 --- /dev/null +++ b/core/utilities-ast.lua @@ -0,0 +1,213 @@ +--- SILE AST utilities +-- +local ast = {} + +--- Find a command node in a SILE AST tree, +--- looking only at the first level. +--- (We're not reimplementing XPath here.) +---@param tree table AST tree +---@param command string command name +---@return table|nil AST command node +function ast.findInTree (tree, command) + for i=1, #tree do + if type(tree[i]) == "table" and tree[i].command == command then + return tree[i] + end + end +end + +--- Find and extract (remove) a command node in a SILE AST tree, +--- looking only at the first level. +---@param tree table AST tree +---@param command string command name +---@return table|nil AST command node +function ast.removeFromTree (tree, command) + for i=1, #tree do + if type(tree[i]) == "table" and tree[i].command == command then + return table.remove(tree, i) + end + end +end + +--- Create a command from a simple content tree. +--- It encapsulates the content in a command node. +---@param command string command name +---@param options table command options +---@param content table child AST tree +---@param position table position in source (or parent AST command node) +---@return table AST command node +function ast.createCommand (command, options, content, position) + local result = { content } + result.options = options or {} + result.command = command + result.id = "command" + if position then + result.col = position.col or 0 + result.lno = position.lno or 0 + result.pos = position.pos or 0 + else + result.col = 0 + result.lno = 0 + result.pos = 0 + end + return result +end + +--- Create a command from a structured content tree. +--- The content is normally a table of an already prepared content list. +---@param command string command name +---@param options table command options +---@param content table child AST tree +---@param position table position in source (or parent AST command node) +---@return table AST command node +function ast.createStructuredCommand (command, options, content, position) + local result = type(content) == "table" and content or { content } + result.options = options or {} + result.command = command + result.id = "command" + if position then + result.col = position.col or 0 + result.lno = position.lno or 0 + result.pos = position.pos or 0 + else + result.col = 0 + result.lno = 0 + result.pos = 0 + end + return result +end + +--- Extract the sub-content tree from a (command) node, +--- that is the child nodes of the (command) node. +---@param content table AST tree +---@return table AST tree +function ast.subContent (content) + local out = {} + for _, val in ipairs(content) do + out[#out+1] = val + end + return out +end + +-- String trimming +local function trimLeft (str) + return str:gsub("^%s*", "") +end +local function trimRight (str) + return str:gsub("%s*$", "") +end + +--- Content tree trimming: remove leading and trailing spaces, but from +--- a content tree i.e. possibly containing several elements. +---@param content table AST tree +---@return table AST tree +function ast.trimSubContent (content) + if #content == 0 then + return + end + if type(content[1]) == "string" then + content[1] = trimLeft(content[1]) + if content[1] == "" then + table.remove(content, 1) + end + end + if type(content[#content]) == "string" then + content[#content] = trimRight(content[#content]) + if content[#content] == "" then + table.remove(content, #content) + end + end + return content +end + +--- Process the AST walking through content nodes as a "structure": +--- Text nodes are ignored (e.g. usually just spaces due to indentation) +--- Command options are enriched with their "true" node position, so we can later +--- refer to it (as with an XPath pos()). +---@param content table AST tree +function ast.processAsStructure (content) + local iElem = 0 + local nElem = 0 + for i = 1, #content do + if type(content[i]) == "table" then + nElem = nElem + 1 + end + end + for i = 1, #content do + if type(content[i]) == "table" then + iElem = iElem + 1 + content[i].options._pos_ = iElem + content[i].options._last_ = iElem == nElem + SILE.process({ content[i] }) + end + -- All text nodes in ignored in structure tags. + end +end + +--- Call `action` on each content AST node, recursively, including `content` itself. +--- Not called on leaves, i.e. strings. +---@param content table AST tree +---@param action function function to call on each node +function ast.walkContent (content, action) + if type(content) ~= "table" then + return + end + action(content) + for i = 1, #content do + ast.walkContent(content[i], action) + end +end + +--- Strip position, line and column recursively from a content tree. +--- This can be used to remove position details where we do not want them, +--- e.g. in table of contents entries (referring to the original content, +--- regardless where it was exactly, for the purpose of checking whether +--- the table of contents changed.) +---@param content table AST tree +---@return table AST tree +function ast.stripContentPos (content) + if type(content) ~= "table" then + return content + end + local stripped = {} + for k, v in pairs(content) do + if type(v) == "table" then + v = ast.stripContentPos(v) + end + stripped[k] = v + end + if content.id or content.command then + stripped.pos, stripped.col, stripped.lno = nil, nil, nil + end + return stripped +end + +--- Flatten content trees into just the string components (allows passing +--- objects with complex structures to functions that need plain strings) +--- @param content table AST tree +--- @return string string representation of content +function ast.contentToString (content) + local string = "" + for i = 1, #content do + if type(content[i]) == "table" and type(content[i][1]) == "string" then + string = string .. content[i][1] + elseif type(content[i]) == "string" then + -- Work around PEG parser returning env tags as content + -- TODO: refactor capture groups in PEG parser + if content.command == content[i] and content[i] == content[i+1] then + break + end + string = string .. content[i] + end + end + return string +end + +--- Check whether a content AST tree is empty. +---@param content table AST tree +---@return boolean true if content is not empty +function ast.hasContent (content) + return type(content) == "function" or type(content) == "table" and #content > 0 +end + +return ast diff --git a/core/utilities.lua b/core/utilities.lua index 05bef5ad3..031f961c8 100644 --- a/core/utilities.lua +++ b/core/utilities.lua @@ -343,76 +343,6 @@ utilities.cast = function (wantedType, value) end end -utilities.hasContent = function(content) - return type(content) == "function" or type(content) == "table" and #content > 0 -end - --- Flatten content trees into just the string components (allows passing --- objects with complex structures to functions that need plain strings) -utilities.contentToString = function (content) - local string = "" - for i = 1, #content do - if type(content[i]) == "table" and type(content[i][1]) == "string" then - string = string .. content[i][1] - elseif type(content[i]) == "string" then - -- Work around PEG parser returning env tags as content - -- TODO: refactor capture groups in PEG parser - if content.command == content[i] and content[i] == content[i+1] then - break - end - string = string .. content[i] - end - end - return string -end - --- Strip the top level command off a content object and keep only the child --- items — assuming that the current command is taking care of itself -utilities.subContent = function (content) - local out = { id="stuff" } - for key, val in utilities.sortedpairs(content) do - if type(key) == "number" then - out[#out+1] = val - end - end - return out -end - --- Call `action` on each content AST node, recursively, including `content` itself. --- Not called on leaves, i.e. strings. -utilities.walkContent = function (content, action) - if type(content) ~= "table" then - return - end - action(content) - for i = 1, #content do - utilities.walkContent(content[i], action) - end -end - ---- Strip position, line and column recursively from a content tree. --- This can be used to remove position details where we do not want them, --- e.g. in table of contents entries (referring to the original content, --- regardless where it was exactly, for the purpose of checking whether --- the table of contents changed.) --- -utilities.stripContentPos = function (content) - if type(content) ~= "table" then - return content - end - local stripped = {} - for k, v in pairs(content) do - if type(v) == "table" then - v = SU.stripContentPos(v) - end - stripped[k] = v - end - if content.id or content.command then - stripped.pos, stripped.col, stripped.lno = nil, nil, nil - end - return stripped -end - utilities.rateBadness = function(inf_bad, shortfall, spring) if spring == 0 then return inf_bad end local bad = math.floor(100 * math.abs(shortfall / spring) ^ 3) @@ -630,4 +560,27 @@ utilities.formatNumber = require("core.utilities-numbers") utilities.collatedSort = require("core.utilities-sorting") +utilities.ast = require("core.utilities-ast") +utilities.subContent = function (content) + SU.deprecated("SU.subContent", "SU.ast.subContent", "0.15.0", "0.17.0", [[ + Note that the new implementation no longer introduces an id="stuff" key.]]) + return utilities.ast.subContent(content) +end +utilities.hasContent = function(content) + SU.deprecated("SU.hasContent", "SU.ast.hasContent", "0.15.0", "0.17.0") + return SU.ast.hasContent(content) +end +utilities.contentToString = function (content) + SU.deprecated("SU.contentToString", "SU.ast.contentToString", "0.15.0", "0.17.0") + return SU.ast.contentToString(content) +end +utilities.walkContent = function (content, action) + SU.deprecated("SU.walkContent", "SU.ast.walkContent", "0.15.0", "0.17.0") + SU.ast.walkContent(content, action) +end +utilities.stripContentPos = function (content) + SU.deprecated("SU.stripContentPos", "SU.ast.stripContentPos", "0.15.0", "0.17.0") + return SU.ast.stripContentPos(content) +end + return utilities diff --git a/documentation/c10-classdesign.sil b/documentation/c10-classdesign.sil index 2a45581c0..e76514128 100644 --- a/documentation/c10-classdesign.sil +++ b/documentation/c10-classdesign.sil @@ -458,7 +458,7 @@ self:registerCommand("tocentry", function (options, content) SILE.call("info", { category = "toc", value = { - label = SU.stripContentPos(content), level = (options.level or 1) + label = SU.ast.stripContentPos(content), level = (options.level or 1) } }) end) diff --git a/documentation/c12-xmlproc.sil b/documentation/c12-xmlproc.sil index 4fc557dc3..409d58bea 100644 --- a/documentation/c12-xmlproc.sil +++ b/documentation/c12-xmlproc.sil @@ -69,10 +69,10 @@ self:registerCommand("example", function(options,content) \code{\\docbook-titling} is a command similarly defined in \code{docbook.sil} which sets the default font for titling and headers. Once again, if someone wants to customize the look of the output we make it easier for them by giving them simple, compartmentalized commands to override. So far so good, but how do we extract the \code{} tag from the \code{content} abstract syntax tree? -SILE does not provide XPath or CSS-style selectors to locate content form within the DOM tree;\footnote{Patches, as they say, are welcome.} instead there is a simple one-level function called \code{SILE.inputter:findInTree()} which looks for a particular tag or command name within the immediate children of the current tree: +SILE does not provide XPath or CSS-style selectors to locate content form within the DOM tree;\footnote{Patches, as they say, are welcome.} instead there is a simple one-level function called \code{SU.ast.findInTree()} which looks for a particular tag or command name within the immediate children of the current tree: \begin[type=autodoc:codeblock]{raw} - local t = SILE.inputter:findInTree(content, "title") + local t = SU.ast.findInTree(content, "title") if t then SILE.typesetter:typeset(": ") SILE.process(t) @@ -149,7 +149,7 @@ We also increment the count at the current level, while at the same time wiping Now we find the title, and prefix it by the concatenation of all the \code{seccount}s: \begin[type=autodoc:codeblock]{raw} - local title = SILE.inputter:findInTree(content, "title") + local title = SU.ast.findInTree(content, "title") local number = table.concat(SILE.scratch.docbook.seccount, '.') if title then SILE.call("docbook-section-"..SILE.scratch.docbook.seclevel.."-title",{},function() diff --git a/inputters/base.lua b/inputters/base.lua index d19c9f206..a72d86c38 100644 --- a/inputters/base.lua +++ b/inputters/base.lua @@ -49,13 +49,9 @@ function inputter:process (doc) return SILE.process(tree) end --- Just a simple one-level find. We're not reimplementing XPath here. function inputter.findInTree (_, tree, command) - for i=1, #tree do - if type(tree[i]) == "table" and tree[i].command == command then - return tree[i] - end - end + SU.deprecated("SILE.inputter:findInTree", "SU.ast.findInTree", "0.15.0", "0.17.0") + return SU.ast.findInTree(tree, command) end local function process_ambles (ambles) diff --git a/inputters/xml.lua b/inputters/xml.lua index 3c019b934..b448bf7da 100644 --- a/inputters/xml.lua +++ b/inputters/xml.lua @@ -8,8 +8,10 @@ inputter.order = 2 local function startcommand (parser, command, options) local stack = parser:getcallbacks().stack - local lno, col, _ = parser:pos() - local element = { command = command, options = options, lno = lno, col = col } + local lno, col, pos = parser:pos() + local position = { lno = lno, col = col, pos = pos } + -- create an empty command which content will be filled on closing tag + local element = SU.ast.createCommand(command, options, nil, position) table.insert(stack, element) end @@ -33,12 +35,13 @@ local function text (parser, msg) end local function parse (doc) - local content = { StartElement = startcommand, - EndElement = endcommand, - CharacterData = text, - _nonstrict = true, - stack = {{}} - } + local content = { + StartElement = startcommand, + EndElement = endcommand, + CharacterData = text, + _nonstrict = true, + stack = {{}} + } local parser = lxp.new(content) local status, err if type(doc) == "string" then @@ -80,7 +83,7 @@ function inputter.parse (_, doc) -- it doesn't look like a native SILE one already. local rootelement = tree.command if rootelement ~= "sile" and rootelement ~= "document" then - tree = { tree, command = "document" } + tree = SU.ast.createCommand("document", {}, tree) end return { tree } end diff --git a/packages/autodoc/init.lua b/packages/autodoc/init.lua index 167bba5f2..d93115953 100644 --- a/packages/autodoc/init.lua +++ b/packages/autodoc/init.lua @@ -126,7 +126,7 @@ end function package:registerRawHandlers () self:registerRawHandler("autodoc:codeblock", function(options, content) - SILE.call("autodoc:codeblock", options, { content[1] }) -- Still issues with SU.contentToString() witb raw content + SILE.call("autodoc:codeblock", options, { content[1] }) -- Still issues with SU.ast.contentToString() witb raw content end) end diff --git a/packages/bibtex/init.lua b/packages/bibtex/init.lua index d4b77a6b5..33829d330 100644 --- a/packages/bibtex/init.lua +++ b/packages/bibtex/init.lua @@ -84,7 +84,7 @@ function package:registerCommands () end) self:registerCommand("cite", function (options, content) - if not options.key then options.key = SU.contentToString(content) end + if not options.key then options.key = SU.ast.contentToString(content) end local style = SILE.settings:get("bibtex.style") local bibstyle = require("packages.bibtex.styles." .. style) local cite = Bibliography.produceCitation(options, SILE.scratch.bibtex.bib, bibstyle) @@ -96,7 +96,7 @@ function package:registerCommands () end) self:registerCommand("reference", function (options, content) - if not options.key then options.key = SU.contentToString(content) end + if not options.key then options.key = SU.ast.contentToString(content) end local style = SILE.settings:get("bibtex.style") local bibstyle = require("packages.bibtex.styles." .. style) local cite, err = Bibliography.produceReference(options, SILE.scratch.bibtex.bib, bibstyle) diff --git a/packages/inputfilter/init.lua b/packages/inputfilter/init.lua index 13fff8e07..448e5739d 100644 --- a/packages/inputfilter/init.lua +++ b/packages/inputfilter/init.lua @@ -25,14 +25,8 @@ function package:transformContent (content, transformFunction, extraArgs) end function package.createCommand (_, pos, col, lno, command, options, content) - local result = { content } - result.col = col - result.lno = lno - result.pos = pos - result.options = options - result.command = command - result.id = "command" - return result + local position = { lno = lno, col = col, pos = pos } + return SU.ast.createCommand(command, options, content, position) end function package:_init () diff --git a/packages/tableofcontents/init.lua b/packages/tableofcontents/init.lua index b4036660d..5d2f08129 100644 --- a/packages/tableofcontents/init.lua +++ b/packages/tableofcontents/init.lua @@ -57,7 +57,7 @@ local function _linkWrapper (dest, func) end -- Flatten a node list into just its string representation. --- (Similar to SU.contentToString(), but allows passing typeset +-- (Similar to SU.ast.contentToString(), but allows passing typeset -- objects to functions that need plain strings). local function _nodesToText (nodes) -- A real interword space width depends on several settings (depending on variable @@ -174,7 +174,7 @@ function package:registerCommands () SILE.call("info", { category = "toc", value = { - label = SU.stripContentPos(content), + label = SU.ast.stripContentPos(content), level = (options.level or 1), number = options.number, link = dest diff --git a/typesetters/base.lua b/typesetters/base.lua index 679334e07..58dacadb4 100644 --- a/typesetters/base.lua +++ b/typesetters/base.lua @@ -501,7 +501,7 @@ function typesetter:boxUpNodes () self:pushGlue(parfillskip) self:pushPenalty(-inf_bad) SU.debug("typesetter", function () - return "Boxed up "..(#nodelist > 500 and (#nodelist).." nodes" or SU.contentToString(nodelist)) + return "Boxed up "..(#nodelist > 500 and (#nodelist).." nodes" or SU.ast.contentToString(nodelist)) end) local breakWidth = SILE.settings:get("typesetter.breakwidth") or self.frame:getLineWidth() local lines = self:breakIntoLines(nodelist, breakWidth) From 06d9ea4178d0122b76f625dac912f3799c7c5fe5 Mon Sep 17 00:00:00 2001 From: Caleb Maclennan <caleb@alerque.com> Date: Wed, 6 Dec 2023 15:16:19 +0300 Subject: [PATCH 2/2] refactor(utilities): Move utility libraries to directory for easier partial overrides --- core/{utilities-ast.lua => utilities/ast.lua} | 0 core/{utilities.lua => utilities/init.lua} | 11 ++++++++--- core/{utilities-numbers.lua => utilities/numbers.lua} | 0 core/{utilities-sorting.lua => utilities/sorting.lua} | 0 4 files changed, 8 insertions(+), 3 deletions(-) rename core/{utilities-ast.lua => utilities/ast.lua} (100%) rename core/{utilities.lua => utilities/init.lua} (99%) rename core/{utilities-numbers.lua => utilities/numbers.lua} (100%) rename core/{utilities-sorting.lua => utilities/sorting.lua} (100%) diff --git a/core/utilities-ast.lua b/core/utilities/ast.lua similarity index 100% rename from core/utilities-ast.lua rename to core/utilities/ast.lua diff --git a/core/utilities.lua b/core/utilities/init.lua similarity index 99% rename from core/utilities.lua rename to core/utilities/init.lua index 031f961c8..49e72adaf 100644 --- a/core/utilities.lua +++ b/core/utilities/init.lua @@ -556,28 +556,33 @@ utilities.breadcrumbs = function () return breadcrumbs end -utilities.formatNumber = require("core.utilities-numbers") +utilities.formatNumber = require("core.utilities.numbers") -utilities.collatedSort = require("core.utilities-sorting") +utilities.collatedSort = require("core.utilities.sorting") + +utilities.ast = require("core.utilities.ast") -utilities.ast = require("core.utilities-ast") utilities.subContent = function (content) SU.deprecated("SU.subContent", "SU.ast.subContent", "0.15.0", "0.17.0", [[ Note that the new implementation no longer introduces an id="stuff" key.]]) return utilities.ast.subContent(content) end + utilities.hasContent = function(content) SU.deprecated("SU.hasContent", "SU.ast.hasContent", "0.15.0", "0.17.0") return SU.ast.hasContent(content) end + utilities.contentToString = function (content) SU.deprecated("SU.contentToString", "SU.ast.contentToString", "0.15.0", "0.17.0") return SU.ast.contentToString(content) end + utilities.walkContent = function (content, action) SU.deprecated("SU.walkContent", "SU.ast.walkContent", "0.15.0", "0.17.0") SU.ast.walkContent(content, action) end + utilities.stripContentPos = function (content) SU.deprecated("SU.stripContentPos", "SU.ast.stripContentPos", "0.15.0", "0.17.0") return SU.ast.stripContentPos(content) diff --git a/core/utilities-numbers.lua b/core/utilities/numbers.lua similarity index 100% rename from core/utilities-numbers.lua rename to core/utilities/numbers.lua diff --git a/core/utilities-sorting.lua b/core/utilities/sorting.lua similarity index 100% rename from core/utilities-sorting.lua rename to core/utilities/sorting.lua