Jump to content

Module:Format TemplateData/global

From Wikipedia, the free encyclopedia
local Export = {
  item = 51435481,
  serial = "2020-08-01",
  subpages = "TemplateData",
  suffix = "tab",
  suite = "TemplateDataGlobal"
}
--[=[
Retrieve TemplateData from Commons:Data (or other global source).

require()

Inspired by [[de:User:Yurik]].
]=]
local Failsafe = Export

local failsafe = function(atleast)
  -- Retrieve versioning and check for compliance.
  -- Precondition:
  --     atleast  -- string, with required version
  --                         or "wikidata" or "~" or "@" or false
  -- Postcondition:
  --     Returns  string  -- with queried version/item, also if problem
  --              false   -- if appropriate
  local last = (atleast == "~")
  local link = (atleast == "@")
  local since = atleast
  local r
  if last or link or since == "wikidata" then
    local item = Failsafe.item
    since = false
    if type(item) == "number" and item > 0 then
      local suited = string.format("Q%d", item)
      local entity = mw.wikibase.getEntity(suited)
      if type(entity) == "table" then
        local seek = Failsafe.serialProperty or "P348"
        local vsn = entity:formatPropertyValues(seek)
        if type(vsn) == "table" and type(vsn.value) == "string" and vsn.value ~= "" then
          if last and vsn.value == Failsafe.serial then
            r = false
          elseif link then
            if mw.title.getCurrentTitle().prefixedText == mw.wikibase.getSitelink(suited) then
              r = false
            else
              r = suited
            end
          else
            r = vsn.value
          end
        end
      end
    end
  end
  if type(r) == "nil" then
    if not since or since <= Failsafe.serial then
      r = Failsafe.serial
    else
      r = false
    end
  end
  return r
end -- failsafe()

local function fair(already, adapt, append)
  -- Merge local definitions into global base.
  -- Parameter:
  --     already  -- global item
  --     adapt    -- local override item
  --     append   -- append to sequence table
  -- Returns merged item
  local r
  if already and adapt then
    if type(already) == "table" and type(adapt) == "table" then
      r = already
      if append then
        for i = 1, #adapt do
          table.insert(r, adapt[i])
        end -- for i
      else
        for k, v in pairs(adapt) do
          r[k] = v
        end -- for k, v
      end
    else
      r = adapt
    end
  else
    r = already or adapt
  end
  return r
end -- fair()

local function feed(apply)
  -- Retrieve override from JSON code.
  -- Parameter:
  --     apply  --  string, with JSON
  -- Returns string, with error message, or table
  local lucky, r = pcall(mw.text.jsonDecode, apply)
  if not lucky then
    r = "fatal JSON error in LOCAL override"
  end
  return r
end -- feed()

local function find(access)
  -- Fetch data from page.
  -- Parameter:
  --    access  -- string, with core page name
  -- Returns
  --    1. string, with prefixed page name
  --    2. table with JSON data, or error message
  local storage = access
  local lucky, r
  if Export.suffix and not storage:find(".", 2, true) then
    local k = -1 - #Export.suffix
    if storage:sub(k) ~= "." .. Export.suffix then
      storage = string.format("%s.%s", storage, Export.suffix)
    end
  end
  if Export.subpages and not storage:find("/", 1, true) then
    storage = string.format("%s/%s", Export.subpages, storage)
  end
  lucky, r = pcall(mw.ext.data.get, storage, "_")
  storage = "Data:" .. storage
  if mw.site.siteName ~= "Wikimedia Commons" then
    storage = "commons:" .. storage
  end
  if type(r) ~= "table" and type(r) ~= "string" then
    r = "INVALID"
  end
  return storage, r
end -- find()

local function flat(apply)
  -- Convert tabular data into TemplateData.
  -- Parameter:
  --     apply -- table, with tabular data
  -- Returns string, with error message, or table, with TemplateData
  local r, scream
  local function failed(at, alert)
    if scream then
      scream = string.format("%s * #%d: %s", scream, at, alert)
    else
      scream = add
    end
  end -- failed()
  if type(apply.schema) == "table" and type(apply.schema.fields) == "table" and type(apply.data) == "table" then
    local order = {}
    local entry, got, params, parOrder, s, sign, td, v
    for k, v in pairs(apply.schema.fields) do
      if type(v) == "table" then
        table.insert(order, v.name)
      end
    end -- for k, v
    for i = 1, #apply.data do
      entry = apply.data[i]
      if type(entry) == "table" then
        got = {}
        sign = false
        for j = 1, #entry do
          s = order[j]
          v = entry[j]
          if type(v) == "string" then
            v = mw.text.trim(v)
            if v == "" then
              v = false
            end
          end
          if v then
            if s == "name" then
              sign = v
            elseif s == "aliases" then
              if type(v) == "string" then
                got.aliases = mw.text.split(v, "%s*|%s*")
              else
                failed(i, "aliases not a string")
              end
            else
              got[s] = v
            end
          end
        end -- for j
        if sign == "|" then
          if td then
            failed(i, "root repeated")
          else
            td = {description = got.description}
            if type(got.type) == "string" then
              td.format = got.type:gsub("N", "\n")
            end
          end
        elseif sign then
          if params then
            if params[sign] then
              failed(i, "name repeated: " .. sign)
            end
          else
            params = {}
            parOrder = {}
          end
          params[sign] = got
          table.insert(parOrder, sign)
        else
          failed(i, "missing name")
        end
      else
        failed(i, "invalid component")
      end
    end -- for i
    r = td or {}
    r.params = params
    r.paramOrder = parOrder
  else
    r = "bad tabular structure"
  end
  return scream or r or "EMPTY"
end -- flat()

local function flush(assembly, avoid)
  -- Remove element from sequence table.
  -- Parameter:
  --     assembly  -- sequence table
  --     avoid     -- element
  for i = 1, #assembly do
    if assembly[i] == avoid then
      table.remove(assembly, i)
      break -- for i
    end
  end -- for i
end -- flush()

local function fold(already, adapt)
  -- Merge local parameter definitions into global base.
  -- Parameter:
  --     already  -- table, with global data
  --     adapt    -- sequence table, with local params overrides
  -- Returns string, with error message, or table, with TemplateData
  local order = {}
  local params = {}
  local r = already
  local entry, override, s
  r.params = r.params or {}
  r.paramOrder = r.paramOrder or {}
  for i = 1, #adapt do
    override = adapt[i]
    if type(override) ~= "table" then
      r = string.format("No object at LOCAL params[%d]", i)
      break -- for i
    elseif type(override.global) == "string" then
      s = override.global
      entry = r.params[s]
      if type(entry) == "table" then
        flush(r.paramOrder, s)
        if type(override["local"]) == "string" then
          s = override["local"]
          override["local"] = nil
        elseif override["local"] == false then
          entry = nil
        end
        if entry then
          override.global = nil
          for k, v in pairs(override) do
            entry[k] = fair(entry[k], override[k], (k == "aliases"))
          end -- for k, v
          table.insert(order, s)
        end
        params[s] = entry
      else
        r = string.format("No GLOBAL params %s for LOCAL [%d]", s, i)
        break -- for i
      end
    elseif type(override["local"]) == "string" then
      s = override["local"]
      override["local"] = nil
      params[s] = override
      table.insert(order, s)
    else
      r = string.format("No name for LOCAL params[%d]", i)
      break -- for i
    end
  end -- for i
  if type(r) == "table" then
    for i = 1, #r.paramOrder do
      s = r.paramOrder[i]
      params[s] = r.params[s]
      table.insert(order, s)
    end -- for i
    r.params = params
    r.paramOrder = order
  end
  return r
end -- fold()

local function fork(already, adapt)
  -- Merge local definitions into global base.
  -- Parameter:
  --     already  -- table, with global data
  --     adapt    -- table, with local overrides
  -- Returns string, with error message, or table, with TemplateData
  local root = {"description", "format", "maps", "sets", "style"}
  local r = already
  for k, v in pairs(root) do
    if adapt[v] then
      r[v] = fair(r[v], adapt[v])
    end
  end -- for k, v
  if type(adapt.params) == "table" then
    r = fold(r, adapt.params)
  end
  return r
end -- fork()

local function furnish(apply, at, adapt)
  -- Convert external data into TemplateData.
  -- Parameter:
  --     apply  -- table, with external data
  --     at     -- string, with page name
  --     adapt  -- JSON string or table or not, with local overrides
  -- Returns string, with error message, or table, with TemplateData
  local r
  if at:sub(-4) == ".tab" then
    r = flat(apply)
  else
    r = "Unknown page format: " .. at
  end
  if adapt and type(r) == "table" then
    local override = adapt
    if type(adapt) == "string" then
      override = feed(adapt)
      if type(override) == "string" then
        r = override
      end
    end
    if type(override) == "table" then
      r = fork(r, override)
    end
  end
  return r
end -- furnish()

Export.failsafe = function(frame)
  -- Versioning interface.
  local s = type(frame)
  local since
  if s == "table" then
    since = frame.args[1]
  elseif s == "string" then
    since = frame
  end
  if since then
    since = mw.text.trim(since)
    if since == "" then
      since = false
    end
  end
  return failsafe(since) or ""
end -- Export.failsafe()

Export.fetch = function(access, adapt)
  -- Fetch data from site.
  -- Parameter:
  --     access  -- string, with page specification
  --     adapt   -- JSON string or table or not, with local overrides
  -- Returns
  --    1. string, with error message or prefixed page name
  --    2. table with TemplateData, or not
  local storage, t = find(access)
  local s
  if type(t) == "table" then
    t = furnish(t, storage, adapt)
    if type(t) ~= "table" then
      s = t
    end
  else
    s = t
  end
  if type(t) ~= "table" then
    storage = string.format("[[%s]]", storage)
    if s then
      storage = string.format("%s * %s", storage, s)
    end
    t = false
  end
  return storage, t
end -- Export.fetch()

return Export