Module:MLB standings: Difference between revisions

Content deleted Content added
change error handling of seeds parameter, so if one seed is specified incorrectly, the others will still be processed
modify to use classes defined in Template:MLB standings/styles.css
 
(58 intermediate revisions by 10 users not shown)
Line 4:
local me = { }
 
local mlbData = mw.loadData('Module:MLB standings/data')
local Navbar = require('Module:Navbar')
 
--
-- if mw.loadData() not supported, use require() instead
-- defaultOutputForInput: table mapping from input format to default output format
if mw.loadData then
-- (if the output format is not specified in the template arguments)
mlbData = mw.loadData('Module:Sandbox/isaacl/MLB standings/data')
--
else
local defaultOutputForInput = {
mlbData = require('Module:Sandbox/isaacl/MLB standings/data')
default = 'default',
end
overallWinLoss = 'winLossOnly',
}
 
--
-- readTeamInfo: table of input parsers
-- Keys are the input formats, values are functions that parse the unnamed parameters
-- that were passed to the template and return a table holding the team name
-- and the win-loss records (either overall, or home and away, depending on the
-- input format).
-- The parsers take the following parameters:
-- args: table holding the parameters (indexed by numeric position)
-- currentIdx: the current index from where the next set of data should be parsed
-- returnData: table that the parser will update to pass additional data back to the caller.
-- returnData.cIndicesRead is updated with the number of parameters that were parsed
--
local readTeamInfo = {
default = function(args, currentIdx, returnData)
if (args[currentIdx] == nil or
args[currentIdx+1] == nil or
args[currentIdx+2] == nil or
args[currentIdx+3] == nil or
args[currentIdx+4] == nil ) then
return nil
end
teamInfo = {
name = mw.text.trim(args[currentIdx]),
homeWins = tonumber(mw.text.trim(args[currentIdx+1])),
homeLosses = tonumber(mw.text.trim(args[currentIdx+2])),
roadWins = tonumber(mw.text.trim(args[currentIdx+3])),
roadLosses = tonumber(mw.text.trim(args[currentIdx+4])),
}
returnData.cIndicesRead = 5
teamInfo.wins = teamInfo.homeWins + teamInfo.roadWins
teamInfo.losses = teamInfo.homeLosses + teamInfo.roadLosses
return teamInfo
end, -- function readTeamInfo.default()
 
overallWinLoss = function(args, currentIdx, returnData)
-- Temporary workaround for missing mw.text utility functions
if (args[currentIdx] == nil or
mw.text = mw.text or {}
args[currentIdx+1] == nil or
 
if (mw.text.trim args[currentIdx+2] == nil ) then
return nil
mw.text.trim = function(s)
if (s == nil) then
return ''
end
teamInfo = {
return mw.ustring.match(s, "^%s*(.-)%s*$")
name = mw.text.trim(args[currentIdx]),
end
wins = tonumber(mw.text.trim(args[currentIdx+1])),
end
losses = tonumber(mw.text.trim(args[currentIdx+2])),
}
returnData.cIndicesRead = 3
return teamInfo
end, -- function readTeamInfo.default()
 
} -- readTeamInfo object
if (mw.text.gsplit == nil) then
mw.text.gsplit = function( text, pattern, plain )
local s, l = 1, mw.ustring.len( text )
return function ()
if s then
local e, n = mw.ustring.find( text, pattern, s, plain )
local ret
if not e then
ret = mw.ustring.sub( text, s )
s = nil
elseif n < e then
-- Empty separator!
ret = mw.ustring.sub( text, s, e )
if e < l then
s = e + 1
else
s = nil
end
else
ret = e > s and mw.ustring.sub( text, s, e - 1 ) or ''
s = n + 1
end
return ret
end
end, nil, nil
end
end
 
--
if (mw.text.split == nil) then
-- generateTableHeader: table of functions that generate table header
mw.text.split = function ( text, pattern, plain )
-- Keys are the output formats, values are functions that return a string with the table header
local ret = {}
-- The generator functions take the following parameter:
for m in mw.text.gsplit( text, pattern, plain ) do
-- tableHeaderInfo: table that contains the information needed for the header
ret[#ret+1] = m
--
end
local generateTableHeader = {
return ret
default = function(tableHeaderInfo)
end
return
end
'{| class="wikitable MLBStandingsTable" \
|+ |' .. tableHeaderInfo.navbarText .. '[[' .. tableHeaderInfo.divisionLink
.. '|' .. tableHeaderInfo.division .. ']]\
|- \
! width="51%" | Team \
! width="6%" | [[Win (baseball)|W]]\
! width="6%" | [[Loss (baseball)|L]]\
! width="9%" | [[Winning percentage|Pct.]]\
! width="8%" | [[Games behind|GB]]\
! width="10%" | [[Home (sports)|Home]]\
! width="10%" | [[Road (sports)|Road]]\
'
end, -- function generateTableHeader.default()
 
winLossOnly = function(tableHeaderInfo)
return
'{| class="wikitable MLBStandingsTable" \
|+ |' .. tableHeaderInfo.navbarText .. tableHeaderInfo.division .. '\
|- \
! width="66%" | Team\
! width="10%" | [[Win (baseball)|W]]\
! width="10%" | [[Loss (baseball)|L]]\
! width="14%" | [[Winning percentage|Pct.]]\
'
end, -- function generateTableHeader.winLossOnlyNoNavBar()
 
wildCard2012 = function(tableHeaderInfo)
local function readTeamInfo(args, currentIdx)
if (args[currentIdx] == nil orreturn
'{| class="wikitable MLBStandingsTable" \
args[currentIdx+1] == nil or
|+ |' .. tableHeaderInfo.navbarText .. 'Wild Card teams<br><small>(Top 2 teams qualify for postseason)</small>\
args[currentIdx+2] == nil or
|- \
args[currentIdx+3] == nil or
! width="64%" | Team \
args[currentIdx+4] == nil or
! width="8%" | [[Win (baseball)|W]]\
args[currentIdx+5] == nil or
! width="8%" | [[Loss (baseball)|L]]\
args[currentIdx+6] == nil ) then
! width="10%" | [[Winning percentage|Pct.]]\
return nil
! width="10%" | [[Games behind|GB]]\
end
'
return {
end, -- function generateTableHeader.wildCard2012
name = mw.text.trim(args[currentIdx]),
 
wins = tonumber(mw.text.trim(args[currentIdx+1])),
wildCard = function(tableHeaderInfo)
losses = tonumber(mw.text.trim(args[currentIdx+2])),
local teamText = 'team'
homeWins = mw.text.trim(args[currentIdx+3]),
local numberOfTeamsText = 'team qualifies'
homeLosses = mw.text.trim(args[currentIdx+4]),
if tableHeaderInfo.wildCardsPerLeague > 1 then
roadWins = mw.text.trim(args[currentIdx+5]),
teamText = 'teams'
roadLosses = mw.text.trim(args[currentIdx+6]),
numberOfTeamsText = tableHeaderInfo.wildCardsPerLeague .. ' teams qualify'
}
end
end -- function readTeamInfo()
return
'{| class="wikitable MLBStandingsTable" \
|+ |' .. tableHeaderInfo.navbarText .. 'Wild Card ' .. teamText .. '<br><small>(Top ' .. numberOfTeamsText ..
' for postseason)</small>\
|- \
! width="64%" | Team \
! width="8%" | [[Win (baseball)|W]]\
! width="8%" | [[Loss (baseball)|L]]\
! width="10%" | [[Winning percentage|Pct.]]\
! width="10%" | [[Games behind|GB]]\
'
end, -- function generateTableHeader.wildCard
 
} -- generateTableHeader object
 
--
-- generateTeamRow: table of functions that generate a table row
-- Keys are the output formats, values are functions that return a string with the table row
-- The generator functions take the following parameter:
-- tableRowInfo: table that contains additional the information needed for the row
-- teamInfo: table that contains the team name and win-loss info
--
local generateTeamRow = {
default = function(teamRowInfo, teamInfo)
return
'|-' .. teamRowInfo.rowStyle .. '\
|| ' .. teamRowInfo.seedText .. '[[' .. teamRowInfo.teamSeasonPage .. '|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. teamRowInfo.winningPercentage .. '\
|| ' .. teamRowInfo.gamesBehind .. '\
|| ' .. teamInfo.homeWins .. '&zwj;–&zwj;' .. teamInfo.homeLosses ..'\
|| ' .. teamInfo.roadWins .. '&zwj;–&zwj;' .. teamInfo.roadLosses .. '\n'
 
end, -- function generateTeamRow.default()
 
winLossOnly = function(teamRowInfo, teamInfo)
return
'|-' .. teamRowInfo.rowStyle .. '\
|| ' .. teamRowInfo.seedText .. '[[' .. teamRowInfo.teamSeasonPage .. '|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. teamRowInfo.winningPercentage .. '\n'
end, -- function generateTeamRow.winLossOnly
 
wildCard2012 = function(teamRowInfo, teamInfo)
return
'|-' .. teamRowInfo.rowStyle .. '\
|| ' .. teamRowInfo.seedText .. '[[' .. teamRowInfo.teamSeasonPage .. '|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. teamRowInfo.winningPercentage .. '\
|| ' .. teamRowInfo.gamesBehind .. '\n'
end, -- function generateTeamRow.wildCard2012
 
wildCard = function(teamRowInfo, teamInfo)
return
'|-' .. teamRowInfo.rowStyle .. '\
|| ' .. teamRowInfo.seedText .. '[[' .. teamRowInfo.teamSeasonPage .. '|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. teamRowInfo.winningPercentage .. '\
|| ' .. teamRowInfo.gamesBehind .. '\n'
end, -- function generateTeamRow.wildCard
} -- generateTeamRow object
 
--
-- parseSeeds: function to parse the seeds template argument
--
local function parseSeeds(seedsArg, seeds)
local seedList = mw.text.split(seedsArg, '%s*,%s*')
Line 103 ⟶ 204:
end -- function parseSeeds()
 
--
-- parseHighlightArg: function to parse the highlight template argument
--
local function parseHighlightArg(highlightArg, teamsToHighlight)
local teamList = mw.text.split(highlightArg, '%s*,%s*')
Line 115 ⟶ 219:
end -- function parseHighlightArg
 
--
-- parseTeamLInks: function to parse the team_links template argument
--
local function parseTeamLinks(teamLinksArg, linkForTeam)
local teamList = mw.text.split(teamLinksArg, '%s*,%s*')
if (#teamList == 0) then
return
end
 
for idx, teamLinkInfo in ipairs(teamList) do
local teamData = mw.text.split(teamLinkInfo, '%s*:%s*')
if (#teamData >= 2) then
local team = mw.text.trim(teamData[1])
local teamLink = mw.text.trim(teamData[2])
linkForTeam[team] = teamLink
end
end
end -- function parseTeamLinks
 
local function getWildCardsPerLeagueForYear(year)
if year == '' then
return 0
end
for idx, wildCardInfo in ipairs(mlbData.wildCardInfo) do
if wildCardInfo.startYear <= year and year <= wildCardInfo.endYear then
return wildCardInfo.wildCardsPerLeague;
end
end
-- year not found, thus no wild cards for specified year
return 0;
end -- function getWildCardsPerLeagueForYear
 
--
-- function generateStandingsTable
--
-- Parameters: frame object from template
-- frame.args.input: input format for standings info
-- if not specified, the default is team name followed by home win-loss and road win-loss records
-- - overallWinLoss: team name followed by overall win-loss record
--
-- frame.args.output: output format for standings table
-- if not specified, the output format is based on the input format (see defaultOutputForInput table):
-- - default => games behind and home and road win-loss records displayed
-- - overallWinLoss => overall win-loss records displayed, no games behind column
-- - winLossOnly: overall win-loss records displayed, no games behind column
-- - wildCard: wildcard standings table displayed
-- - wildCard2012: wildcard standings table displayed (effectively the same as wildcard for years from 2012-2021; kept for backwards compatibility)
--
-- frame.args.template_name: name of standings template
-- if not specified, the default is <year> <division name> standings
--
-- frame.args.seeds: list of team seedings
-- frame.args.highlight: list of teams to highlight
-- frame.args.team_links: list of link targets for each team
-- If not specified, the default is just the team name.
-- This is used to generate the season page for each team, in the form
-- <year> <team link target> season
--
function me.generateStandingsTable(frame)
local yearinputFormat = mw.text.trim(frame.args.year)'default'
local division = mw.text.trim(frame.args.division)
local divisionForNavbox = division
 
-- If the input parameter is specified in the template, use it as the input format.
if (mlbData.abbreviationForDivision[division] ~= nil) then
if (frame.args.input ~= nil) then
divisionForNavbox = mlbData.abbreviationForDivision[division]
local inputArg = mw.text.trim(frame.args.input)
if (inputArg == 'overallWinLoss') then
inputFormat = 'overallWinLoss'
end
end
 
local templateName = nil
if (frame.args.template_name ~= nil) then
templateName = frame.args.template_name
end
 
local outputFormat = defaultOutputForInput[inputFormat]
local fDisplayNavbar = true
local fDisplayGamesBehind = true
 
-- If the output parameter is specified in the template, use it as the output format.
-- Note no cross validation is performed to check if it is valid given the input format.
if (frame.args.output ~= nil) then
local outputArg = mw.text.trim(frame.args.output)
if (outputArg == 'winLossOnly') then
outputFormat = 'winLossOnly'
fDisplayGamesBehind = false
end
if (outputArg == 'wildCard2012') then
outputFormat = 'wildCard2012'
end
if (outputArg == 'wildCard') then
outputFormat = 'wildCard'
end
end
 
local year = tonumber(mw.text.trim(frame.args.year or '0'))
local division = mw.text.trim(frame.args.division or '')
local divisionLink = mw.text.trim(frame.args.division_link or division)
local wildCardsPerLeague = getWildCardsPerLeagueForYear(year)
 
local seedInfo = {}
Line 132 ⟶ 326:
if (frame.args.highlight ~= nil) then
parseHighlightArg(frame.args.highlight, teamsToHighlight)
end
 
local linkForTeam = {}
if (frame.args.team_links ~= nil) then
parseTeamLinks(frame.args.team_links, linkForTeam)
end
 
Line 137 ⟶ 336:
local currentArgIdx = 1;
 
-- Parse the unnamed parameters from the template. This consists of the
local fTeamInfoPresent = false
-- team names and their win-loss records.
 
while (frame.args[currentArgIdx] ~= nil) do
local teamInforeturnData = readTeamInfo(frame.args,{ currentArgIdx);}
local teamInfo = readTeamInfo[inputFormat](frame.args, currentArgIdx, returnData);
if (teamInfo == nil) then
break
end
if (linkForTeam[teamInfo.name] ~= nil) then
fTeamInfoPresent = true
teamInfo.teamLink = linkForTeam[teamInfo.name]
else
teamInfo.teamLink = teamInfo.name
end
table.insert(listOfTeams, teamInfo)
currentArgIdx = currentArgIdx + 7returnData.cIndicesRead
end
 
if (not#listOfTeams fTeamInfoPresent== 0) then
return ''
end
 
-- table to hold list of strings that will be concatenated at the end
local outputSeq = { }
-- to create a string with the standings table
local outputBuffer = { }
 
local tableHeaderInfo = {
table.insert(outputSeq,
frame:expandTemplate{ titledivision = 'navbar'division, args = {
divisionLink = divisionLink,
year .. ' ' .. divisionForNavbox .. ' standings',
wildCardsPerLeague = wildCardsPerLeague,
}
 
if (fDisplayNavbar) then
local divisionForNavbox = division
if (mlbData.abbreviationForDivision[division] ~= nil) then
divisionForNavbox = mlbData.abbreviationForDivision[division]
end
 
local standingsPage
if (templateName ~= nil) then
standingsPage = templateName
else
standingsPage = year .. ' ' .. divisionForNavbox .. ' standings'
end
tableHeaderInfo.navbarText =
Navbar.navbar({
standingsPage,
mini = 1,
}} .. style = '\nfloat:left;width:0;',
})
end
table.insert(outputSeq,
 
'{| class="wikitable" width="60%" style="text-align:center;"\
table.insert(outputBuffer,
! width="38%" | ' .. division .. '\
generateTableHeader[outputFormat](tableHeaderInfo)
! width="7%" | [[Win (baseball)|W]]\
! width="7%" | [[Loss (baseball)|L]]\
! width="9%" | [[Winning percentage|Pct.]]\
! width="7%" | [[Games behind|GB]]\
! width="9%" | [[Home (sports)|Home]]\
! width="9%" | [[Road (sports)|Road]]\
'
)
 
local leadingHalfGames = nil;
if (fDisplayGamesBehind) then
local standingsLeaderIdx = 1
if (outputFormat == 'wildCard2012' and #listOfTeams > 1) then
standingsLeaderIdx = 2
end
if (outputFormat == 'wildCard' and #listOfTeams >= wildCardsPerLeague) then
standingsLeaderIdx = wildCardsPerLeague
end
 
local teamInfo = listOfTeams[standingsLeaderIdx]
leadingHalfGames = (teamInfo.wins - teamInfo.losses)
end
 
for idx, teamInfo in ipairs(listOfTeams) do
local winningPercentage = string.format(
local gamesBehind
'%.3f', teamInfo.wins / ( teamInfo.wins + teamInfo.losses )
if (leadingHalfGames == nil) then
leadingHalfGames = (teamInfo.wins - teamInfo.losses)
winningPercentage = string.gsub(winningPercentage, '^0', '')
gamesBehind = '—'
elselocal teamRowInfo = {
teamSeasonPage = year .. ' ' .. teamInfo.teamLink .. ' season',
winningPercentage = winningPercentage,
gamesBehind = '',
seedText = '',
rowStyle = '',
}
 
if (fDisplayGamesBehind) then
local halfGamesBehind = leadingHalfGames - (teamInfo.wins - teamInfo.losses)
gamesBehindlocal prefix = math.floor(halfGamesBehind / 2)nil
-- if (halfGamesBehindgames behind is negative, take the absolute %value 2and ==prefix 1)a then+
-- gamesBehind = gamesBehind .. '½'character
if (halfGamesBehind < 0) then
halfGamesBehind = -halfGamesBehind
prefix = '+'
end
if (halfGamesBehind == 0) then
teamRowInfo.gamesBehind = '—'
else -- if halfGamesBehind is not 0
teamRowInfo.gamesBehind = math.floor(halfGamesBehind / 2)
if (halfGamesBehind % 2 == 1) then
if (halfGamesBehind == 1) then
teamRowInfo.gamesBehind = '½'
else
teamRowInfo.gamesBehind = teamRowInfo.gamesBehind .. '½'
end
end
if ( prefix ~= nil ) then
teamRowInfo.gamesBehind = prefix .. teamRowInfo.gamesBehind
end
end -- if halfGamesBehind is not 0
end -- if (fDisplayGamesBehind)
 
end
 
local seedText = ''
local rowStyle = ''
local nameStyle = ''
if (seedInfo[teamInfo.name] ~= nil) then
teamRowInfo.seedText = '<sup>(' .. seedInfo[teamInfo.name] .. ')</sup> &nbsp;'
teamRowInfo.rowStyle = ' styleclass="background:#CCFFCCMLBStandingsHighlightedRow"'
end
 
if (teamsToHighlight[teamInfo.name]) then
teamRowInfo.rowStyle = ' styleclass="background:#CCFFCCMLBStandingsHighlightedRow"'
nameStyle = '| style="font-weight:bold" '
end
 
table.insert(outputSeqoutputBuffer,
generateTeamRow[outputFormat](teamRowInfo, teamInfo)
'|-' .. rowStyle .. '\n' ..
nameStyle .. '| ' .. seedText .. '[[' .. year .. ' ' .. teamInfo.name .. ' season|' .. teamInfo.name .. ']]\
|| ' .. teamInfo.wins .. ' || ' .. teamInfo.losses .. '\
|| ' .. string.format('%.3f', teamInfo.wins / ( teamInfo.wins + teamInfo.losses )) .. '\
|| ' .. gamesBehind .. '\
|| ' .. teamInfo.homeWins .. '–' .. teamInfo.homeLosses ..'\
|| ' .. teamInfo.roadWins .. '–' .. teamInfo.roadLosses .. '\n'
)
end -- end of looping over listOfTeams
end
 
table.insert(outputSeqoutputBuffer, '|}\n')
 
return table.concat(outputSeqoutputBuffer)
 
end -- function me.generateStandingsTable()