Modulo:ChronoSpine/stardate

Da Wikitrek.
Vai alla navigazione Vai alla ricerca

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

--[[
Module:ChronoSpine/stardate
=============================

Purpose
-------
Parses the raw wikitext string an editor types into a `stellare`
parameter into a single normalised numeric stardate value.

Stardates are entirely independent of the real/partial date parsed by
Module:ChronoSpine/date: per the project briefing they are only ever
used as an in-year tie-breaker for sorting, never as a substitute for
a real date. Accordingly this module has NO dependency on
Module:ChronoSpine/date, matching the same "these two axes never
depend on each other" principle the briefing applies to `tempo` and
`fonte`.

Accepted formats
-----------------
  Plain number             4789.6
  Comma as decimal point   4789,6      (normalised to a dot)
  With a display prefix    DS 4789.6   (prefix stripped; case-
                            SD4789.6   insensitive; "DS "/"SD " both
                                       accepted since different
                                       fandoms/eras use either)

The "DS" shown to readers ("DS 4789.6") is added back at render time
from /i18n -- it is a display convention, not part of the stored
value, so it is stripped here rather than kept.

Return value convention
------------------------
Same convention as Module:ChronoSpine/date, for consistency:

  On success:  { ok = true, value = <number> }
  On failure:  { ok = false, raw = "<original string>", code = "<error code>" }

  `code` is one of: "empty", "bad-format".
--]]

local M = {}

--[[
trim(text)
-----------
Private helper. Strips leading and trailing whitespace.
--]]
local function trim(text)
	return text:match("^%s*(.-)%s*$")
end

--[[
strip_prefix(text)
--------------------
Private helper. Removes a leading "DS" or "SD" display prefix
(case-insensitive, with optional following whitespace), if present.
Returns just the remainder in that case -- if there was no prefix,
`text` is returned unchanged.
--]]
local function strip_prefix(text)
	local remainder = text:match("^[Dd][Ss]%s*(.+)$")
	if remainder then
		return remainder
	end
	remainder = text:match("^[Ss][Dd]%s*(.+)$")
	if remainder then
		return remainder
	end
	return text
end

--[[
M.parse(raw)
-------------
Parses one raw stardate string into a normalised numeric value.

Parameters:
  raw (string|nil) -- the raw `stellare` parameter value, exactly as
    the editor typed it.

Returns:
  (table) -- either { ok = true, value = <number> } or
             { ok = false, raw = raw, code = "..." }.
--]]
function M.parse(raw)
	if raw ~= nil and type(raw) ~= "string" then
		raw = tostring(raw)
	end

	if raw == nil then
		return { ok = false, raw = raw, code = "empty" }
	end

	local text = trim(raw)
	if text == "" then
		return { ok = false, raw = raw, code = "empty" }
	end

	text = trim(strip_prefix(text))
	if text == "" then
		-- The prefix was the entire string ("DS" with nothing after it).
		return { ok = false, raw = raw, code = "bad-format" }
	end

	-- Accept a comma as the decimal separator (common when editors
	-- copy numbers typed with Italian formatting) by normalising it
	-- to a dot -- but only when there is no dot already present, so
	-- we never silently mangle a string that is malformed some other
	-- way (e.g. "4.789,6").
	if not text:find("%.", 1, true) then
		text = text:gsub(",", ".")
	end

	-- After normalisation, the value must be a plain non-negative
	-- decimal number: one or more digits, optionally a dot and more
	-- digits.
	if not text:match("^%d+%.?%d*$") then
		return { ok = false, raw = raw, code = "bad-format" }
	end

	local value = tonumber(text)
	if not value then
		return { ok = false, raw = raw, code = "bad-format" }
	end

	return { ok = true, value = value }
end

return M