وحدة:SST
يمكن إنشاء صفحة توثيق الوحدة في وحدة:SST/شرح
require('strict')
local p = {}
--[[--------------------------< H O S T _ M A P >--------------------------------------------]]
local host_map = {
['ia'] = { engine = 'IA', shard_key = 'ia' },
['internetarchive'] = { engine = 'IA', shard_key = 'ia' },
['hathi'] = { engine = 'Hathi', shard_key = 'hathi' },
['hathitrust'] = { engine = 'Hathi', shard_key = 'hathi' },
['guten'] = { engine = 'Gutenberg', shard_key = 'guten' },
['gutenberg'] = { engine = 'Gutenberg', shard_key = 'guten' },
['wikisrc'] = { engine = 'Wikisource', shard_key = 'wikisrc' },
['wikisource'] = { engine = 'Wikisource', shard_key = 'wikisrc' },
['gbook'] = { engine = 'GBook', shard_key = 'gbook' },
['googlebooks'] = { engine = 'GBook', shard_key = 'gbook' },
['web'] = { engine = 'Web', shard_key = 'web' },
['physical'] = { engine = 'Physical', shard_key = 'physical' }
}
--[[--------------------------< P A R A M E T E R M A P >----------------------------------]]
-- SSTS Architecture Note:
-- The parameter_map is designed specifically for sub-divisions of a single work
-- (like individual chapters or dictionary entries).
-- To maintain the Single-Source Template (SSTS) philosophy, core identity
-- parameters like 'title', 'year', or 'isbn' are managed at the variant
-- level (creating a new library entry) rather than through this map.
local parameter_map_guardrail = {
['chapter'] = true,
['entry'] = true,
['article'] = true,
['section'] = true,
['part'] = true
}
local function apply_parameter_map(user_args, book_data, library)
-- Follow the pointer if an alias exists
local map_source = book_data
if book_data.parameter_map_alias and library and library[book_data.parameter_map_alias] then
map_source = library[book_data.parameter_map_alias]
end
if not map_source.parameter_map then return end
for param, map_table in pairs(map_source.parameter_map) do
if parameter_map_guardrail[param] then
local user_input = user_args[param]
if user_input then
-- Trim whitespace so "|chapter= 8 " matches key ['8']
local clean_input = mw.text.trim(tostring(user_input))
if map_table[clean_input] then
user_args[param] = map_table[clean_input]
end
end
end
end
end
--[[--------------------------< H E L P E R S >----------------------------------------------]]
local function error_msg(msg, tracking_category)
local err_text = string.format('<strong class="error">SSTS Error: %s</strong>', msg)
local title = mw.title.getCurrentTitle()
if title and (title.namespace == 0 or title.namespace == 118) then
if tracking_category then
return err_text .. '[[Category:' .. tracking_category .. ']]'
else
return err_text .. '[[Category:SSTS errors]]'
end
end
return err_text
end
--[[--------------------------< C O R E _ E X E C U T I O N >--------------------------------]]
-- Shared function to build the citation regardless of how it was routed
local function build_citation(frame, book_data, target_host, library)
if not target_host then return error_msg("No host defined for this record.") end
-- 1. Route to the correct Host/Engine
local host_info = host_map[string.lower(mw.text.trim(target_host))]
if not host_info then return error_msg("Unsupported host '" .. target_host .. "'") end
-- 2. Validate Host Data within the record
local host_data = book_data.hosts and book_data.hosts[host_info.shard_key]
if not host_data then
return error_msg("Host '" .. host_info.shard_key .. "' is not defined for this book.")
end
-- 3. Load the Hosts module
local success_engine, hosts_module = pcall(require, 'Module:SST/hosts')
if not success_engine then
return error_msg("Could not load Module:SST/hosts.")
end
local engine = hosts_module[host_info.engine]
if not engine then
return error_msg("Engine '" .. host_info.engine .. "' not found in hosts module.")
end
-- 4. Process Arguments & Apply ignore_args Filter
local ignore_list = {
['ignore_args'] = true,
['id'] = true,
['key'] = true,
['default'] = true
}
if frame.args['ignore_args'] then
for key in string.gmatch(frame.args['ignore_args'], '([^,]+)') do
ignore_list[mw.text.trim(key)] = true
end
end
local combined_args = {}
-- Grab parent args (user input), skipping ignored keys
for k, v in pairs(frame:getParent().args) do
if type(k) == 'string' and not ignore_list[k] then
combined_args[k] = v
elseif type(k) == 'number' and not ignore_list[tostring(k)] then
combined_args[k] = v
end
end
-- Grab current frame args (template overrides like our generated 'chapter')
for k, v in pairs(frame.args) do
if type(k) == 'string' and not ignore_list[k] then
combined_args[k] = v
end
end
-- ==========================================================
-- 4.5 APPLY PARAMETER MAP (Translate shorthand into full titles)
-- ==========================================================
apply_parameter_map(combined_args, book_data, library)
local citeArgs = {}
for k, v in pairs(book_data.cite_params or {}) do
citeArgs[k] = v
end
-- ==========================================================
-- 4.7 SILENT LOGIC TRAP (Inspect raw shard data for faults)
-- ==========================================================
local has_logic_fault = false
local t_type = citeArgs['_template'] or 'cite book'
if t_type == 'cite encyclopedia' then
-- Encyclopedia shards must use 'encyclopedia', not 'title' or specific entries.
-- They also cannot contain book or journal container names.
if citeArgs['title'] or citeArgs['title-link'] or citeArgs['article'] or citeArgs['entry'] then
has_logic_fault = true
elseif citeArgs['journal'] or citeArgs['magazine'] or citeArgs['work'] then
has_logic_fault = true
end
elseif t_type == 'cite journal' then
-- Journal shards must have a journal container.
-- They cannot be locked to specific chapters, nor contain book/encyclopedia containers.
if not citeArgs['journal'] and not citeArgs['work'] and not citeArgs['magazine'] then
has_logic_fault = true
end
if citeArgs['chapter'] or citeArgs['encyclopedia'] then
has_logic_fault = true
end
elseif t_type == 'cite book' then
-- Book shards cannot contain encyclopedia or journal container names.
if citeArgs['encyclopedia'] or citeArgs['journal'] or citeArgs['magazine'] or citeArgs['work'] then
has_logic_fault = true
end
end
-- 5. Pass execution to the Hosts module for parsing and link-building
if type(hosts_module.process) == "function" then
citeArgs = hosts_module.process(engine, host_data, citeArgs, combined_args)
else
return error_msg("System Error: hosts_module.process is missing.")
end
-- 6. Render standard Cite Book/Encyclopedia template
local template_name = citeArgs['_template'] or 'cite book'
citeArgs['_template'] = nil
if citeArgs['ref'] and mw.ustring.match(citeArgs['ref'], '^{{') then
local pre_ref = frame:preprocess(citeArgs['ref'])
citeArgs['ref'] = mw.ustring.gsub(pre_ref, " ", "_")
end
local citation = frame:expandTemplate{ title = template_name, args = citeArgs }
-- 7. Catch CS1/CS2 errors and inject silent tracking categories
local current_page = mw.title.getCurrentTitle()
if current_page and (current_page.namespace == 0 or current_page.namespace == 118) then
-- Catch native CS1 red errors
local has_cs1_error = string.find(citation, "cs1%-visible%-error") or
string.find(citation, "cs1%-hidden%-error") or
string.find(citation, "Category:CS1 errors")
if has_cs1_error then
citation = citation .. "[[Category:SSTS errors]]"
end
-- Catch parameter logical faults
if has_logic_fault then
citation = citation .. "[[Category:SSTS parameter logic faults]]"
end
end
return citation
end
--[[--------------------------< T H E B R I D G E >----------------------------------------]]
-- LEGACY ROUTER: Supports existing live templates. DO NOT DELETE until Phase 4.
function p.main(frame)
local raw_host = frame.args[1]
local book_key = frame.args[2]
if not raw_host then return error_msg("No host specified in #invoke.") end
if not book_key then return error_msg("No book key specified in #invoke.") end
local shard_letter = mw.ustring.upper(mw.ustring.sub(mw.text.trim(book_key), 1, 1))
local success_shard, library = pcall(mw.loadData, 'Module:SST/shards/' .. shard_letter)
if not success_shard then return error_msg("Could not load shard module '" .. shard_letter .. "'.") end
local book_data = library[mw.text.trim(book_key)]
if not book_data then return error_msg("Book key '" .. book_key .. "' not found.", "SSTS missing records") end
-- Smart Host Fallback
local final_host = book_data.host
if not final_host then
-- Fallback 1: Try to grab the first available host from the hosts table
if book_data.hosts and type(book_data.hosts) == 'table' then
for k, _ in pairs(book_data.hosts) do
final_host = k
break
end
end
-- Fallback 2: If still nothing, treat it as a physical book
if not final_host then
final_host = 'physical'
end
end
local output = build_citation(frame, book_data, final_host, library)
-- Only categorize in Main (0) and Template (10) namespaces to ignore sandboxes
local ns = mw.title.getCurrentTitle().namespace
if ns == 0 or ns == 10 then
output = output .. "[[Category:SSTS errors|*LEGACY]]"
end
return output
end
--[[--------------------------< A R C H I T E C T U R E >----------------------------]]
-- ROUTER: Single A-Z Books
function p.single(frame)
local base_id = frame.args.id
if not base_id then return error_msg("No id specified in #invoke.") end
local shard_letter = mw.ustring.upper(mw.ustring.sub(mw.text.trim(base_id), 1, 1))
local success_shard, library = pcall(mw.loadData, 'Module:SST/shards/' .. shard_letter)
if not success_shard then return error_msg("Could not load shard module '" .. shard_letter .. "'.") end
local book_data = library[mw.text.trim(base_id)]
if not book_data then return error_msg("Record '" .. base_id .. "' not found.", "SSTS missing records") end
-- Smart Host Fallback
local final_host = book_data.host
if not final_host then
if book_data.hosts and type(book_data.hosts) == 'table' then
for k, _ in pairs(book_data.hosts) do
final_host = k
break
end
end
if not final_host then final_host = 'physical' end
end
return build_citation(frame, book_data, final_host, library)
end
-- ROUTER: Multi-Volume Sets
function p.set(frame)
local base_id = frame.args.id
if not base_id then return error_msg("No id specified for the set in #invoke.") end
-- Determine target key (e.g. "1", "2", "ALL")
local raw_key = frame.args.key
local target_key
if not raw_key or mw.text.trim(raw_key) == "" then
target_key = frame.args.default or "ALL"
else
target_key = mw.text.trim(raw_key)
end
local prefix = base_id .. "_"
local lookup_id
-- Smart detection: Check if the editor accidentally passed the full book key instead of the short variant
if mw.ustring.sub(target_key, 1, mw.ustring.len(prefix)) == prefix then
lookup_id = target_key
-- Strip the prefix from target_key so error messages still look clean
target_key = mw.ustring.sub(target_key, mw.ustring.len(prefix) + 1)
else
lookup_id = prefix .. target_key
end
-- Route to dedicated /sets/ module
local success_shard, library = pcall(mw.loadData, 'Module:SST/shards/sets/' .. base_id)
if not success_shard then return error_msg("Could not load set module for '" .. base_id .. "'.") end
local book_data = library[lookup_id]
if not book_data then return error_msg("Part '" .. target_key .. "' not found in set '" .. base_id .. "'.", "SSTS missing records") end
-- Smart Host Fallback
local final_host = book_data.host
if not final_host then
if book_data.hosts and type(book_data.hosts) == 'table' then
for k, _ in pairs(book_data.hosts) do
final_host = k
break
end
end
if not final_host then final_host = 'physical' end
end
return build_citation(frame, book_data, final_host, library)
end
return p