Module:QualityTable

From Fallen London Wiki

Documentation for this module may be created at Module:QualityTable/doc

-- This module takes a quality as an argument, and returns a table comparing
-- all the actions that gain that quality in some fashion.

-- Unfortunately, there's no way (that I can find) to expand the members of a
-- category from within Lua. However, we *can* find out the *count* of the
-- members, and we can also expand the category *onto the rendered page* using
-- <categorytree>. So the overall strategy is to hold a pre-rendered table
-- as the "data" argument, and do a lightweight check to see if the count
-- of rows matches the number of elements in the category.

-- If the counts don't match, we prompt the user to begin the update process.
-- This consists of several rounds of copying the rendered output from the
-- preview screen over the template. This serves as a sort of "poor man's
-- recursion," allowing us to work around limitations like the inability to
-- access the output of <categorytree> and limits on the number of calls to
-- frame:getContent().

-- Thus, this module/template doesn't act like a normal template, but more
-- like a table that has a built-in way of updating itself quickly.

-- That said, this is very much work-in-progress, and doesn't function
-- correctly yet.

local p = {}
-- Maximum # of expensive calls we'll make, generally to frame:getContent().
-- If we go over the limit of 100 (on the whole page), the script will be
-- aborted with no ability to trap the error. We leave some extra room to
-- allow for other expensive functions on the page.
local EXPENSIVE_CALL_MAX = 90

-- Useful helper for formatting error text.
function format_error(err)
    return "<span style='color:red'>'''" .. err .. "'''</span>"
end

-- This wiki's version of Scribunto isn't very up to date, use this to find out
-- what functions are available. TODO: Delete when everything's finished.
function table_keys(tab)
    local keys = {}
    local n = 0
    for k, v in pairs(tab) do
        n = n + 1
        keys[n] = "*" .. k .. "\n"
    end
    return table.concat(keys, "")
end

-- Returns the string to prepend before the table, informing the user how
-- to update (if it is time to do so).
function get_update_text(current_count, true_count)
    if current_count == true_count then
        return ""
    end
    return string.format([[<div class="mw-collapsible mw-collapsed">
''This table is out of date: It has %d rows vs. %d members of the category.''
<div class="mw-collapsible-content">
To get rid of this message, edit this page using the source editor and add
update=1 as an argument to the template. Then '''preview''' the page and follow
the instructions: you'll have to replace the template with what it generates a
couple of times before you can submit.
</div></div>]],
        current_count,
        true_count)
end

function get_merged_args(frame)
    local args = {}
    for k, v in pairs(frame:getParent().args) do
        args[k] = v
    end
    for k, v in pairs(frame.args) do  -- Override with local arguments
        args[k] = v
    end
    return args
end

function handle_update(frame, args, category_name)
    -- The list of valid arguments (arguments we will pass along), in
    -- the order we'll write them.
    local valid_args = {"update", "name", "expensive_calls",
        "num_processed", "data", "actions"}
    -- Figure out where we're at in the update process.
    if (args["actions"] or "") == "" then
        -- No actions, fill out <categorytree> and also reread content for as
        -- many rows as we can.
        -- It's OK to modify args, since handle_update is a tail-call.
        args["actions"] = frame:preprocess(
            '<categorytree mode="pages" hideroot="on">' ..
                category_name .. "</categorytree>")
    else
        -- Use num_processed to figure out which rows are already processed,
        -- reconcile that against the action list, read content for new rows,
        -- and possibly clean up args if we're done now.
        local referenced_actions = mw.text.split(args["actions"], "\n", true)
        local result_table = {}
        for i, v in ipairs(referenced_actions) do
            result_table[i] = mw.title.new(v):getContent()
        end
        args["data"] = frame:preprocess(
            "<nowiki>" .. table.concat(result_table, "\n") .. "</nowiki>")
    end
    local flattened_args = {}
    for i, k in ipairs(valid_args) do
        if args[k] then
            table.insert(flattened_args, k .. "=" .. args[k])
        end
    end
    return string.format([[
'''YOU ARE NOT DONE YET!''' Replace the template you are editing with the
code that appears below. You may have to repeat this procedure multiple times.

This is step %d/%d. '''XXXXX BEGIN COPY-PASTE XXXXX'''

{{#invoke:QualityTable|main|%s}}

'''XXXXX END COPY-PASTE XXXXX''']],
        1, 2, table.concat(flattened_args, "|"))
end

-- Wrap the real main in an error handler so we get useful error messages
-- when things go wrong.
function p.main(frame)
    success, result = pcall(p.raw_main, frame)
    if success then
        return result
    else
        return format_error("Script error:" .. result)
    end
end

-- The true main function. You can substitute this directly for main, but don't.
function p.raw_main(frame)
    local args = get_merged_args(frame)
    EXPENSIVE_CALL_MAX = args["expensive_calls"] or EXPENSIVE_CALL_MAX

    local quality_item_name = args["name"]
    if quality_item_name == nil then
        return format_error("Module:QualityTable requires a name= argument!")
    end

    local bare_category_name = quality_item_name .. " Sources"
    local category_name = "Category:" .. bare_category_name
    
    if args["update"] ~= nil then
        -- Abort further processing
        return handle_update(frame, args, category_name)
    end
    
    local num_in_category = mw.site.stats.pagesInCategory(bare_category_name, "pages")

    return get_update_text(#referenced_actions, num_in_category) ..
        frame:preprocess(
            "<pre><nowiki>\n" .. (args[data] or "") .. "\n</nowiki></pre>")
end

return p