191 lines
5.4 KiB
Lua
191 lines
5.4 KiB
Lua
-- The original author's credits for a Forge port of an existing game: the fields of the game's own
|
|
-- games.dat entry (developer, publisher, year, platform, genre, creator, source, description), the
|
|
-- "KEY: value" lines of its main script's header comment as notes, and the licence files beside
|
|
-- the script, which a release carries. Nothing is invented: a field the original does not state
|
|
-- is left out.
|
|
--
|
|
-- lua5.4 util/forgeCredits.lua <description.forge> <game directory> <main script>
|
|
--
|
|
-- writes or replaces the description's credits block, just after its title, and touches nothing
|
|
-- else in it. util/forgePortKaris.lua loads this file for creditsFor, so a converted description
|
|
-- carries its credits from the start.
|
|
|
|
local CREDIT_KEYS = {
|
|
{ field = "developer", key = "DEVELOPER" },
|
|
{ field = "publisher", key = "PUBLISHER" },
|
|
{ field = "year", key = "YEAR" },
|
|
{ field = "platform", key = "PLATFORM" },
|
|
{ field = "genre", key = "GENRE" },
|
|
{ field = "creator", key = "CREATOR" },
|
|
{ field = "source", key = "SOURCE" },
|
|
{ field = "description", key = "DESCRIPTION" }
|
|
}
|
|
local LICENCE_PATTERNS = { "^copying", "^licen[cs]e", "^gpl" }
|
|
local GAMES_DAT_DEPTH = 2 -- How many folders above the script games.dat may be.
|
|
|
|
|
|
local function readFile(path)
|
|
local handle = io.open(path, "rb")
|
|
local text = nil
|
|
|
|
if handle then
|
|
text = handle:read("a")
|
|
handle:close()
|
|
end
|
|
|
|
return text
|
|
end
|
|
|
|
|
|
-- The games.dat entry for the script: beside it or a folder or two above, the entry whose SCRIPT
|
|
-- names this script, or the only entry there is.
|
|
local function gamesEntry(dir, main)
|
|
local folder = dir
|
|
|
|
for _ = 0, GAMES_DAT_DEPTH do
|
|
local text = readFile(folder .. "/games.dat")
|
|
|
|
if text then
|
|
local sandbox = {}
|
|
local chunk = load(text, "games.dat", "t", sandbox)
|
|
|
|
if chunk and pcall(chunk) and (type(sandbox.GAMES) == "table") then
|
|
for _, entry in ipairs(sandbox.GAMES) do
|
|
local script = tostring(entry.SCRIPT or "")
|
|
|
|
if script:lower():sub(-#main) == main:lower() then
|
|
return entry
|
|
end
|
|
end
|
|
if #sandbox.GAMES == 1 then
|
|
return sandbox.GAMES[1]
|
|
end
|
|
end
|
|
end
|
|
folder = folder:match("^(.*)[/\\][^/\\]+$") or folder
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
|
|
-- The header comment's "KEY: value" lines, as the author wrote them: program name, author, who
|
|
-- converted it and for what.
|
|
local function headerNotes(dir, main)
|
|
local text = readFile(dir .. "/" .. main) or ""
|
|
local block = text:match("%-%-%[=*%[(.-)%]=*%]")
|
|
local notes = {}
|
|
|
|
for line in (block or ""):gmatch("[^\r\n]+") do
|
|
local key, value = line:match("^%s*([A-Z][A-Z0-9 /%-]*[A-Z0-9])%s*:%s*(.-)%s*$")
|
|
|
|
if key and (value ~= "") then
|
|
notes[#notes + 1] = key:sub(1, 1) .. key:sub(2):lower() .. ": " .. value
|
|
end
|
|
end
|
|
|
|
return notes
|
|
end
|
|
|
|
|
|
-- The licence files beside the script, by name.
|
|
local function licences(dir)
|
|
local found = {}
|
|
local handle = io.popen('ls -1 "' .. dir .. '" 2>/dev/null')
|
|
|
|
if handle then
|
|
for name in handle:lines() do
|
|
for _, pattern in ipairs(LICENCE_PATTERNS) do
|
|
if name:lower():match(pattern) then
|
|
found[#found + 1] = name
|
|
break
|
|
end
|
|
end
|
|
end
|
|
handle:close()
|
|
end
|
|
table.sort(found)
|
|
|
|
return found
|
|
end
|
|
|
|
|
|
-- The credits block as description source, one line, or nil when there is nothing to credit.
|
|
function creditsFor(dir, main)
|
|
local entry = gamesEntry(dir, main) or {}
|
|
local parts = {}
|
|
local notes = headerNotes(dir, main)
|
|
local files = licences(dir)
|
|
|
|
for _, credit in ipairs(CREDIT_KEYS) do
|
|
local value = entry[credit.key]
|
|
|
|
if type(value) == "number" then
|
|
parts[#parts + 1] = string.format("%s = %d", credit.field, value)
|
|
elseif (type(value) == "string") and (value ~= "") then
|
|
parts[#parts + 1] = string.format("%s = %q", credit.field, value)
|
|
end
|
|
end
|
|
if #notes > 0 then
|
|
local quoted = {}
|
|
|
|
for _, note in ipairs(notes) do
|
|
quoted[#quoted + 1] = string.format("%q", note)
|
|
end
|
|
parts[#parts + 1] = "notes = { " .. table.concat(quoted, ", ") .. " }"
|
|
end
|
|
if #files > 0 then
|
|
local quoted = {}
|
|
|
|
for _, name in ipairs(files) do
|
|
quoted[#quoted + 1] = string.format("%q", name)
|
|
end
|
|
parts[#parts + 1] = "licenses = { " .. table.concat(quoted, ", ") .. " }"
|
|
end
|
|
if #parts == 0 then
|
|
return nil
|
|
end
|
|
|
|
return "\tcredits = { " .. table.concat(parts, ", ") .. " },"
|
|
end
|
|
|
|
|
|
-- The description with its credits line put just after the title, replacing one already there.
|
|
local function withCredits(text, line)
|
|
local out = {}
|
|
local after = false
|
|
|
|
for row in (text .. "\n"):gmatch("(.-)\n") do
|
|
if not row:match("^\tcredits = {") then
|
|
out[#out + 1] = row
|
|
if (not after) and line and row:match("^\ttitle%s*=") then
|
|
out[#out + 1] = line
|
|
after = true
|
|
end
|
|
end
|
|
end
|
|
while (#out > 0) and (out[#out] == "") do
|
|
out[#out] = nil
|
|
end
|
|
|
|
return table.concat(out, "\n") .. "\n", after
|
|
end
|
|
|
|
|
|
if arg and arg[0] and arg[0]:match("forgeCredits%.lua$") then
|
|
local description = arg[1] or error("a description")
|
|
local dir = arg[2] or error("the game's directory")
|
|
local main = arg[3] or error("its main script")
|
|
local text = readFile(description) or error("cannot read " .. description)
|
|
local line = creditsFor(dir, main)
|
|
local result, put = withCredits(text, line)
|
|
|
|
if line and not put then
|
|
error("no title line in " .. description)
|
|
end
|
|
local handle = io.open(description, "wb")
|
|
|
|
handle:write(result)
|
|
handle:close()
|
|
print(description .. ": " .. (line and line:gsub("^\t", "") or "nothing to credit"))
|
|
end
|