Modulo:ChronoSpine/manual
Vai alla navigazione
Vai alla ricerca
La documentazione per questo modulo può essere creata in Modulo:ChronoSpine/manual/man
--[[
Module:ChronoSpine/manual
============================
Purpose
-------
The "manual" data-source adapter: turns numbered parameter sets typed
directly into a template call (`data1=`, `voce1=`, `stellare1=`, ...)
into normalised entries (Module:ChronoSpine/entry shape), ready for
the sort/group/render stages of the pipeline (Steps 3-4).
This module is `tempo`-independent: nothing here reads or branches on
`tempo` (reale/narrativo). A bare year is a valid date either way, and
the differences between the two modes (which date SMW property to
prefer, the "DS" stardate display prefix, year auto-links, ...) only
matter once entries reach grouping/rendering, not while parsing a
single manual entry. If a `tempo`-specific validation rule ever
surfaces, it belongs here as an explicit exception -- flagged in the
design discussion rather than assumed away.
Discovery of numbered sets
---------------------------
Unlike Wikipedia's `Module:Timeline of release years` (which stops at
the first gap in a contiguous 1997, 1998, 1999, ... sequence), this
module scans the ENTIRE set of frame arguments for every field's
aliases (not just `date`), and processes every distinct index for
which at least one field has a non-blank value -- gaps are fine, and
an index where every field is blank or absent is a leftover unused
template slot, not a mistake (no problem is logged for it at all).
Problem severity
------------------
Each numbered set can go wrong in two different ways:
* FATAL (the whole entry is left out of `entries`, and only appears
in `problems`): the `date` field is missing entirely, or present
but fails to parse; or the `label` field is missing. There is no
sensible position (no date) or no name (no label) to put on the
timeline.
* NON-FATAL (the entry still goes into `entries`; the one bad field
is simply left unset, and a problem is still logged so the editor
can fix it): the `stardate` field is present but fails to parse.
A stardate is only ever an in-year tie-breaker, never a
substitute for the date, so losing it does not make the entry
unplaceable.
`?` typed for `date` is NOT a problem of either kind -- it is the
normal "uncertain date" path already handled by
Module:ChronoSpine/date (`ok = true, incerta = true`), so those
entries go straight into `entries`.
What this module does NOT do
------------------------------
It never turns a problem `code` into displayed text (that is
Module:ChronoSpine/i18n's M.message), and it never decides how
problems reach the reader -- a note box, a tracking category, or
anything else (that is the renderer, Step 4, using
Module:ChronoSpine/config for the category name). `/manual` only
produces the structured data.
--]]
local M = {}
local i18n = require("Module:ChronoSpine/i18n")
local entry = require("Module:ChronoSpine/entry")
local date_parser = require("Module:ChronoSpine/date")
local stardate_parser = require("Module:ChronoSpine/stardate")
--[[
discover_indices(args)
------------------------
Private helper. Finds every numbered-set index that has at least one
non-blank field, across every field's aliases (see M.field_aliases in
Module:ChronoSpine/i18n) -- not just the `date` field -- so that, for
example, a set with `voce7` filled in but `data7` forgotten is still
discovered (and therefore still gets a "missing-date" problem logged
for it), rather than being silently invisible to this module simply
because nothing ever looked for a `data7`/`date7` key.
Parameters:
args (table) -- the raw frame arguments: a plain table of parameter
name -> string value (frame:getParent().args or
equivalent).
Returns:
(table) -- a list of indices (numbers), sorted ascending.
--]]
local function discover_indices(args)
local found = {} -- set: index (number) -> true
for _, aliases in pairs(i18n.field_aliases) do
for _, alias in ipairs(aliases) do
-- `alias` is always one of our own hardcoded strings from
-- Module:ChronoSpine/i18n, never derived from wiki
-- content, so it is safe to splice directly into a Lua
-- pattern without needing to escape it.
local pattern = "^" .. alias .. "(%d+)$"
for key, value in pairs(args) do
if type(key) == "string" then
local index_text = key:match(pattern)
if index_text then
local value_text = (type(value) == "string") and value or tostring(value)
local trimmed = value_text:match("^%s*(.-)%s*$")
if trimmed ~= "" then
found[tonumber(index_text)] = true
end
end
end
end
end
end
local indices = {}
for index in pairs(found) do
table.insert(indices, index)
end
table.sort(indices)
return indices
end
--[[
build_one(args, index)
------------------------
Private helper. Resolves and validates a single numbered set (e.g.
everything with suffix `3`: `data3`, `voce3`, `stellare3`, ...).
Parameters:
args (table) -- the raw frame arguments, as in discover_indices().
index (number) -- the numbered set's index.
Returns:
ok_entry (table|nil) -- a normalised entry (see
Module:ChronoSpine/entry), or nil if `date`
or `label` was fatally missing/invalid.
problems (table) -- a list (possibly empty) of problem tables,
each { ordine, field, code, raw }. Populated
regardless of whether `ok_entry` is nil --
a non-fatal problem can exist alongside a
perfectly good entry.
--]]
local function build_one(args, index)
local problems = {}
local fatal = false
-- `date`: fatal if missing or unparseable.
local date_raw = i18n.resolve(args, "date", index)
local date_result
if date_raw == nil then
fatal = true
table.insert(problems, { ordine = index, field = "date", code = "missing-date", raw = nil })
else
date_result = date_parser.parse(date_raw)
if not date_result.ok then
fatal = true
table.insert(problems, {
ordine = index,
field = "date",
code = "date:" .. date_result.code,
raw = date_raw,
})
end
end
-- `label`: fatal if missing. (No parser to run -- any non-blank
-- text is a valid label, wikitext included.)
local label_raw = i18n.resolve(args, "label", index)
if label_raw == nil then
fatal = true
table.insert(problems, { ordine = index, field = "label", code = "missing-label", raw = nil })
end
if fatal then
-- No point even attempting the non-fatal fields below: there
-- is nothing valid to attach them to.
return nil, problems
end
-- `stardate`: NON-fatal. A bad value just means the entry has no
-- tie-breaker; it still gets placed on the spine by its date.
local stellare_value
local stardate_raw = i18n.resolve(args, "stardate", index)
if stardate_raw ~= nil then
local stardate_result = stardate_parser.parse(stardate_raw)
if stardate_result.ok then
stellare_value = stardate_result.value
else
table.insert(problems, {
ordine = index,
field = "stardate",
code = "stardate:" .. stardate_result.code,
raw = stardate_raw,
})
end
end
local new_entry = entry.new({
anno = date_result.anno,
mese = date_result.mese,
giorno = date_result.giorno,
circa = date_result.circa,
incerta = date_result.incerta,
fine = date_result.fine,
stellare = stellare_value,
voce = label_raw,
fonte = i18n.resolve(args, "source", index),
nota = i18n.resolve(args, "note", index),
gruppo = i18n.resolve(args, "group", index),
categoria = i18n.resolve(args, "category", index),
stato = i18n.resolve(args, "status", index),
ordine = index,
})
return new_entry, problems
end
--[[
M.build(args)
--------------
The adapter's single public entry point. Discovers every numbered set
present in `args`, validates and normalises each one, and returns
everything found.
Parameters:
args (table) -- the raw frame arguments (frame:getParent().args or
equivalent).
Returns:
(table) {
entries = { <normalised entry>, ... }, -- see Module:ChronoSpine/entry
problems = { { ordine, field, code, raw }, ... },
}
Both lists may be empty. `entries` is NOT sorted (that is Step 3's
job); it reflects discovery order, which is already ascending by
index since discover_indices() sorts it.
--]]
function M.build(args)
local indices = discover_indices(args)
local entries = {}
local problems = {}
for _, index in ipairs(indices) do
local ok_entry, entry_problems = build_one(args, index)
if ok_entry then
table.insert(entries, ok_entry)
end
for _, problem in ipairs(entry_problems) do
table.insert(problems, problem)
end
end
return { entries = entries, problems = problems }
end
return M