-- 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