Module:SMAPI compatibility overrides

From Stardew Valley Wiki
Jump to navigation Jump to search

This module provides the table structure and data for the Modding:Mod compatibility#Mod data overrides list, which SMAPI uses to override data specified in mods' manifests or mod pages.

Usage

Change descriptor

A change descriptor describes a specific change to make for a field. There are three supported changes:

operation format effect
remove -value Delete that distinct value from the field.
add +value Add that distinct value to the field.
replace old value → new value Replace the old value (if found) with the new one.

Each field can list multiple change descriptors delimited with commas. For example, -Nexus:541, +Nexus:905 would delete Nexus:541 from the update keys and add Nexus:905. These operate on distinct values, so -Nexus: won't match anything (unless there's actually an update key without a number).

Fields

There are two required static fields:

field effect
name The mod's name as it appears in the Modding:Mod compatibility list. If the mod has multiple names, specify the first one.
id The manifest UniqueID field of the mod to change. This can be comma-delimited.

And these optional change descriptor fields:

field effect
update keys Override the update keys SMAPI uses to perform update checks.
local version
remote version
Override the version number specified in the mod manifest (local) or mod page (remote). You should generally only use replace in this field, since only the first value will be used.

SMAPI will compare using semantic versioning if they're parseable (e.g., 1.0 and 1.0.0 are equivalent), else it'll fallback to case-insensitive string comparison (e.g., 1.01 and 1.01.0 are not equivalent). If an invalid format is specified or the new version can't be parsed, the mapping will be ignored.

reason A human-readable reason the values need to be overridden, so it's easier to check whether it still needs to be overridden later.

Example

{{#invoke:SMAPI compatibility overrides|entry
  |name           = Lookup Anything
  |id             = Pathoschild.LookupAnything
  |update keys    = -Nexus:541, +Nexus:905
  |local version  = 1.05-beta → 1.0.5-beta
  |remote version = 1.06 → 1.0.6
  |reason         = test values
}}

local p = {}
local private = {}

--##########
--## Public functions
--##########
-- Start a mod data overrides table.
-- @test mw.log(p.header())
function p.header()
  return
    '<table class="wikitable sortable plainlinks" id="mod-overrides-list">'
    .. "<tr><th style=\"position: sticky; top: 0;\">mod name</th><th style=\"position: sticky; top: 0;\">update keys</th><th style=\"position: sticky; top: 0;\">manifest version</th><th style=\"position: sticky; top: 0;\">mod page version</th><th style=\"position: sticky; top: 0;\">reason</th><th style=\"position: sticky; top: 0;\">&nbsp;</th></tr>";
end

-- End a mod data overrides table.
-- @test mw.log(p.footer())
function p.footer()
  return '</table>'
end

--- Render a mod row in the mod data overrides table.
-- @param frame The arguments passed to the script.
-- @test mw.log(p.entry({ args = { name="Lookup Anything", id="Pathoschild.LookupAnything", ["update keys"]="-Nexus:541, +Nexus:905", ["local version"]="1.05-beta → 1.0.5-beta", ["remote version"]="1.06 → 1.0.6", ["reason"]="test values" }}))
function p.entry(frame)
  -- read input args
  local name          = private.emptyToNil(frame.args["name"])
  local ids           = private.parseCommaDelimited(frame.args["id"] or '')
  local updateKeys    = private.emptyToNil(frame.args["update keys"])
  local localVersion  = private.emptyToNil(frame.args["local version"])
  local remoteVersion = private.emptyToNil(frame.args["remote version"])
  local reason        = private.emptyToNil(frame.args["reason"])

  -- build HTML row
  local row = mw.html.create("tr")
  row:addClass("mod")
  row:attr("id", name and "override_" .. mw.uri.anchorEncode(name));
  row:attr("data-id", table.concat(ids, ","))
  row:attr("data-local-version", localVersion)
  row:attr("data-remote-version", remoteVersion)
  row:attr("data-update-keys", updateKeys)
  row:attr("data-reason", reason)
  row:attr("style", "line-height: 1em;")
  row:newline()

  -- add name field
  do
    local field = mw.html.create("td")

    if name then
        field:wikitext("[[#" .. name .. "|" .. name .. "]]")
    else
        field:wikitext(name)
    end

    row:node(field)
    row:newline()
  end

  -- add update keys field
  do
    local field = mw.html.create("td")
    field:wikitext(updateKeys)
    row:node(field)
    row:newline()
  end

  -- add local version field
  do
    local field = mw.html.create("td")
    field:wikitext(localVersion)
    row:node(field)
    row:newline()
  end

  -- add remote version field
  do
    local field = mw.html.create("td")
    field:wikitext(remoteVersion)
    row:node(field)
    row:newline()
  end

  -- add reason field
  do
    local field = mw.html.create("td")
    field:wikitext(reason)
    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("[[#override_" .. (name or '') .. "|#]] ")

    -- validation
    if #ids == 0 then
      field:wikitext("[⚠ no id] ")
    end

    row:node(field)
  end

  return tostring(row)
end


--##########
--## Private functions
--##########
-- 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
    local values = mw.text.split(value, ",", true)
    for i = 1, #values do
      v = mw.text.trim(values[i])
      if v ~= "" then
        table.insert(result, v)
      end
    end
  end

  return result
end

return p