Module:SOM.meta.Page
From SunshinePPS Wiki
Documentation for this module may be created at Module:SOM.meta.Page/doc
require("Module:No globals")
local References = require("Module:SOM.util.References")
local StringBuffer = require("Module:SOM.util.StringBuffer")
local util = require("Module:SOM.util")
local function compute_renderer(specifier, class)
if type(specifier) == "function" then
return specifier
elseif class and class[specifier] then
return class[specifier]
elseif class and class.render then
return class.render
else
return mw.dumpObject
end
end
local p = {}
function p.generate_page(args)
local page_class = args.class_name
local parent_categories = args.parent_categories
local category_description = args.category_description
local attributes = args.attributes
local form_layout = args.form_layout
local page_layout = args.page_layout
local class_prefix = args.class_prefix or "SOM.Page."
local naming_advice = args.naming_advice
local page_form = page_class
local page_category = page_class
local page_template = class_prefix .. page_class
local page_module = class_prefix .. page_class
local describe_page_form
local produce_page_form
local describe_page_template
local expand_page_template
local page = {}
page.docs = {}
-- page.module_name = page_module
function page.docs.docs()
return {
module_name = page_module,
docstring = mw.ustring.format([==[
This module defines the class <code>%s</code> which defines
pages in the category [[:Category:%s]].
%s
]==], page_class, page_category, category_description),
}
end
function page.page_category(frame)
frame:callParserFunction("#default_form", {page_form})
local buffer = StringBuffer.new()
buffer
:add('<div style="background-color: #EEEE88; padding: 0.5em 0.5em; margin: 1em 3em">'):nl()
:add_uformat("To create or edit a ''%s'' page, type in a name below. ",
page_category)
if naming_advice then
buffer:add_uformat("%s ", naming_advice)
end
buffer
:add(frame:callParserFunction("#forminput",
{"", form = page_form,
["autocomplete on category"] = page_category})):nl()
:add("</div>"):nl()
:nl()
:add(category_description):nl()
:add("----"):nl()
:add('<div style="font-size: 85%">')
:add("Technical Details<br>"):nl()
:add("Pages in this category are:<ul>"):nl()
:add_uformat("<li>defined by [[Module:%s]];</li>", page_module):nl()
:add_uformat("<li>instantiated by [[Template:%s]];</li>", page_template):nl()
:add_uformat("<li>created and edited using [[Form:%s]].</li>", page_form):nl()
:add("</ul>"):nl()
:add("</div>"):nl()
:nl()
for i, c in ipairs(parent_categories) do
buffer:add_uformat("[[Category:%s]]", c):nl()
end
return buffer:output()
end
function page.docs.page_category()
return {
desc = mw.ustring.format("Generates the definition for this class' category, [[:Category:%s]]. Typically, invoked by <code>SOM.util.auto_generate_page_category</code>",
page_category),
args = {},
needs_frame = true,
}
end
function page.page_form(frame)
local parent = frame:getParent()
local parent_title = frame:getParent():getTitle()
local current_title = mw.title.getCurrentTitle().fullText
parent_title = nil
-- if parent_title == current_title then
if mw.ustring.match(current_title, "^Form:") ~= nil then
return describe_page_form(frame)
.. "\n\n<hr>\n\n<pre>" .. frame:callParserFunction('#tag:nowiki', produce_page_form()) .. "</pre>"
else
return produce_page_form()
end
end
function page.docs.page_form()
return {
desc = mw.ustring.format("Generates the definition for this class' form, [[Form:%s]]. Typically, invoked by <code>SOM.util.auto_generate_page_form</code>",
page_form),
args = {},
needs_frame = true,
}
end
function page.page_template(frame)
local parent = frame:getParent()
local parent_title = frame:getParent():getTitle()
local current_title = mw.title.getCurrentTitle().fullText
if parent_title == current_title then
return describe_page_template()
else
return expand_page_template(parent)
end
end
function page.docs.page_template()
return {
desc = mw.ustring.format("Generates the definition for the template which instantiates this class, [[Template:%s]]. Typically, invoked by <code>SOM.util.auto_generate_page_template</code>",
page_template),
args = {},
needs_frame = true,
}
end
if false then
local arg_list = {}
local sorted = {}
for k, v in pairs(attributes) do
table.insert(sorted, k)
end
table.sort(sorted)
for _, k in ipairs(sorted) do
local attr = attributes[k]
local desc
if attr.class.is_composite() then
desc = mw.ustring.format(
"JSON-encoded data for zero or more [[Module:%s|<code>%s</code>]]",
attr.class.module_name, attr.class.module_name)
else
desc = mw.ustring.format(
"Data for [[Module:%s|<code>%s</code>]]",
attr.class.module_name, attr.class.module_name)
end
table.insert(arg_list, { attr.arg, desc })
end
return {
desc = mw.ustring.format("Implements the template for this class, [[Template:%s]]. Typically, invoked by <code>SOM.util.auto_generate_page_template</code>",
page_template),
args = arg_list,
needs_frame = true,
}
end
function describe_page_form(frame)
return StringBuffer.new()
:add_uformat("This is the ''%s'' form.", page_category):nl()
:add([==[
To create a page with this form, enter the page name below;
if a page with that name already exists, you will be sent to a form to edit that page.
]==])
:add(frame:callParserFunction("#forminput",
{"",
form = page_form,
["autocomplete on category"] = page_category}))
:output()
end
function produce_page_form()
local FormTable = require("Module:SOM.util.FormTable")
local buffer = StringBuffer.new()
-- Start with boilerplate.
buffer
:add("__NOEDITSECTION__"):nl()
:add("__NOTOC__"):nl()
:add_uformat(
"{{{info|create title=Create a new %s|edit title=Edit a %s}}}",
page_category, page_category):nl()
:add([===[
<div id="wikiPreview" style="display: none; padding-bottom: 25px; margin-bottom: 25px; border-bottom: 1px solid #AAAAAA;"></div>
]===])
-- Construct the form template for the page itself.
buffer:add_uformat("{{{for template|%s}}}", page_template):nl()
for _, chunk in ipairs(form_layout) do
if chunk.section2 then
buffer:add_uformat("== %s ==", chunk.section2):nl()
elseif chunk.section3 then
buffer:add_uformat("=== %s ===", chunk.section3):nl()
elseif chunk.section4 then
buffer:add_uformat("==== %s ====", chunk.section4):nl()
elseif chunk.section5 then
buffer:add_uformat("===== %s =====", chunk.section5):nl()
elseif chunk.attribute then
local attr = attributes[chunk.attribute]
buffer:add(attr.class.form_field{ field_name = attr.arg }):nl()
elseif chunk.table then
local ft = FormTable.new()
for _, row in ipairs(chunk.table) do
local attr = attributes[row.attribute]
ft:row(attr.label .. ":", attr.class.form_field{ field_name = attr.arg })
end
buffer:add(ft):nl():nl()
else
util.assertf(false, "What does this chunk do? %s", mw.dumpObject(chunk))
end
end
buffer:add("{{{end template}}}"):nl()
-- Add embedded form templates.
for key, attr in pairs(attributes) do
if attr.class.is_composite() then
buffer:add(attr.class.form_entry{
embed_in = mw.ustring.format("%s[%s]", page_template, attr.arg),
hide = attr.link_to_key
}):nl()
end
end
return buffer:output()
end
function describe_page_template()
local buffer = StringBuffer.new()
:add("This template is used to instantiate the ")
:add_uformat("[[Module:%s|<code>%s</code>]]", page_module, page_module)
:add("class, creating pages in the ")
:add_uformat("[[:Category:%s|%s]]", page_category, page_category)
:add(" category.")
:nl():nl()
:add("This template has been automatically generated by the class.")
:nl():nl()
:add("It takes these parameters:"):nl()
-- TODO maddog get template docs from somewhere
-- for _, argdesc in ipairs(page.docs.instantiate().args) do
-- local arg, desc = unpack(argdesc)
-- buffer:add_uformat("* <code>%s</code> - %s", arg, desc):nl()
-- end
return buffer:output()
end
function expand_page_template(frame)
-- PageForms docs recommend calling {{#default_form|...}} in the page's
-- defining Category... but in practice that seems to be brittle in the
-- face of errors, probably due to caching issues. So, we just execute
-- it on every page, before anything else might get in the way.
frame:callParserFunction("#default_form", {page_form})
-- -------------------------------------------------
-- Process all input data supplied by template, and capture errors
-- -------------------------------------------------
local i_ = {}
local we_have_missing_data = false
local ec = require("Module:SOM.util.ErrorCapture").new()
for k, v in pairs(attributes) do
-- (Templates/etc supplied in template parameters may not be expanded
-- until the value is dereferenced/accessed.)
local param = frame.args[v.arg]
if v.class == nil then
i_[k] = param
elseif v.class.is_composite() then
local override_values = {}
if v.link_to_key then
override_values[v.link_to_key] = mw.title.getCurrentTitle().fullText
end
local _, instances, missing, sub_ec =
ec:pcall(v.class.decode_and_attach, param or "", override_values)
if instances then
i_[k] = instances
we_have_missing_data = we_have_missing_data or missing
ec:add_ec(sub_ec)
end
else -- not composite => simple
-- Call .attach() methods for simple values, and capture any errors.
local _, i = ec:pcall(v.class.attach,
{ value = param, empty_as = "missing" })
i_[k] = i and i.value
we_have_missing_data = we_have_missing_data or (not i)
end
end
--local documents = AttachedFile.fetch_all({page_as = "page"})
-- -------------------------------------------------
-- Produce the page
-- -------------------------------------------------
local buffer = StringBuffer.new()
-- Start with boilerplate.
buffer
:add("__NOEDITSECTION__"):nl()
-- TODO maddog Should missing district be considered an /error/ or just
-- missing datum?
:add_and_nl(ec)
-- Add content.
for _, chunk in ipairs(page_layout) do
if chunk.infobox then
-- In MediaWiki:Common.css, we have disabled the "float: right"
-- built-in to Capiunto's infobox CSS, so that we can use the
-- responsive float utility from Bootstrap here. This way, on
-- narrow (mobile) screens, we don't try to float at all since
-- there isn't enough room anyway.
local ib = require("capiunto").create{ bodyClass = "wikitable float-md-right mx-3" }
for _, row in ipairs(chunk.infobox) do
if row.section then
ib:addHeader(row.section)
elseif row.attribute then
local attr = attributes[row.attribute]
local label = row.label or attr.label
local renderer = compute_renderer(row.render, attr.class)
ib:addRow(label,
renderer(i_[row.attribute], frame, row.render_args))
-- if row.render then
-- ib:addRow(attr.label, row.render(i_[row.attribute], frame))
-- elseif attr.class.render then
-- ib:addRow(attr.label, attr.class.render(i_[row.attribute]))
-- else
-- ib:addRow(attr.label, mw.dumpObject(i_[row.attribute]))
-- end
elseif row.render_any then
local label, value = row.render_any(i_, frame, row.render_args)
if label ~= nil then
ib:addRow(label, value)
end
else
util.assertf(false, "Unknown infobox row %s", tostring(row and next(row)))
end
end
buffer:add(ib:getHtml()):nl()
elseif chunk.section then
buffer:add_uformat("\n=== %s ===", chunk.section):nl()
elseif chunk.section2 then
buffer:add_uformat("\n== %s ==", chunk.section2):nl()
elseif chunk.section3 then
buffer:add_uformat("\n=== %s ===", chunk.section3):nl()
elseif chunk.section4 then
buffer:add_uformat("\n==== %s ====", chunk.section4):nl()
elseif chunk.section5 then
buffer:add_uformat("\n===== %s =====", chunk.section5):nl()
elseif chunk.attribute then
local attr = attributes[chunk.attribute]
local renderer = compute_renderer(chunk.render, attr.class)
buffer:add(renderer(i_[chunk.attribute], frame, chunk.render_args)):nl()
elseif chunk.wikitext then
buffer:add(chunk.wikitext):nl()
elseif chunk.references then
buffer:add(References.emit_references(frame)):nl()
elseif chunk.renderer then
chunk.renderer(i_, buffer, frame)
elseif chunk.fact_table then
buffer:add("FACT TABLE"):nl():nl()
end
end
-- Add categories.
buffer
:add_uformat("[[Category:%s]]", page_category):nl()
:nl()
if we_have_missing_data then
buffer:add(util.MISSING_DATA_CATEGORY_TAG):nl()
end
if not ec:is_empty() then
buffer:add(util.BROKEN_PAGE_CATEGORY_TAG):nl()
end
return buffer:output()
end
return page
end
return p