Module:SMAPI compatibility
Revision as of 03:24, 24 October 2018 by Pathoschild (talk | contribs) (add temporary printAttribute method)
This module provides the table structure and data for the Modding:Mod compatibility list.
Examples
Compatible mod
{{#invoke:SMAPI compatibility|entry |name = Lookup Anything |author = Pathoschild |id = Pathoschild.LookupAnything |nexus = 541 |github = Pathoschild/StardewMods }}
mod name | author | compatibility | broke in | source | |
---|---|---|---|---|---|
[ Lookup Anything] | Pathoschild | ✓ use latest version. | source | # |
Broken mod
{{#invoke:SMAPI compatibility|entry |name = Lookup Anything |author = Pathoschild |id = Pathoschild.LookupAnything |nexus = 541 |github = Pathoschild/StardewMods |summary = |broke in = Stardew Valley 1.2 }}
mod name | author | compatibility | broke in | source | |
---|---|---|---|---|---|
[ Lookup Anything] | Pathoschild | ✖ broken, not open-source. | Stardew Valley 1.2 | source | # |
Unofficial update
For an unofficial update, use the broken-mod template and add these under the other fields:
|unofficial url = https://community.playstarbound.com/attachments/201345000 |unofficial version = 1.18.2-unofficial.1-example
mod name | author | compatibility | broke in | source | |
---|---|---|---|---|---|
[ Lookup Anything] | Pathoschild | ⚠ broken, use unofficial version (1.18.2-unofficial.1-example). | Stardew Valley 1.2 | source | # |
Usage
Limitations
The name, author, and id arguments are comma-separated. If the actual value contains a comma, use ,
instead.
Main fields (shown above)
field | purpose |
---|---|
name
|
The normalised display name for the mod. Delimit alternate names with commas. |
author
|
The name of the author, as shown on Nexus or in its manifest.json file. Delimit alternate names with commas. |
id
|
The latest unique mod ID, as listed in its manifest.json file. Delimit alternate/older IDs with commas (ideally in latest to oldest order). For very old mods with no ID, use none to disable validation checks. |
nexus
|
The mod's unique ID on Nexus (if any). This is the number in the mod page's URL. |
github
|
The mod's GitHub repository in the form owner/repo. |
summary
|
Specify custom notes or instructions about the mod's compatibility. Should usually be blank. |
broke in
|
The SMAPI or Stardew Valley update which broke this mod (if applicable). |
Other fields
field | purpose |
---|---|
status
|
Whether the mod is compatible with the latest versions of Stardew Valley and SMAPI (see #Valid statuses). If not specified, it defaults to unofficial if an unofficial URL is given, else broken if broke in is specified, else ok .
|
unofficial url
|
A page URL where the player can download an unofficial update, if any. |
unofficial version
|
The unofficial update's version number, if any. |
chucklefish
|
The mod's ID in the Chucklefish mod repository. |
curse
|
The mod's project ID and key in the CurseForge mod repository. The ID is shown on the mod page next to "Project ID", and the key is shown in the mod page's URL. This must be in the form id,key .
|
moddrop
|
The mod's ID in the ModDrop mod repository. |
url
|
The arbitrary mod URL, if not on a known mod site. Avoid if possible, since this makes crossreferencing more difficult. |
source
|
An arbitrary source code URL, if not on GitHub. Avoid if possible, since this makes crossreferencing more difficult. |
warnings
|
Text explaining additional compatibility warnings about the mod (e.g., not compatible with Linux/Mac). |
content pack for
|
The name of the mod which loads this content pack. |
dev note
|
Special notes intended for developers who maintain unofficial updates or submit pull requests. |
Valid statuses
status | description |
---|---|
ok
|
The mod is compatible. This is the default and doesn't need to be specified. Default summary: use latest version. |
optional
|
The mod is compatible, if you use an optional download on the mod page. Default summary: use optional download.[1] |
unofficial
|
The mod is compatible using an unofficial update. There's no need to specify this; if you also set unofficial url and unofficial version, you can remove the status field. |
workaround
|
The mod isn't compatible, but the player can fix it or there's a good alternative. A summary should be provided manually. If you also set unofficial url and unofficial version, you can remove the status field. |
broken
|
The mod isn't compatible. The message depends on whether the source link is set.Default summary: broken, not updated yet or broken, not open-source. |
abandoned
|
The mod is no longer maintained by the author, and an unofficial update or continuation is unlikely. This should only be used when the author has definitively abandoned the mod (either explicitly, or by removing the mod page or downloads). Default summary: no longer maintained. |
obsolete
|
The mod is no longer needed and should be removed. |
unknown
|
The mod's compatibility status hasn't been tested. This should only be used as a placeholder (e.g., when adding a new beta), it should never be used long since that defeats the purpose of the compatibility list. |
local p = {}
local private = {}
--##########
--## Constants
--##########
-- The valid status values.
local statuses = {
ok = "ok",
optional = "optional",
unofficial = "unofficial",
workaround = "workaround",
broken = "broken",
abandoned = "abandoned",
obsolete = "obsolete"
}
--##########
--## Public functions
--##########
-- Start a SMAPI compatibility table.
-- @test mw.log(p.header())
function p.header()
local row = mw.html.create("tr")
row:node(mw.html.create("th"):wikitext("mod name"))
row:node(mw.html.create("th"):wikitext("author"))
row:node(mw.html.create("th"):wikitext("compatibility"))
row:node(mw.html.create("th"):wikitext("broke in"))
row:node(mw.html.create("th"):wikitext("source"))
row:node(mw.html.create("th"):wikitext(" "))
return '<table class="wikitable plainlinks">' .. tostring(row)
end
-- End a SMAPI compatibility table.
-- @test mw.log(p.footer())
function p.footer()
return '</table>'
end
--- Render a mod row in the SMAPI compatibility table.
-- @param frame The arguments passed to the script.
-- @test mw.log(p.entry({ args = { name="Lookup Anything", name2="LookupAnything", author="Pathoschild", author2="Pathos", id="Pathoschild.LookupAnything", ["old ids"]="LookupAnything", ["nexus id"]="541", ["cf id"]="4250", ["github"]="Pathoschild/StardewMods", warnings="warning A, warning B", links="https://google.ca" }}))
function p.entry(frame)
-- read input args
local names = private.parseCommaDelimited((private.trim(frame.args["name"]) or '') .. ',' .. (private.trim(frame.args["name2"]) or ''))
local authors = private.parseCommaDelimited((private.trim(frame.args["author"]) or '') .. ',' .. (private.trim(frame.args["author2"]) or ''))
local ids = private.parseCommaDelimited((private.trim(frame.args["id"]) or '') .. ',' .. (private.trim(frame.args["old ids"]) or ''))
local nexusID = private.emptyToNil(private.trim(frame.args["nexus id"]))
local github = private.emptyToNil(private.trim(frame.args["github"]))
local summary = private.emptyToNil(private.trim(frame.args["summary"]))
local brokeIn = private.emptyToNil(private.trim(frame.args["broke in"]))
local status = private.emptyToNil(private.trim(frame.args["status"]))
local unofficialVersion = private.emptyToNil(private.trim(frame.args["unofficial version"]))
local unofficialUrl = private.emptyToNil(private.trim(frame.args["unofficial url"]))
local oldIDs = private.emptyToNil(private.trim(frame.args["old ids"]))
local chucklefishID = private.emptyToNil(private.trim(frame.args["cf id"]))
local customUrl = private.emptyToNil(private.trim(frame.args["url"]))
local customSource = private.emptyToNil(private.trim(frame.args["source"]))
local name2 = private.emptyToNil(private.trim(frame.args["name2"]))
local author2 = private.emptyToNil(private.trim(frame.args["author2"]))
local links = private.parseCommaDelimited(frame.args["links"])
local warnings = private.parseCommaDelimited(frame.args["warnings"])
local betaSummary = private.emptyToNil(private.trim(frame.args["beta summary"]))
local betaBrokeIn = private.emptyToNil(private.trim(frame.args["beta broke in"]))
local betaStatus = private.emptyToNil(private.trim(frame.args["beta status"]))
local betaUnofficialVersion = private.emptyToNil(private.trim(frame.args["beta unofficial version"]))
local betaUnofficialUrl = private.emptyToNil(private.trim(frame.args["beta unofficial url"]))
-- parse compatibility
local compat = private.getCompatInfo(status, summary, brokeIn, unofficialVersion, unofficialUrl)
local betaCompat = nil
if betaStatus or betaBrokeIn or betaUnofficialUrl or betaUnofficialVersion then
betaCompat = private.getCompatInfo(betaStatus, betaSummary, betaBrokeIn, betaUnofficialVersion, betaUnofficialUrl)
end
-- get main URL
local url = nil
if nexusID then
url = "https://www.nexusmods.com/stardewvalley/mods/" .. mw.uri.encode(nexusID, "PATH")
elseif chucklefishID then
url = "https://community.playstarbound.com/resources/" .. mw.uri.encode(chucklefishID, "PATH")
else
url = customUrl
end
-- get source url
local sourceUrl = customSource
if github then
sourceUrl = "https://github.com/" .. string.gsub(mw.uri.encode(github, "PATH"), "%%2F", "/")
end
-- get background color
local background = '#999'
if compat.status == statuses.ok or compat.status == statuses.optional then
background = '#9F9'
elseif compat.status == statuses.workaround or compat.status == statuses.unofficial then
background = '#CF9'
elseif compat.status == statuses.broken then
background = '#F99'
elseif compat.status == statuses.obsolete or compat.status == statuses.abandoned then
background = '#999'
end
-- build HTML row
local row = mw.html.create("tr")
row:addClass("mod")
row:attr("id", names[1] and mw.uri.anchorEncode(names[1]) or nil);
row:attr("data-name", table.concat(names, ","))
row:attr("data-id", table.concat(ids, ","))
row:attr("data-author", table.concat(authors, ","))
row:attr("data-url", url)
row:attr("data-nexus-id", nexusID)
row:attr("data-cf-id", chucklefishID)
row:attr("data-github", github)
row:attr("data-custom-source", customSource)
row:attr("data-status", compat.status)
row:attr("data-summary", compat.summary)
row:attr("data-broke-in", compat.brokeIn)
row:attr("data-unofficial-version", compat.unofficialVersion)
row:attr("data-unofficial-url", compat.unofficialUrl)
row:attr("data-beta-status", betaCompat and betaCompat.status or nil)
row:attr("data-beta-summary", betaCompat and betaCompat.summary or nil)
row:attr("data-beta-broke-in", betaCompat and betaCompat.brokeIn or nil)
row:attr("data-beta-unofficial-version", betaCompat and betaCompat.unofficialVersion or nil)
row:attr("data-beta-unofficial-url", betaCompat and betaCompat.unofficialUrl or nil)
row:attr("data-warnings", private.emptyToNil(table.concat(warnings, ",")))
row:attr("style", "line-height: 1em; background: " .. background .. ";")
row:newline()
-- add name field
do
local field = mw.html.create("td")
field:wikitext("[" .. (url or '') .. " " .. (names[1] or '') .. "]")
if #names > 1 then
local altNames = mw.html.create("small"):wikitext("(aka ")
for k, v in pairs(names) do
if k > 1 then
altNames:wikitext(v)
end
end
altNames:wikitext(")")
field:node(mw.html.create("br"))
field:node(altNames)
end
row:node(field)
row:newline()
end
-- add author field
do
local field = mw.html.create("td")
field:wikitext(authors[1])
if #authors > 1 then
local altNames = mw.html.create("small"):wikitext("(aka ")
for k, v in pairs(authors) do
if k > 1 then
altNames:wikitext(v)
end
end
altNames:wikitext(")")
field:node(mw.html.create("br"))
field:node(altNames)
end
row:node(field)
row:newline()
end
-- add summary field
do
local field = mw.html.create("td")
-- stable status
field:wikitext(compat.summaryIcon .. " " .. compat.summary)
if compat.status == statuses.optional then
field:wikitext("<ref name=\"optional-update\" />")
end
-- beta status
if betaCompat ~= nill then
field:node(mw.html.create("br"))
field:wikitext("'''SDV beta only:''' " .. betaCompat.summaryIcon .. " " .. betaCompat.summary)
if betaCompat.status == statuses.optional then
field:wikitext("<ref name=\"optional-update\" />")
end
end
-- warnings
if #warnings > 0 then
for k, v in pairs(warnings) do
field:node(mw.html.create("br"))
field:wikitext("⚠ " .. v)
end
end
row:node(field)
row:newline()
end
-- add 'broke in' field
do
local field = mw.html.create("td")
if betaCompat ~= nil and betaCompat.brokeIn ~= nil then
field:wikitext(betaCompat.brokeIn)
elseif compat.brokeIn ~= nil then
field:wikitext(compat.brokeIn)
end
row:node(field)
row:newline()
end
-- add 'source' field
do
local field = mw.html.create("td")
if sourceUrl then
field:wikitext("[" .. sourceUrl .. " source]")
else
field:wikitext("<span style=\"color: red; font-size: 0.85em; opacity: 0.5;\">closed source</span>")
end
row:node(field)
row:newline()
end
-- add metadata field
do
local field = mw.html.create("td")
field:attr("style", "font-size: 0.8em")
-- anchor
field:wikitext("[[#" .. (names[1] or '') .. "|#]] ")
-- reference links
for k, v in pairs(links) do
field:wikitext("[" .. v .. " " .. k .. "] ")
end
-- 'no id' warning
if #ids == 0 then
field:wikitext("⚠ no id")
end
-- backwards-compatible metadata (temporary)
local metadata = mw.html.create("div")
metadata:addClass("mod-metadata")
metadata:attr("style", "display: none;")
metadata:node(mw.html.create("div"):addClass("mod-anchor"):wikitext(names[1] and mw.uri.anchorEncode(names[1])))
metadata:node(mw.html.create("div"):addClass("mod-id"):wikitext(table.concat(ids, ",")))
metadata:node(mw.html.create("div"):addClass("mod-url"):wikitext(url))
if nexusID ~= nil then
metadata:node(mw.html.create("div"):addClass("mod-nexus-id"):wikitext(nexusID))
end
if chucklefishID ~= nil then
metadata:node(mw.html.create("div"):addClass("mod-cf-id"):wikitext(chucklefishID))
end
if github ~= nil then
metadata:node(mw.html.create("div"):addClass("mod-github"):wikitext(github))
end
if customSource ~= nil then
metadata:node(mw.html.create("div"):addClass("mod-custom-source"):wikitext(customSource))
end
metadata:node(mw.html.create("div"):addClass("mod-status"):wikitext(compat.status))
if compat.brokeIn ~= nil then
metadata:node(mw.html.create("div"):addClass("mod-broke-in"):wikitext(compat.brokeIn))
end
if compat.unofficialVersion ~= nil and compat.unofficialUrl ~= nil then
metadata:node(mw.html.create("div"):addClass("mod-unofficial-version"):wikitext(compat.unofficialVersion))
metadata:node(mw.html.create("div"):addClass("mod-unofficial-url"):wikitext(compat.unofficialUrl))
end
if betaCompat ~= nil then
metadata:node(mw.html.create("div"):addClass("mod-beta-status"):wikitext(betaCompat.status))
if betaCompat.brokeIn ~= nil then
metadata:node(mw.html.create("div"):addClass("mod-beta-broke-in"):wikitext(betaCompat.brokeIn))
end
if betaCompat.unofficialVersion ~= nil and betaCompat.unofficialUrl ~= nil then
metadata:node(mw.html.create("div"):addClass("mod-beta-unofficial-version"):wikitext(betaCompat.unofficialVersion))
metadata:node(mw.html.create("div"):addClass("mod-beta-unofficial-url"):wikitext(betaCompat.unofficialUrl))
end
end
if #warnings > 0 then
metadata:node(mw.html.create("div"):addClass("mod-warnings"):wikitext(table.concat(warnings, ",")))
end
field:node(metadata)
row:node(field)
end
return tostring(row)
end
-- Print an HTML attribute (like key="value") if a non-empty value is specified. This is a temporary method during the migration to Lua.
-- @param frame The arguments passed to the script.
-- @test mw.log(p.printAttribute({ args = { key="someKey", value="some \"'<> value" }}))
function p.printAttribute(frame)
local key = frame.args["key"]
local value = frame.args["value"]
if value == nil or value == "" then
return ""
else
return key .. "=\"" .. mw.text.encode(value) .. "\""
end
end
--##########
--## Private functions
--##########
-- Get the normalised compatibility info for a mod.
-- @param status The specified status code (one of the `statuses` values). If nil or blank, it'll be derived from the other fields.
-- @param summary A human-readable summary of the compatibility info. If nil or blank, it'll be derived from the other fields.
-- @param brokeIn The SMAPI or Stardew Valley version which broke the mod, if applicable.
-- @param unofficialVersion The unofficial version which fixes compatibility, if applicable.
-- @param unofficialUrl The URL for the unofficial version which fixes compatibility, if applicable.
-- @param hasSource Whether the mod has public source code available.
function private.getCompatInfo(status, summary, brokeIn, unofficialVersion, unofficialUrl, hasSource)
-- normalise values
status = private.emptyToNil(private.trim(status))
summary = private.emptyToNil(private.trim(summary))
brokeIn = private.emptyToNil(private.trim(brokeIn))
unofficialVersion = private.emptyToNil(private.trim(unofficialVersion))
unofficialUrl = private.emptyToNil(private.trim(unofficialUrl))
-- derive status
if status == nil then
if unofficialVersion ~= nil then
status = statuses.unofficial
elseif brokeIn ~= nil then
status = statuses.broken
else
status = statuses.ok
end
end
-- derive summary icon
local summaryIcon = "✓"
if status == statuses.unofficial or status == statuses.workaround then
summaryIcon = "⚠"
elseif status == statuses.broken and hasSource then
summaryIcon = "↻"
elseif status == statuses.broken or status == statuses.obsolete or status == statuses.abandoned then
summaryIcon = "✖"
end
-- derive summary
if summary == nil then
if status == statuses.ok then
summary = "use latest version."
elseif status == statuses.optional then
summary = "use optional download."
elseif status == statuses.unofficial then
summary = "broken, use [" .. (unofficialUrl or "") .. " " .. "unofficial version]"
if unofficialVersion ~= nil then
summary = summary .. " (<small>" .. unofficialVersion .. "</small>)"
end
summary = summary .. "."
elseif status == statuses.workaround then
summary = "broken. '''error:''' should specify summary."
elseif status == statuses.broken then
if hasSource then
summary = "broken, not updated yet."
else
summary = "broken, not open-source."
end
elseif status == statuses.obsolete then
summary = "obsolete."
elseif status == statuses.abandoned then
summary = "no longer maintained."
else
summary = "'''error:''' unknown status '" .. status .. "'."
end
end
return {
status = status,
summaryIcon = summaryIcon,
summary = summary,
brokeIn = brokeIn,
unofficialVersion = unofficialVersion,
unofficialUrl = unofficialUrl
}
end
-- Trim a string value.
-- @param value The string to trim.
function private.trim(value)
if value ~= nil then
return mw.text.trim(value)
else
return value
end
end
-- Get a nil value if the specified value is an empty string, else return the value unchanged.
-- @param value The string to check.
function private.emptyToNil(value)
if value ~= "" then
return value
else
return nil
end
end
-- Parse a comma-delimited string into an array.
-- @param value The string to parse.
function private.parseCommaDelimited(value)
local result = {}
if value ~= nil then
for k, v in pairs(mw.text.split(value, ",", true)) do
v = mw.text.trim(v)
if v ~= "" then
table.insert(result, v)
end
end
end
return result
end
return p