Modulo:ChronoSpine/entry

Da Wikitrek.
Vai alla navigazione Vai alla ricerca

La documentazione per questo modulo può essere creata in Modulo:ChronoSpine/entry/man

--[[
Module:ChronoSpine/entry
=========================

Purpose
-------
Defines the *normalised entry* -- the single, fixed-shape Lua table
that every ChronoSpine data-source adapter (manual, SMW, Wikibase, ...)
must produce. This is the "central contract" of the whole pipeline
(project briefing, section 4.3): whatever the entry's origin, once it
has passed through an adapter it looks exactly the same to everything
downstream (sorting, grouping, rendering).

What lives here
----------------
  * M.new(fields)   -- builds one entry, filling in defaults.
  * M.compare(a, b) -- the sort comparator used by later pipeline
                       stages (Step 3) to order entries on the spine.

What does NOT live here
------------------------
This module never parses raw wikitext strings (that is
Module:ChronoSpine/date and Module:ChronoSpine/stardate), and it
never refers to any specific wiki, data source, or language. It is
pure data-shape logic, usable unchanged by any future adapter.
--]]

local M = {}

--[[
M.new(fields)
-------------
Builds one normalised entry table from a plain Lua table of fields.

Every field described in the contract (briefing sec. 4.3) is present
in the returned entry, even when the caller did not supply it: missing
values become `nil`, except for the two boolean flags `circa` and
`incerta`, which default to `false`. That way calling code can always
write `if entry.circa then ... end` without first checking whether the
field exists at all.

Parameters:
  fields (table|nil) -- may contain any of the fields below. Anything
    else in `fields` is ignored (this function is a strict filter, not
    a raw copy), so a typo in a caller's field name fails silently by
    being dropped rather than polluting the entry with an unknown key.

    anno      (number|nil)  -- year, e.g. 2257. `nil` only when the
                               whole date is uncertain (`incerta`).
    mese      (number|nil)  -- month, 1-12.
    giorno    (number|nil)  -- day, 1-31.
    circa     (boolean|nil) -- true for an approximate date ("c. ...").
    incerta   (boolean|nil) -- true for a wholly unknown date ("?").
    fine      (table|nil)   -- end of a range, e.g. "2256-2257"; same
                               shape as a plain date
                               (anno/mese/giorno/circa), no `incerta`.
    stellare  (number|nil)  -- stardate; an in-year tie-breaker only,
                               never a substitute for anno/mese/giorno.
    voce      (string|nil)  -- the entry's label (wikitext allowed).
    fonte     (string|nil)  -- optional source/citation (wikitext).
    nota      (string|nil)  -- optional short note under the label.
    gruppo    (string|nil)  -- optional explicit group override.
    categoria (string|nil)  -- optional icon/colour category key.
    stato     (string|nil)  -- optional status, e.g. "annunciato".
    ordine    (number|nil)  -- input position; the final, stable
                               tie-breaker when everything else ties.

Returns:
  (table) -- the normalised entry, with every documented field present
             (as a real key, possibly holding `nil` -- note: Lua does
             not actually store keys whose value is `nil`, so
             "present" here means "safe to read", not "shows up in
             `pairs()`").
--]]
function M.new(fields)
	fields = fields or {}
	return {
		anno      = fields.anno,
		mese      = fields.mese,
		giorno    = fields.giorno,
		circa     = fields.circa or false,
		incerta   = fields.incerta or false,
		fine      = fields.fine,
		stellare  = fields.stellare,
		voce      = fields.voce,
		fonte     = fields.fonte,
		nota      = fields.nota,
		gruppo    = fields.gruppo,
		categoria = fields.categoria,
		stato     = fields.stato,
		ordine    = fields.ordine,
	}
end

--[[
compare_dates_only(a, b)
-------------------------
Private helper. Compares two date-shaped tables (anything with
anno/mese/giorno fields -- this works equally for full entries and for
the plain date tables produced by Module:ChronoSpine/date) using only
those three fields, treating a missing month or day as "before" any
specified value.

This matches how a partial date is naturally read: "2022" means
"sometime in 2022", which comes before "2022-05" ("sometime in May
2022"), which comes before a fully-specified "2022-05-05".

Note: this logic is intentionally duplicated in
Module:ChronoSpine/date (as its own private helper, used there to
validate that a range's end is not earlier than its start). The two
copies are small and conceptually belong to different concerns --
"how do full entries sort" here, "is this range well-formed" there --
so we are not yet introducing a shared /util submodule for four lines
of code. This can be revisited if a third caller appears.

Returns:
  -1 if `a` sorts before `b`
   1 if `a` sorts after `b`
   0 if they are equal at this level (the caller must break the tie
     using some other field, e.g. stardate or input order)
--]]
local function compare_dates_only(a, b)
	local a_anno, b_anno = a.anno or 0, b.anno or 0
	if a_anno ~= b_anno then
		return (a_anno < b_anno) and -1 or 1
	end

	local a_mese, b_mese = a.mese or 0, b.mese or 0
	if a_mese ~= b_mese then
		return (a_mese < b_mese) and -1 or 1
	end

	local a_giorno, b_giorno = a.giorno or 0, b.giorno or 0
	if a_giorno ~= b_giorno then
		return (a_giorno < b_giorno) and -1 or 1
	end

	return 0
end

--[[
M.compare(a, b)
----------------
The sort comparator for normalised entries. Implements the sort key
from the briefing (sec. 4.3):

    year -> month -> day -> stardate -> input order

with all `incerta` ("?", wholly uncertain) entries placed together as
a final block, in their own input order, per section 6.2.

Meant to be passed straight to Lua's `table.sort`:

    table.sort(entries, M.compare)

Parameters:
  a, b (table) -- two normalised entries, as produced by M.new.

Returns:
  (boolean) -- true if `a` must sort strictly before `b`.
--]]
function M.compare(a, b)
	-- Rule 1: uncertain entries form their own trailing block.
	-- Within that block there is no date to compare by, so they keep
	-- only their relative input order.
	if a.incerta ~= b.incerta then
		-- Exactly one of the two is uncertain: whichever one is NOT
		-- uncertain sorts first. `b.incerta` is true exactly when
		-- `b` is the uncertain one, which is exactly when `a` (the
		-- certain one) must come first -- so this doubles as the
		-- return value we want.
		return b.incerta
	end
	if a.incerta then
		-- Both uncertain: same block, order preserved by `ordine`.
		return (a.ordine or 0) < (b.ordine or 0)
	end

	-- Rule 2: year -> month -> day.
	local date_cmp = compare_dates_only(a, b)
	if date_cmp ~= 0 then
		return date_cmp < 0
	end

	-- Rule 3: stardate, strictly as a tie-breaker. We only ever reach
	-- this point when year/month/day are already equal, which is
	-- exactly what the briefing means by "stardate is a tie-breaker
	-- within a year, never a substitute for the date". If either
	-- entry has no stardate at all, this level contributes nothing
	-- and we fall through to input order -- we do not invent an
	-- ordering for "has a stardate" vs. "does not".
	if a.stellare ~= nil and b.stellare ~= nil and a.stellare ~= b.stellare then
		return a.stellare < b.stellare
	end

	-- Rule 4: final, stable tie-break on input order.
	return (a.ordine or 0) < (b.ordine or 0)
end

return M