chroma_solstice/libs/narrator/narrator/narrator.lua
Your Name 03532d29e4 Vendor libs/hump, sfxrlua, json4lua, narrator as real files
These were committed as empty submodule gitlinks with no .gitmodules,
so the folders were never populated and the game could not load
(require of libs/hump/timer, libs/sfxrlua/sfxr, narrator.narrator).
Checked out each at its pinned commit and vendored the files, matching
the existing bundled-libs convention. Removed the junk libs/libs/ tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-11 11:37:09 -04:00

150 lines
No EOL
4 KiB
Lua

local lume = require('narrator.libs.lume')
local enums = require('narrator.enums')
local parser = require('narrator.parser')
local Story = require('narrator.story')
--
-- Local
local folder_separator = package.config:sub(1, 1)
---Clear path from '.lua' and '.ink' extensions and replace '.' to '/' or '\'
---@param path string
---@return string normalized_path
local function normalize_path(path)
local path = path:gsub('.lua$', '')
local path = path:gsub('.ink$', '')
if path:match('%.') and not path:match(folder_separator) then
path = path:gsub('%.', folder_separator)
end
return path
end
---Parse an .ink file to the content string.
---@param path string
---@return string content
local function read_ink_file(path)
local path = normalize_path(path) .. '.ink'
local file = io.open(path, 'r')
assert(file, 'File doesn\'t exist: ' .. path)
local content = file:read('*all')
file:close()
return content
end
---Save a book to the lua module
---@param book Narrator.Book
---@param path string
---@return boolean success
local function save_book(book, path)
local path = normalize_path(path) .. '.lua'
local data = lume.serialize(book)
data = data:gsub('%[%d+%]=', '')
data = data:gsub('[\'[%w_]+\']', function(match) return
match:sub(3, #match - 2)
end)
local file = io.open(path, 'w')
if file == nil then
return false
end
file:write('return ' .. data)
file:close()
return true
end
---Merge a chapter to the book
---@param book Narrator.Book
---@param chapter Narrator.Book
---@return Narrator.Book
local function merge_chapter_to_book(book, chapter)
-- Check a engine version compatibility
if chapter.version.engine and chapter.version.engine ~= enums.engine_version then
assert('Version ' .. chapter.version.engine .. ' of book isn\'t equal to the version ' .. enums.engine_version .. ' of Narrator.')
end
--Merge the root knot and it's stitch
book.tree._._ = lume.concat(chapter.tree._._, book.tree._._)
chapter.tree._._ = nil
book.tree._ = lume.merge(chapter.tree._, book.tree._)
chapter.tree._ = nil
--Merge a chapter to the book
book.tree = lume.merge(book.tree or { }, chapter.tree or { })
book.constants = lume.merge(book.constants or { }, chapter.constants or { })
book.lists = lume.merge(book.lists or { }, chapter.lists or { })
book.variables = lume.merge(book.variables or { }, chapter.variables or { })
book.params = lume.merge(book.params or { }, chapter.params or { })
return book
end
--
-- Public
local narrator = { }
---Parse a book from an Ink file
---Use it during development, but prefer already parsed and stored books in production
---Requires `lpeg` and `io`.
---@param path string
---@param params Narrator.ParsingParams|nil
---@return Narrator.Book
function narrator.parse_file(path, params)
local params = params or { save = false }
assert(parser, 'Can\'t parse anything without lpeg, sorry.')
local content = read_ink_file(path)
local book = parser.parse(content)
for _, inclusion in ipairs(book.inclusions) do
local folder_path = normalize_path(path):match('(.*' .. folder_separator .. ')')
local inclusion_path = folder_path .. normalize_path(inclusion) .. '.ink'
local chapter = narrator.parse_file(inclusion_path)
merge_chapter_to_book(book, chapter)
end
if params.save then
save_book(book, path)
end
return book
end
---Parse a book from the ink content string
---Use it during development, but prefer already parsed and stored books in production
---Requires `lpeg`
---@param content string
---@param inclusions string[]
---@return Narrator.Book
function narrator.parse_content(content, inclusions)
local inclusions = inclusions or { }
assert(parser, 'Can\'t parse anything without a parser.')
local book = parser.parse(content)
for _, inclusion in ipairs(inclusions) do
local chapter = parser.parse(inclusion)
merge_chapter_to_book(book, chapter)
end
return book
end
---Init a story based on the book
---@param book Narrator.Book
---@return Narrator.Story
function narrator.init_story(book)
return Story(book)
end
return narrator