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