Module: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

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.