Module:MaroScaleSandbox

From MTG Wiki
Revision as of 03:05, 6 July 2022 by >Jerodast (oops, only if it found a number)
Jump to navigation Jump to search

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
Innistrad11[4]
Ravnica[d]11[4]
  1. First fake footnote
  2. Second
  3. Final fake footnote!
  4. Worst plane ever
MechanicLatest rankingTimeline
-1/-1 counters21[4], 2[4]
Grind65[4], 6[4]
Rhystic99[4]
Creature typeLatest rankingTimeline
Beeble108[4], 10[4]
Dauthi66[4]
Djinn44[2] Reprinted in Khans of Tarkir block and Dominaria.
Dragon11[1] Iconic
Elf11[1] Characteristic
Goblin11[1] Characteristic
Human11[1] Characteristic
Hydra11[1] Iconic
Werewolf33[2] A 6 outside of Innistrad.
PlaneswalkerLatest rankingTimeline
Kiora44[4]
Nahiri43[4], 4[4]
Tibalt75[4], 6[4], 7[4]
Urza99[4]

Quick links for live pages:

TESTLatest rankingTimeline
ERROR NO entry243[2], 4[2] No entry2!
Entry is the VISIBLE title55[7]
Mix ratings and printings (Lorwyn is there but should be ignored)85[4], 6[4], 7[4], 8[4]
More notes[a]42[6], 4[6]
  1. This is another footnote

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:
  * (DONE?) learn a bit more how it's used, AND about Modules in general
  * (DONE) update LEGAL_EXPANSIONS (although it's not used at all rn lol)
  * 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
]]

--[[ 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",
}
-- See initLegalExpansions func for how this will be reframed into LEGAL_EXPANSIONS
local LEGAL_EXPANSIONS_DATA = {
	{"2012-10-05", "RTR", "Return to Ravnica"},
	{"2013-02-01", "GTC", "Gatecrash"},
	{"2013-05-03", "DGM", "Dragon's Maze"},
	{"2013-06-13", "M14", "Magic 2014"},
	{"2013-09-27", "THS", "Theros"},
	{"2014-02-07", "BNG", "Born of the Gods"},
	{"2014-05-02", "JOU", "Journey into Nyx"},
	{"2014-07-18", "M15", "Magic 2015"},
	{"2014-09-26", "KTK", "Khans of Tarkir"},
	{"2015-01-23", "FRF", "Fate Reforged"},
	{"2015-03-17", "DTK", "Dragons of Tarkir"},
	{"2015-07-15", "ORI", "Magic Origins"},
	{"2015-10-02", "BFZ", "Battle for Zendikar"},
	{"2016-01-22", "OGW", "Oath of the Gatewatch"},
	{"2016-04-08", "SOI", "Shadows over Innistrad"},
	{"2016-06-22", "EMN", "Eldritch Moon"},
	{"2016-09-30", "KLD", "Kaladesh"},
	{"2016-01-20", "AER", "Aether Revolt"},
	{"2017-04-28", "AKH", "Amonkhet"},
	{"2017-07-14", "HOU", "Hour of Devastation"},
	{"2017-09-29", "XLN", "Ixalan"},
	{"2018-01-19", "RIX", "Rivals of Ixalan"},
	{"2018-04-27", "DOM", "Dominaria"},
	{"2018-07-13", "M19", "Core Set 2019"},
	{"2018-10-05", "GRN", "Guilds of Ravnica"},
	{"2019-01-25", "RNA", "Ravnica Allegiance"},
	{"2019-05-03", "WAR", "War of the Spark"},
	{"2019-07-12", "M20", "Core Set 2020"},
	{"2019-10-04", "ELD", "Throne of Eldraine"},
	{"2020-01-24", "THB", "Theros Beyond Death"},
	{"2020-05-15", "IKO", "Ikora: Lair of Behemoths"},
	{"2020-07-03", "M21", "Core Set 2021"},
	{"2020-09-25", "ZNR", "Zendikar Rising"},
	{"2021-02-05", "KHM", "Kaldheim"},
	{"2021-04-23", "STX", "Strixhaven"},
	{"2021-07-23", "AFR", "Dungeons & Dragons: Adventures in the Forgotten Realms"},
	{"2021-09-24", "MID", "Innistrad: Midnight Hunt"},
	{"2021-11-19", "VOW", "Innistrad: Crimson Vow"},
	{"2022-02-18", "NEO", "Kamigawa: Neon Dynasty"},
	{"2022-04-29", "SNC", "Streets of New Capenna"},
	{"2020-09-09", "DMU", "Dominaria United"},
	{"2020-11-18", "BRO", "The Brothers' War"},
}

-- sets up LEGAL_EXPANSIONS so that all set data can be queried with either
--   the set full name or set code, and label the fields
local function initLegalExpansions()
	for _,setData in ipairs(LEGAL_EXPANSIONS_DATA) do
	    --(musing from jerodast - based on my new understanding of Lua, this could
	    --  be done with __index metatable for each setData; doesn't seem worth)
		setData.releaseDate = setData[1]
		setData.setCode = setData[2]
		setData.setName = setData[3]
		
		LEGAL_EXPANSIONS[setData[2]] = setData
		LEGAL_EXPANSIONS[setData[3]] = setData
	end
end

-- 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 setData, releaseDate, t
	-- Parse each list element delimited by commas
	gsub(printings, "([^,]+)", function(set)
		setData = LEGAL_EXPANSIONS[set]
		
		-- The set provided doesn't match a post-Storm Scale set
		if not setData then return end
		
		rd = setData.releaseDate
		
		-- Dates MUST be yyyy-mm-dd
		t = os.time({year=sub(rd, 1,4), month=sub(rd, 6,7), day=sub(rd, 9,10),})
		
		table.insert(timeline, {text = generateLink(setData.code), 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)
	
	initLegalExpansions()
	
	-- 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
			local entry = args["entry"..eNum]
			if not entry then entry = "ERROR NO entry"..eNum end
			table.insert(entryNums, {num = tonumber(eNum), entry = entry})
		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.
		--[[ Commented out by jerodast - let's just try regular wikitext.
		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
		]]
	    list = {}
		for j, event in ipairs(timeline) do table.insert(list, event.text) end
		row:tag("td"):wikitext(table.concat(list,", "))
	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