191 lines
6.4 KiB
Lua
191 lines
6.4 KiB
Lua
-- This program scans a text for REGIONS to sort.
|
|
--
|
|
-- Each REGION contains the following:
|
|
-- A "START_SORT:" string command that also tells the sorter which types of HEADLINES the REGION contains.
|
|
-- (optionally) a number of PREFACE lines.
|
|
-- A number of SECTIONS to sort
|
|
-- An "END_SORT" command
|
|
--
|
|
-- Each SECTION contains the following
|
|
-- A HEADLINE
|
|
-- A number of lines of body text.
|
|
--
|
|
-- A HEADLINE can (currently) be one of:
|
|
-- - An asciidoc headline: a line that always begins with a number of '=' characters, followed by at least one normal text character.
|
|
-- These headlines are convenient because it allows us to easily alphabetize headlines in certain chapters
|
|
-- (for instance in chapters where spells, items, or abilities each have their own headline).
|
|
-- - A hidden headline. A text they always begins "//HEADLINE:", followed by a number of normal text characters.
|
|
-- These hidden headlines are useful when we want to alphabetize tables, lists, and other sections that do not contain
|
|
-- explicit headlines.
|
|
--
|
|
--
|
|
-- Each REGION has a number of SECTIONS, and each SECTION
|
|
-- has a HEADLINE. HEADLINES can be actual asciidoc headlines (strings beginning with a number of "=" characters)
|
|
-- or they can be pseudo-headlines that exist as comments inside the asciidoc file.
|
|
--
|
|
--
|
|
-- {{{ Some variables to help us
|
|
local output = "" -- Output buffer
|
|
local STATE_SCANNING = 0 -- State machine: scanning for START_SORT commands
|
|
local MODE_SORTING = 1 -- State machine: sorting, and looking for END_SORT commands.
|
|
local state = STATE_SCANNING -- State machine: current mode
|
|
local headlinePattern = "" -- If a line matches this pattern, is a section headline (a sort key)
|
|
local sections = {} -- The sections we're currently sorting.
|
|
local preface = "" -- contains the text in a chunk that comes before the first section headline
|
|
--}}}
|
|
|
|
--{{{ _start_sort(line)
|
|
---Check if a line starts with START_SORT and then a key to look for.
|
|
---@param line string The line we're checking
|
|
---
|
|
---@return boolean success Was the line correctly formatted?
|
|
---@return boolean start_sort Did this line contain a correct START_SORT command?
|
|
---@return string pattern The regex pattern to scan for to find the headline/key that each section starts with.
|
|
---
|
|
local function _is_start_sort_command(line)
|
|
local match = string.match(line, "^//%s*START_SORT%s*(%S*)%s*$")
|
|
|
|
if not match then
|
|
return true, false, ""
|
|
end
|
|
|
|
--
|
|
-- ASCIIDOC HEADLINE
|
|
--
|
|
-- These types of headlines start with a number of "=" characters.
|
|
-- They sort REGIONS of text where each SECTION begins with an asciidoc headline
|
|
if string.sub(match, 1, 1) == "=" then
|
|
-- We are sorting by section names
|
|
-- The type of section we're sorting (i.e. its depth) is denoted by the given number '=' characters
|
|
-- Every time we encounter a section with the corresponding number of equal signs, we consider that a sorting key.
|
|
return true, true, "^" .. match .. "%s+"
|
|
end
|
|
|
|
--
|
|
-- HIDDEN HEADLINE
|
|
--
|
|
-- These headlines begin with "//HEADLINE:"
|
|
-- They are comments inside the asciidoc source code so they do not show up in the rendered text.
|
|
-- Thus they sort REGIONS of text where each SECTION begins with a hidden headline
|
|
if match == "//HEADLINE:" then
|
|
-- We are sorting by custom keys.
|
|
-- This means that every time we encounter the string "HEADLINE:" we consider the next line a sorting key.
|
|
return true, true, "^%s*//HEADLINE:"
|
|
end
|
|
|
|
return false, false, match
|
|
end
|
|
--}}}
|
|
|
|
-- {{{ _state_machine_scan(line)
|
|
local function _state_machine_scan(line)
|
|
local success, is_start_sort_command, _headline_pattern = _is_start_sort_command(line)
|
|
|
|
if not success then
|
|
local errMsg = string.format("Failed to parse sort key: '%s'", _headline_pattern)
|
|
print(errMsg)
|
|
error(errMsg)
|
|
end
|
|
|
|
if is_start_sort_command then
|
|
state = MODE_SORTING
|
|
headlinePattern = _headline_pattern
|
|
sections = {}
|
|
preface = ""
|
|
end
|
|
|
|
output = output .. line
|
|
end
|
|
--}}}
|
|
|
|
--{{{ _state_machine_sort(line)
|
|
local function _state_machine_sort(line)
|
|
--
|
|
-- NEW SECTION STARTED
|
|
--
|
|
if string.match(line, headlinePattern) then -- We found a new section headline/sortkey
|
|
-- Start a new section
|
|
sections[#sections + 1] = line
|
|
|
|
return
|
|
end
|
|
|
|
--
|
|
-- SORTED REGION ENDED
|
|
--
|
|
if string.match(line, "^//%s*END_SORT") then
|
|
-- We have encountered a command to stop sorting. So we should stop sorting.
|
|
-- Each section within the current sorting area is a single string
|
|
-- Sort those strings to sort the entire area,
|
|
-- and compile/combine/join the strings into the output.
|
|
|
|
table.sort(sections)
|
|
local sorted = table.concat(sections, "")
|
|
|
|
-- Compile the resulting sorted text.
|
|
output = output .. preface .. sorted .. line
|
|
|
|
-- Reset the state machine to start looking for new areas to sort.
|
|
state = STATE_SCANNING
|
|
sections = {}
|
|
preface = ""
|
|
|
|
return
|
|
end
|
|
|
|
-- SECTION PREFACE
|
|
--
|
|
-- This happens if we have preface text before the first heading/sortkey
|
|
-- Preface text is paragraph text that comes after START_SORT, but before the
|
|
-- first sorting key/section is encountered.
|
|
if nil == sections[#sections] then
|
|
preface = preface .. line
|
|
|
|
return
|
|
end
|
|
|
|
-- This line is a normal line within the section.
|
|
-- Add it to the current section.
|
|
sections[#sections] = sections[#sections] .. line
|
|
end
|
|
-- }}}
|
|
|
|
--{{{ _state_machine_process_single_line(line)
|
|
local function _state_machine_process_single_line(line)
|
|
line = line .. "\n"
|
|
|
|
-- SCANNING
|
|
if state == STATE_SCANNING then
|
|
_state_machine_scan(line)
|
|
return
|
|
end
|
|
|
|
--
|
|
-- SORTING
|
|
--
|
|
if state == MODE_SORTING then
|
|
_state_machine_sort(line)
|
|
return
|
|
end
|
|
end
|
|
--}}}
|
|
|
|
--{{{ sorter(lines)
|
|
---Scan a file and sort the headings of the chunks marked for sorting
|
|
---@param lines Iterator
|
|
---@return string output sorted string
|
|
local function sorter(lines)
|
|
for line in lines do
|
|
_state_machine_process_single_line(line)
|
|
end
|
|
|
|
if not (state == STATE_SCANNING and #sections == 0) then
|
|
error("you must be missing an END_SORT or similar")
|
|
end
|
|
|
|
return output
|
|
end
|
|
--}}}
|
|
|
|
return sorter
|