Module:FR:RailwaySignalState

From OpenStreetMap Wiki
Jump to navigation Jump to search

local p = {}

-- Signal shapes mapped to signal widths
local signalWidths = {
    A = "60",
    C = "60",
    F = "120",
    H = "120",
    R = "120",
    K = "46"
}

-- Signal types mapped to plate and signal head codes.
-- The category (main or distant) is implicit: any head whose shape letter is
-- "R" belongs to the "distant" category; all other shapes belong to "main".
local signalTypes = {
    CARRE = { plate="NF", heads = { "C1", "F1", "H1" }},
    CV    = { plate="NF", heads = { "A2", "C2", "F2", "H2", "K2" }},
    S     = { plate="F",  heads = { "A1", "C1", "F1", "H1", "K1" }},
    D     = { plate="D",  heads = { "R6" }},
    A     = { plate="A",  heads = { "R1" }}
}

-- Return the signal type key for a given signal head code (e.g. "C1" -> "CARRE")
local function getSignalType(head)
    for typeName, signalData in pairs(signalTypes) do
        for _, h in ipairs(signalData.heads) do
            if h == head then
                return typeName
            end
        end
    end
    return nil  -- Return nil if head code not found
end

-- State display names and the exhaustive list of heads that can display each
-- state, covering both main (*1, *2) and distant (R*) heads.
-- Within each state, heads are ordered by shape (A, C, F, H, K for main;
-- R for distant), with *1 heads before *2 heads within the same shape.
-- The "category" parameter in render() filters which subset is displayed.
local stateInfo = {
    C = {
        fullname = "Carré",
        heads    = { "C1", "F1", "H1" }
    },
    CV = {
        fullname = "Carré violet",
        heads    = { "A2", "C2", "F2", "H2", "K2" }
    },
    D = {
        fullname = "Disque",
        heads    = { "R6" }
    },
    S = {
        fullname = "Sémaphore",
        heads    = { "A1", "C1", "F1", "H1", "K1", "A2", "C2", "F2", "H2", "K2" }
    },
    ["(S)"] = {
        fullname = "Feu rouge clignotant",
        heads    = { "A1", "C1", "F1", "H1", "K1", "A2", "C2", "F2", "H2", "K2" }
    },
    A = {
        fullname = "Avertissement",
        heads    = { "A1", "C1", "F1", "H1", "C2", "F2", "H2", "R1", "R6" }
    },
    ["(A)"] = {
        fullname = "Feu jaune clignotant",
        heads    = { "A1", "C1", "F1", "H1", "C2", "F2", "H2", "R1", "R6" }
    },
    R = {
        fullname = "Ralentissement 30",
        heads    = { "F1", "H1", "F2", "H2", "R6" }
    },
    ["R+A"] = {
        fullname = "Ralentissement 30 et Avertissement",
        heads    = { "F1", "H1", "F2", "H2", "R6" }
    },
    ["R+(A)"] = {
        fullname = "Ralentissement 30 et Feu jaune clignotant",
        heads    = { "F1", "H1", "F2", "H2", "R6" }
    },
    ["(R)"] = {
        fullname = "Ralentissement 60",
        heads    = { "F1", "H1", "F2", "H2", "R6" }
    },
    ["(R)+A"] = {
        fullname = "Ralentissement 60 et Avertissement",
        heads    = { "F1", "H1", "F2", "H2", "R6" }
    },
    ["(R)+(A)"] = {
        fullname = "Ralentissement 60 et Feu jaune clignotant",
        heads    = { "F1", "H1", "F2", "H2", "R6" }
    },
    RR = {
        fullname = "Rappel 30",
        heads    = { "H1", "H2" }
    },
    ["RR+A"] = {
        fullname = "Rappel 30 et Avertissement",
        heads    = { "H1", "H2" }
    },
    ["RR+(A)"] = {
        fullname = "Rappel 30 et Feu jaune clignotant",
        heads    = { "H1", "H2" }
    },
    ["(RR)"] = {
        fullname = "Rappel 60",
        heads    = { "H1", "H2" }
    },
    ["(RR)+A"] = {
        fullname = "Rappel 60 et Avertissement",
        heads    = { "H1", "H2" }
    },
    ["(RR)+(A)"] = {
        fullname = "Rappel 60 et Feu jaune clignotant",
        heads    = { "H1", "H2" }
    },
    M = {
        fullname = "Feu blanc",
        heads    = { "A2", "C2", "F2", "H2", "K2", "C1", "F1", "H1" }
    },
    ["(M)"] = {
        fullname = "Feu blanc clignotant",
        heads    = { "A2", "C2", "F2", "H2", "K2", "C1", "F1", "H1" }
    },
    VL = {
        fullname = "Feu vert",
        heads    = { "A1", "C1", "F1", "H1", "K1", "A2", "C2", "F2", "H2", "K2", "R1", "R6" }
    },
    ["(VL)"] = {
        fullname = "Feu vert clignotant",
        heads    = { "A1", "C1", "F1", "H1", "K1", "A2", "C2", "F2", "H2", "K2", "R1", "R6" }
    }
}

-- Main function to generate content based on parameters
function p.render(frame)
    local args = frame:getParent().args
    local state = args.state
    if not state then
        return "State not specified!"
    end
    local category = args.category or "main"
    local info = stateInfo[state]
    if not info then
        return "Unknown state!"
    end

    -- Function to generate a tag with optional value and option
    local function generateTag(key, value, option)
        return "*" .. frame:expandTemplate{title = "Tag", args = {key, value or "", option or ""}}
    end

    -- Function to generate a tag value
    local function generateTagValue(key, value)
        return frame:expandTemplate{title = "TagValue", args = {key, value}}
    end

    -- Determine the image extension based on the state.
    -- MediaWiki does not support flashing lights in SVG; use animated GIFs instead.
    local ext = "svg"
    if string.find(state, "%b()") then
        ext = "gif"
    end

    -- Build the page
    local result = {}

    -- Transclude the subpage describing the state
    local subpageName = "Template:FR:RailwaySignalStateDescription/" .. info.fullname
    local titleObj = mw.title.new(subpageName)

    if titleObj and titleObj.exists then
        table.insert(result, frame:expandTemplate{title = subpageName})
    else
        table.insert(result, "The subpage for '''" .. info.fullname .. "''' does not exist.\n")
    end

    -- Insert tagging examples.
    -- Filter heads by category: distant heads have shape "R", all others are main.
    -- Within the filtered set, show one example per shape letter (first head wins).
    local signalKey = "railway:signal:" .. category
    local seenShape = {}
    local hasAny    = false

    local prefix = (state == "CV") and "Cv" or state  -- Fix for "Cv" file names

    for _, head in ipairs(info.heads) do
        local shape    = string.match(head, "^.")
        local headCat  = (shape == "R") and "distant" or "main"

        if headCat == category and not seenShape[shape] then
            seenShape[shape]  = true
            hasAny            = true
            local stateValue  = generateTagValue(signalKey .. ":states", "FR:" .. state)
            local signalType  = getSignalType(head)
            local plateValue  = generateTagValue(signalKey .. ":plates", "FR:" .. signalTypes[signalType].plate)
            local signalWidth = signalWidths[shape]
            local fileName    = prefix .. " Cible " .. head .. "." .. ext
            -- The :shape tag is omitted for distant signals: an R-shape head
            -- implicitly identifies the signal as distant, so the tag is redundant.
            local tagArgs = {
                {signalKey,              "FR:" .. signalType    },
                {signalKey .. ":plates", plateValue .. ";*"     },
                {signalKey .. ":form",   "light"                },
            }
            if category ~= "distant" then
                table.insert(tagArgs, {signalKey .. ":shape", "FR:" .. shape})
            end
            table.insert(tagArgs, {signalKey .. ":states", "", stateValue .. ";*"})

            table.insert(result, "=== " .. shape .. "-type Signal Head ===\n\n")
            table.insert(result, "[[File:" .. fileName .. "|frameless|" .. signalWidth .. "px]]\n")

            for _, tagArg in ipairs(tagArgs) do
                table.insert(result, generateTag(tagArg[1], tagArg[2], tagArg[3]) .. "\n")
            end
        end
    end

    if not hasAny then
        table.insert(result, "No signal heads available for the category \"" .. category .. "\".\n\n")
    end

    return table.concat(result)
end

return p