Module:MaroScaleSandbox: Difference between revisions

From MTG Wiki
Jump to navigation Jump to search
>Jerodast
m (funcs commented)
>Jerodast
(Remainder of comments, and some rephrasings now that I understand Lua better. Major code changes would be renaming a few vars and, what I'm especially curious about, removing the section that pre-loads all expected arguments up to MAX_ENTRIES)
Line 49: Line 49:


local sub, match, gsub = mw.ustring.sub, mw.ustring.match, mw.ustring.gsub
local sub, match, gsub = mw.ustring.sub, mw.ustring.match, mw.ustring.gsub
-- ustring is string library equivalents that work on UTF-8 aka mediawiki text
-- ustring equivalents of string library that work on UTF-8 aka MediaWiki text


local MAX_ENTRIES = 500
local MAX_ENTRIES = 500
Line 98: Line 98:
}
}


-- wikitext for a simple link; displayText is optional as usual
-- Wikitext for a simple link; displayText is optional as usual
local function generateLink(target, displayText)
local function generateLink(target, displayText)
if displayText then
if displayText then
Line 108: Line 108:


--[[ parseRatings(raw)
--[[ parseRatings(raw)
Parses raw entry text into a sortable Lua list (table) and determines most
Parses raw entry text into a sortable sequence (Lua table) and determines most
   recent rating.
   recent rating.


Line 119: Line 119:
RETURN:
RETURN:
  (newestRating, - latest rating (number)
  (newestRating, - latest rating (number)
   outputList)  - list (table) of lua objects {text,t}
   outputList)  - list (Lua sequence/table) of Lua objects {text,t}
                       text: wikitext
                       text: wikitext
                       t:    lua time object with y,m,d
                       t:    lua time object with y,m,d
Line 128: Line 128:
local rating, refDate, refString, t
local rating, refDate, refString, t
-- for each curly-braced entry, extract data and translate to table
-- Extract data from each curly-braced entry
gsub(raw, "{(.-)}", function(a)
gsub(raw, "{(.-)}", function(a)
rating, refDate, refString = match(a, "(%d+),([%d%-]+),(.+)")
rating, refDate, refString = match(a, "(%d+),([%d%-]+),(.+)")
Line 135: Line 135:
-- Dates MUST be yyyy-mm-dd
-- Dates MUST be yyyy-mm-dd
t = os.time({year=sub(refDate, 1,4), month=sub(refDate, 6,7), day=sub(refDate, 9,10),})
t = os.time({year=sub(refDate, 1,4), month=sub(refDate, 6,7), day=sub(refDate, 9,10),})
-- find most recent entry as we go
-- Find most recent entry as we go
if t > newestDate then
if t > newestDate then
newestDate = t
newestDate = t
Line 141: Line 141:
end
end
-- add to sequence (table) of entries, with ref concatenated on rating
-- Add to sequence of entries, with ref concatenated on rating
table.insert(outputList, {text = rating..refString, t = t})
table.insert(outputList, {text = rating..refString, t = t})
end)
end)
Line 149: Line 149:


--[[ addPrintings(timeline, printings)
--[[ addPrintings(timeline, printings)
Parses raw list of sets and adds them into date-sortable Lua list (table)
Parses raw list of sets and adds them into date-sortable sequence (Lua table)


PARAMS:
PARAMS:
   timeline - existing Lua list (table) to add to, each entry with format
   timeline - existing sequence (Lua table) to add to, each entry with format
       {text, t}, where text is wikitext and t is a Lua time object w/y,m,d
       {text, t}, where text is wikitext and t is a Lua time object w/y,m,d
   printings - comma separated list of set names in plaintext
   printings - comma separated list of set names in plaintext
Line 161: Line 161:
local function addPrintings(timeline, printings)
local function addPrintings(timeline, printings)
local releaseDate, t
local releaseDate, t
-- Comma-separated list
-- Parse each list element delimited by commas
gsub(printings, "([^,]+)", function(set)
gsub(printings, "([^,]+)", function(set)
releaseDate = LEGAL_EXPANSIONS[set]
releaseDate = LEGAL_EXPANSIONS[set]
Line 178: Line 178:


--[[ sortTimeline(events)
--[[ sortTimeline(events)
Sorts a timeline (list/Lua table) as generated by parseRatings & addPrintings,
Sorts a timeline as generated by parseRatings & addPrintings, by each entry's date
  by each entry's date


PARAM: events - list (Lua table); each entry must have a "t" component which
PARAM: events - sequence (Lua table); each entry must have a "t" component which
         is a Lua date object with y,m,d
         is a Lua date object with y,m,d
RETURN: sorted events
RETURN: sorted events
Line 193: Line 192:
--[[ MaroScaleSandbox.TableBuilder(frame)
--[[ MaroScaleSandbox.TableBuilder(frame)


Entry point for this module. Turns all template arguments (via frame) into
Entry point for this module. Turns all template arguments into a table as wikitext.
  a table as wikitext.


See Template:MaroScale for parameter documentation. Note that for any entry###,
ratings### is required or the entry will be ignored.
]]
]]
function MaroScaleSandbox.TableBuilder(frame)
function MaroScaleSandbox.TableBuilder(frame)
local args = frame:getParent().args -- coming from one layer up
local args = frame:getParent().args
-- frame.args:            Module/invoke parameters (none expected)
-- frame:getParent().args: Template parameters (nameI, entryI, noteI, etc)
-- Imitating navbox, here. I trust they know what they're doing.
-- Imitating navbox, here. I trust they know what they're doing.
-- Read the arguments in the order they'll be output in, to make references number in the right order.
-- Read the arguments in the order they'll be output in, to make references number in the right order.
--[[ Commented out by jerodast:
I don't understand why this is necessary; pairs already works on .args
and the list will be re-sorted anyway...
     local _
     local _
     _ = args.type
     _ = args.type
Line 212: Line 217:
         _ = args["printings" .. tostring(i)]
         _ = args["printings" .. tostring(i)]
     end
     end
    ]]
-- Seriously this loop is entirely too clever
-- Get parameter numbers for each entry into sequence, sort it by NAME
local listnums = {}
local entryNums = {}
for k, v in pairs(args) do
for k, v in pairs(args) do
local listnum = match('' .. k, '^ratings(%d+)$')
-- Extract param numbers based on ratings### parameter (required)
if listnum then table.insert(listnums, {num = tonumber(listnum), alpha = args["entry"..listnum]}) end
local eNum = match('' .. k, '^ratings(%d+)$')
if eNum then
table.insert(entryNums, {num = tonumber(eNum), entry = args["entry"..eNum]})
end
end
end
table.sort(listnums, function(a,b) return a.alpha < b.alpha end)
table.sort(entryNums, function(a,b) return a.entry < b.entry end)


-- Build the table header row
-- Build the table header row
local type = args.type
local tbl = mw.html.create("table"):addClass("wikitable"):addClass("sortable")
local tbl = mw.html.create("table"):addClass("wikitable"):addClass("sortable")
tbl:tag("tr")
tbl:tag("tr")
:tag("th"):wikitext(type):done()
:tag("th"):wikitext(args.type):done()   -- "Mechanic", "Plane", etc
:tag("th"):wikitext("Latest ranking"):done()
:tag("th"):wikitext("Latest ranking"):done()
:tag("th"):addClass("unsortable"):wikitext("Timeline"):done() -- Last column can't be sorted
:tag("th"):wikitext("Timeline"):addClass("unsortable"):done() -- Last col can't be sorted
local hasNotes = false
local hasNotes = false
Line 236: Line 245:
local linkedEntry, lastRating, timeline
local linkedEntry, lastRating, timeline
local row, list, cellText
local row, list, cellText
for i, listnum in ipairs(listnums) do
listnum = listnum.num
-- For each entry identified and sorted by name earlier...
for i, eData in ipairs(entryNums) do
eNum = eData.num   -- original parameter number for this entry
entry = eData.entry;
-- Retrieve the relevant args for this row
-- Retrieve the relevant args for this row
name, entry, note, ratings, printings = args["name"..listnum], args["entry"..listnum], args["note"..listnum], args["ratings"..listnum], args["printings"..listnum]
name, note = args["name"..eNum], args["note"..eNum]
ratings, printings = args["ratings"..eNum], args["printings"..eNum]
-- Create a wikilink for the entry, using name as the target article if provided
-- Create a wikilink for the entry, using name as the target article if provided
linkedEntry = generateLink(entry, name)
linkedEntry = generateLink(entry, name)
if note then
if note then
if not hasNotes then hasNotes = true end
hasNotes = true
-- Use {{efn}} template (explanatory footnote)
linkedEntry = linkedEntry .. frame:expandTemplate({title = "efn", args = { note }})
linkedEntry = linkedEntry .. frame:expandTemplate({title = "efn", args = { note }})
end
end
-- Decode the ratings into a table
-- Decode the ratings into a sequence (Lua table)
lastRating, timeline = parseRatings(ratings)
lastRating, timeline = parseRatings(ratings)
-- Add any printings
-- Add any printings
Line 267: Line 281:
-- Third cell
-- Third cell
-- Each rating is included as an item in an inline HTML list - which
--  may be excessive.
list = row:tag("td"):tag("ul"):cssText("display:block; margin-top:0; margin-left:0;")
list = row:tag("td"):tag("ul"):cssText("display:block; margin-top:0; margin-left:0;")
for n, event in pairs(timeline) do
for j, event in ipairs(timeline) do
if n ~= #timeline then
if j ~= #timeline then
list:tag("li"):cssText("display:inline-block;"):wikitext(event.text .. ",&nbsp;")
list:tag("li"):cssText("display:inline-block;"):wikitext(event.text .. ",&nbsp;")
else
else
Line 278: Line 294:
end
end
-- Include full footnotes below the table. Note this will include any other
--  explanatory footnotes on the page, and if a notelist is included in the
--  References section near the bottom page as it usually is, all notes
--  from the table will be duplicated there.
--  ...I think. (Maybe it breaks completely.)
if hasNotes then
if hasNotes then
return tostring(tbl) .. frame:expandTemplate({title = "notelist"})
return tostring(tbl) .. frame:expandTemplate({title = "notelist"})

Revision as of 18:46, 4 July 2022

This is a sandbox page for experimenting with the Module:MaroScale lua script. Click for /doc access.

Goals:

  • Learn a bit more how it's used/about Modules
  • Make some updates/improvements (see script comments for details)

User:jerodast (talk) will request to delete this page once any useful changes are merged to core MaroScale page.

Since this module gets arguments from frame:getParent(), its use is best exemplified / tested via the calling template. These examples don't use real references, which in practice would be true citations with a full reference footnote. In these examples, ratings will always be EXPECTED to be in ascending order once sorted by date, to easily verify correctness.

Let's put[a] in some[b] fake footnotes to test compatibility of tables & other footnotes.[c]

PlaneLatest rankingTimeline
Innistrad1
  • 1[4]
Ravnica[d]1
  • 1[4]
  1. First fake footnote
  2. Second
  3. Final fake footnote!
  4. Worst plane ever
MechanicLatest rankingTimeline
-1/-1 counters2
  • 1[4]
  • 2[4]
Grind6
  • 5[4]
  • 6[4]
Rhystic9
  • 9[4]
Creature typeLatest rankingTimeline
Beeble10
  • 8[4]
  • 10[4]
Dauthi6
  • 6[4]
Djinn4
Dragon1
Elf1
Goblin1
Human1
Hydra1
Werewolf3
PlaneswalkerLatest rankingTimeline
Kiora4
  • 4[4]
Nahiri4
  • 3[4]
  • 4[4]
Tibalt7
  • 5[4]
  • 6[4]
  • 7[4]
Urza9
  • 9[4]

Quick links for live pages:

Lua error at line 230: attempt to compare nil with string.

Footnotes


--[[ Sandbox page for the MaroScale lua script

jerodast will request to delete this page once any useful changes are merged to
  the live MaroScale page

Goals for jerodast:
  * learn a bit more how it's used, AND about Modules in general
  * update LEGAL_EXPANSIONS
  * allow for not-a-single-number phrasings to be given an approximate number
      for sorting but still display, e.g. "about a 9" (treated as 9) or
      "7 or 8" (treated as 7.5)
  * explore possibility of having dates show up in the list of ratings changes,
      since the current simple serial list presentation makes ratings appear
      uniformly spaced when they're not
  * add column for additional notes - note that Beeble Scale crams notes into
      Timeline column already
]]

--[[ Beeble scale currently includes notes simply by placing them after the ref
     part of an entry:
    
	| entry25  = Phelddagrif
	| ratings25= {9,2018-10-21,<ref>{{EzTumblr|http://markrosewater.tumblr.com/post/179292907638/|title=Where is Phelddagriff on the Beeble Scale?|October 21, 2018}}</ref>}, 
	{9,2018-11-26,<ref>{{EzTumblr|http://markrosewater.tumblr.com/post/180506854918/|title=Where are phelddagrifs on the beeble scale? |November 26, 2018}}</ref> (actually 9 ½.)},
	
	Shows as:           9[32], 9[33] (actually 9 ½.)
	
	
	| entry4   = Dwarf
	| ratings4 = {1,2013-01-23,<ref>{{EzTumblr|http://markrosewater.tumblr.com/post/41287858160/|title=I like your "storm scale" invention so I hope...|2013-01-23}}</ref>},
	{1,2013-10-25,<ref>{{EzTumblr|http://markrosewater.tumblr.com/post/65064665515/|title=Where are dwarves on the storm scale? I know that orcs and dwarves...|2013-10-25}}</ref>},
	{1,2016-06-04,<ref>{{EzTumblr|http://markrosewater.tumblr.com/post/145378547208/|title=Hello Mr. Rosewater! Can you tell us where are dwarves on the Storm Scale?|2016-06-04}}</ref> Reprinted in ''[ [Kaladesh] ]''}, 
	{2,2018-03-20,<ref>{{EzTumblr|http://markrosewater.tumblr.com/post/172073275828/|title=What’s the current outlook for Dwarves in Magic?|2018-03-20}}</ref>}, 
	{3,2018-10-23,<ref>{{EzTumblr|http://markrosewater.tumblr.com/post/17934156953/|title=Where are dwarves on the Beeble Scale?|October 23, 2018}}</ref>}, 
	{2,2019-08-21,<ref name="D&O">{{EzTumblr|https://markrosewater.tumblr.com/post/187167710678/|title=So what are Dwarves and Orcs on the Beeble scale?|August 21, 2019}}</ref>},
	{2,2019-09-30,<ref>{{EzTumblr|https://markrosewater.tumblr.com/post/188050238433/|title=Hi Mark, Since we got some pretty sweet Dwarf cards in Eldraine|September 30, 2019}}</ref>}, 
	{4,2020-03-29,<ref>{{EzTumblr|http://markrosewater.tumblr.com/post/613949342837096448/|title=Judging by my playgroup and another Question Marks...|2020-03-29}}</ref>},
	
	Shows as:     1[5], 1[6], 1[7] Reprinted in Kaladesh, 2[8], 3[9], 2[10], 2[11], 4[12]
]]

-- Little bit of Lua for building tables for the Storm and Rabiah scales.
-- Mechanics have been re-ranked in the past, and it's probably interesting to track their drift.
-- Rather than force editors to track the newest one, let's see if we can't do it in code.

-- Invoked by {{#invoke:MaroScale|TableBuilder}} from the MW template.

local MaroScaleSandbox = {}

local sub, match, gsub = mw.ustring.sub, mw.ustring.match, mw.ustring.gsub
-- ustring equivalents of string library that work on UTF-8 aka MediaWiki text

local MAX_ENTRIES = 500
-- A list of all sets which have been Standard-legal since the introduction of the Storm Scale (September 2012)
local LEGAL_EXPANSIONS = {
	["Return to Ravnica"]		 = "2012-10-05",
	["Gatecrash"] 				 = "2013-02-01",
	["Dragon's Maze"] 			 = "2013-05-03",
	["Magic 2014"] 				 = "2013-06-13",
	["Theros"] 					 = "2013-09-27",
	["Born of the Gods"] 		 = "2014-02-07",
	["Journey into Nyx"]		 = "2014-05-02",
	["Magic 2015"] 				 = "2014-07-18",
	["Khans of Tarkir"] 		 = "2014-09-26",
	["Fate Reforged"] 			 = "2015-01-23",
	["Dragons of Tarkir"] 		 = "2015-03-17",
	["Magic Origins"] 			 = "2015-07-15",
	["Battle for Zendikar"] 	 = "2015-10-02",
	["Oath of the Gatewatch"] 	 = "2016-01-22",
	["Shadows over Innistrad"] 	 = "2016-04-08",
	["Eldritch Moon"] 			 = "2016-06-22",
	["Kaladesh"] 				 = "2016-09-30",
	["Aether Revolt"] 			 = "2016-01-20",
	["Amonkhet"] 				 = "2017-04-28",
	["Hour of Devastation"] 	 = "2017-07-14",
	["Ixalan"]                   = "2017-09-29",
	["Rivals of Ixalan"]         = "2018-01-19",
	["Dominaria"]                = "2018-04-27",
	["Core Set 2019"]            = "2018-07-13",
	["Guilds of Ravnica"]        = "2018-10-05",
	["Ravnica Allegiance"]       = "2019-01-25",
	["War of the Spark"]         = "2019-05-03",
	["Core Set 2020"]            = "2019-07-12",
	["Throne of Eldraine"]       = "2019-10-04",
	["Theros Beyond Death"]      = "2020-01-24",
	["Ikora: Lair of Behemoths"] = "2020-05-15",
	["Core Set 2021"]            = "2020-07-03",
	["Zendikar Rising"]          = "2020-09-25",
	["Kaldheim"]                 = "2021-02-05",
	["Strixhaven"]               = "2021-04-23",
	["Dungeons & Dragons: Adventures in the Forgotten Realms"] = "2021-07-23",
	["Innistrad: Midnight Hunt"] = "2021-09-24",
	["Innistrad: Crimson Vow"]   = "2021-11-19",
	["Kamigawa: Neon Dynasty"]   = "2022-02-18",
	["Streets of New Capenna"]   = "2022-04-29",
	["Dominaria United"]         = "2020-09-09",
	["The Brothers' War"]        = "2020-11-18",
}

-- Wikitext for a simple link; displayText is optional as usual
local function generateLink(target, displayText)
	if displayText then
		return "[["..displayText.."|"..target.."]]"
	else
		return "[["..target.."]]"
	end
end

--[[ parseRatings(raw)
Parses raw entry text into a sortable sequence (Lua table) and determines most
  recent rating.

PARAM:
  raw - text for one mechanic's storm scale ratings/entries, in the comma-
        separated, curly-braced format {rating,date,ref},{...},{...},...
            rating: integer only
            date:   must be exactly yyyy-mm-dd format, always hyphen-separated
            ref:    source, presumably in ref tags; technically could be any text
RETURN:
 (newestRating, - latest rating (number)
  outputList)   - list (Lua sequence/table) of Lua objects {text,t}
                      text: wikitext
                      t:    lua time object with y,m,d
]]
local function parseRatings(raw)
	local outputList = {}
	local newestDate, newestRating = 0, 0
	local rating, refDate, refString, t
	
	-- Extract data from each curly-braced entry
	gsub(raw, "{(.-)}", function(a)
		rating, refDate, refString = match(a, "(%d+),([%d%-]+),(.+)")
		rating = tonumber(rating)
		
		-- Dates MUST be yyyy-mm-dd
		t = os.time({year=sub(refDate, 1,4), month=sub(refDate, 6,7), day=sub(refDate, 9,10),})
		-- Find most recent entry as we go
		if t > newestDate then
			newestDate = t
			newestRating = rating
		end
		
		-- Add to sequence of entries, with ref concatenated on rating
		table.insert(outputList, {text = rating..refString, t = t})
	end)
	
	return newestRating, outputList
end

--[[ addPrintings(timeline, printings)
Parses raw list of sets and adds them into date-sortable sequence (Lua table)

PARAMS:
  timeline - existing sequence (Lua table) to add to, each entry with format
      {text, t}, where text is wikitext and t is a Lua time object w/y,m,d
  printings - comma separated list of set names in plaintext
RETURN:
  timeline - same format as input parameter, but with new entries containining
    wikitext for each set, and including wikilinks
]]
local function addPrintings(timeline, printings)
	local releaseDate, t
	-- Parse each list element delimited by commas
	gsub(printings, "([^,]+)", function(set)
		releaseDate = LEGAL_EXPANSIONS[set]
		
		-- The set name provided doesn't match a post-Storm Scale set
		if not releaseDate then return end
		
		-- Dates MUST be yyyy-mm-dd
		t = os.time({year=sub(releaseDate, 1,4), month=sub(releaseDate, 6,7), day=sub(releaseDate, 9,10),})
		
		table.insert(timeline, {text = generateLink(set), t = t})
	end)
	
	return timeline
end

--[[ sortTimeline(events)
Sorts a timeline as generated by parseRatings & addPrintings, by each entry's date

PARAM: events - sequence (Lua table); each entry must have a "t" component which
         is a Lua date object with y,m,d
RETURN: sorted events
]]
local function sortTimeline(events)
	-- Sort all events into chronological order
	table.sort(events, function(a,b) return a.t < b.t end)
	return events
end

--[[ MaroScaleSandbox.TableBuilder(frame)

Entry point for this module. Turns all template arguments into a table as wikitext.

See Template:MaroScale for parameter documentation. Note that for any entry###,
ratings### is required or the entry will be ignored.
]]
function MaroScaleSandbox.TableBuilder(frame)
	local args = frame:getParent().args
	-- frame.args:             Module/invoke parameters (none expected)
	-- frame:getParent().args: Template parameters (nameI, entryI, noteI, etc)
	
	-- Imitating navbox, here. I trust they know what they're doing.
	-- Read the arguments in the order they'll be output in, to make references number in the right order.
	--[[ Commented out by jerodast:
			I don't understand why this is necessary; pairs already works on .args
			and the list will be re-sorted anyway...
    local _
    _ = args.type
    _ = args.above
    for i = 1, MAX_ENTRIES do
        _ = args["name" .. tostring(i)]
        _ = args["entry" .. tostring(i)]
        _ = args["note" .. tostring(i)]
        _ = args["ratings" .. tostring(i)]
        _ = args["printings" .. tostring(i)]
    end
    ]]
	
	-- Get parameter numbers for each entry into sequence, sort it by NAME
	local entryNums = {}
	for k, v in pairs(args) do
		-- Extract param numbers based on ratings### parameter (required)
		local eNum = match('' .. k, '^ratings(%d+)$')
		if eNum then
			table.insert(entryNums, {num = tonumber(eNum), entry = args["entry"..eNum]})
		end
	end
	table.sort(entryNums, function(a,b) return a.entry < b.entry end)

	
	-- Build the table header row
	local tbl = mw.html.create("table"):addClass("wikitable"):addClass("sortable")
	tbl:tag("tr")
		:tag("th"):wikitext(args.type):done()    -- "Mechanic", "Plane", etc
		:tag("th"):wikitext("Latest ranking"):done()
		:tag("th"):wikitext("Timeline"):addClass("unsortable"):done() -- Last col can't be sorted
	
	
	local hasNotes = false
	
	-- Reusable loop vars for performance
	local name, entry, note, ratings, printings
	local linkedEntry, lastRating, timeline
	local row, list, cellText
	
	-- For each entry identified and sorted by name earlier...
	for i, eData in ipairs(entryNums) do
		eNum = eData.num   -- original parameter number for this entry
		entry = eData.entry;
		-- Retrieve the relevant args for this row
		name, note = args["name"..eNum], args["note"..eNum]
		ratings, printings = args["ratings"..eNum], args["printings"..eNum]
		
		-- Create a wikilink for the entry, using name as the target article if provided
		linkedEntry = generateLink(entry, name)
		if note then
			hasNotes = true
			-- Use {{efn}} template (explanatory footnote)
			linkedEntry = linkedEntry .. frame:expandTemplate({title = "efn", args = { note }})
		end
		
		-- Decode the ratings into a sequence (Lua table)
		lastRating, timeline = parseRatings(ratings)
		-- Add any printings
		if printings then
			timeline = addPrintings(timeline, printings)
		end
		-- Sort chronologically
		timeline = sortTimeline(timeline)
		
		-- Make the row
		row = tbl:tag("tr")
		
		-- First cell
		row:tag("td"):wikitext(linkedEntry)
		
		-- Second cell
		row:tag("td"):wikitext(lastRating)
			
		-- Third cell
		-- Each rating is included as an item in an inline HTML list - which
		--   may be excessive.
		list = row:tag("td"):tag("ul"):cssText("display:block; margin-top:0; margin-left:0;")
		
		for j, event in ipairs(timeline) do
			if j ~= #timeline then
				list:tag("li"):cssText("display:inline-block;"):wikitext(event.text .. ",&nbsp;")
			else
				list:tag("li"):cssText("display:inline-block;"):wikitext(event.text)
			end
		end
	end
	
	-- Include full footnotes below the table. Note this will include any other
	--   explanatory footnotes on the page, and if a notelist is included in the
	--   References section near the bottom page as it usually is, all notes
	--   from the table will be duplicated there.
	--   ...I think. (Maybe it breaks completely.)
	if hasNotes then
		return tostring(tbl) .. frame:expandTemplate({title = "notelist"})
	else
		return tbl
	end
end

return MaroScaleSandbox