Module:Tennis performance timeline

local p = {}

local concat = table.concat local insert = table.insert local format = mw.ustring.format

local tConfig = mw.loadData("Module:Tennis performance timeline/data") local rounds = tConfig.rounds local tournaments = tConfig.tournaments local environments = tConfig.environments local surfaces = tConfig.surfaces local tOrders = tConfig.orders

local curYear = os.date("!*t").year local calendar = mw.loadData("Module:Tennis performance timeline/calendar")

local genders = { men = "Men's", women = "Women's" }

local matchTypes = { singles = "Singles", doubles = "Doubles" }

--Utility functions

local function checkNonNil(value, type) if value == nil then error("Expected " .. type .. ", but is nil", 2) end return value end

local function checkFormat(str, pattern, type) if str == mw.ustring.match(str, pattern) then return str else error("Invalid " .. type .. ": " .. str, 2) end end

local function checkYear(year) checkNonNil(year, "year") return checkFormat(year, "%d%d%d%d", "year") end

local function checkNum(num, diagMsg) diagMsg = "number" .. (diagMsg and " for " .. diagMsg or "") checkNonNil(num, diagMsg); return checkFormat(num, "%d+", diagMsg) end

local function checkMember(elem, arr, type, diagMsg) if arr[elem] then return elem else diagMsg = type .. (diagMsg and " for " .. diagMsg or "") checkNonNil(elem, diagMsg) local message = {} insert(message, "Invalid ") insert(message, diagMsg) insert(message, ": ") insert(message, elem) error(concat(message)) end end

-- Format an HTML element with link, tooltip, and colors. local function tooltip(tag, link, tooltip, text, spec) spec = spec or {} if spec.color then tag:css('color', spec.color) end if spec.italic then tag:wikitext("''"); end if spec.bold then tag:wikitext("'''"); end if link then tag:wikitext("")	end	if tooltip then		if spec.abbr then			tag:tag('abbr'):attr('title', tooltip):wikitext(text)		else			tag:attr('title', tooltip):wikitext(text)		end	else		tag:wikitext(text)	end	if link then tag:wikitext("") end if spec.bold then tag:wikitext("'''"); end if spec.italic then tag:wikitext("''"); end end

-- Substitute "$[`param`]$" appearing in `str` with `value`. -- For example, subst("$year$ ATP Tour", "year", "1990") -> "1990 ATP Tour" local function subst(str, param, value) if str == nil then return str end return str:gsub("%$" .. param .. "%$", value) end

local function tr return mw.html.create('tr') end

local function th(row) return row:tag('th') end

local function td(row) return row:tag('td') end

local years local data local usedRounds

-- parser for data tables local function parse(entry, year, tStats) local entryType = type(entry) if entryType == "string" then return entry elseif entryType == "table" then if entry.type == "chrono" then local numericYear = tonumber(year) for _,elem in ipairs(entry) do				if type(elem) == "table" and numericYear >= elem[1] then return parse(elem[2], year, tStats) end end return parse(entry.default, year, tStats) elseif entry.type == "switch" then local param = entry.param local arg = param == "year" and year or param == "gender" and data.gender or tStats[param] if entry[arg] then return parse(entry[arg], year, tStats) end return parse(entry.default, year, tStats) else return entry end end end

-- Transform `param` entry in `array` with supplied data. local function transform(array, param, data, tournament, year, country) local entry = array[param] entry = subst(entry, "gender", genders[data.gender]) entry = subst(entry, "matchType", matchTypes[data.matchType]) entry = subst(entry, "year", year) if data[tournament] and data[tournament][year] then local tStats = data[tournament][year] if tournaments[tournament] and tournaments[tournament].region then entry = subst(entry, "region",				parse(tournaments[tournament].region[country] or tournaments[tournament].region.default, year, tStats)) end if tournaments[tournament].group then entry = subst(entry, "group", tournaments[tournament].group[tStats.group] or "") end end array[param] = entry end

-- Return wiki page title for the tournament in a given year. local function annualTournamentLink(year, tStats, tInfo) local annualLink = tInfo.annualLink return parse(annualLink, year, tStats) end

--- Prepare the header row of the performance timeline table. @return three elements: - table header wikitext - header row - the first cell in the header row local function header local rows = {} local row = tr local headerCell = th(row):attr('scope', 'col'):addClass('unsortable') local tooltipSpec = {abbr = true} if years.amateur or years.professional then headerCell:attr('rowspan', 2) local eras = {} if years.amateur then eras[#eras+1] = {years.amateur, "Amateur"} end if years.professional then eras[#eras+1] = {years.professional, "Professional"} end eras[#eras+1] = {"1968", "Open Era"} eras[#eras+1] = {tostring(years.last + 1)} local lastEra = "?" local idx = 1 for _,era in ipairs(eras) do			local count = 0 while years[idx] and years[idx] ~= era[1] do				count = count + 1 idx = idx + 1 end if count > 0 then th(row):attr('scope', 'col'):attr('colspan', count):wikitext(lastEra) end lastEra = era[2] end tooltip(th(row):attr('scope', 'col'):attr('rowspan', 2), nil, "Strike rate", "SR", tooltipSpec) tooltip(th(row):attr('scope', 'col'):attr('rowspan', 2), nil, "Win–Loss", "W–L", tooltipSpec) th(row):attr('scope', 'col'):attr('rowspan', 2):wikitext("Win %") insert(rows, row) row = tr end if data.categories.count > 1 then headerCell:attr('colspan', 2):wikitext("Tournament") end for _,year in ipairs(years) do		local link = subst(parse(tConfig.tours.link, year), "year", year) th(row):attr('scope', 'col'):addClass('unsortable') :wikitext(link and format("%s", link, year) or year) end if #rows == 0 then tooltip(th(row):attr('scope', 'col'), nil, "Strike rate", "SR", tooltipSpec) tooltip(th(row):attr('scope', 'col'), nil, "Win–Loss", "W–L", tooltipSpec) th(row):attr('scope', 'col'):wikitext("Win %") end insert(rows, row)

-- Add hatnote if needed. local headerText = {} if years.last and data.last and tournaments[data.last] then local tInfo = tournaments[data.last] local annualLink = {link = annualTournamentLink(years.last, nil, tInfo)} transform(annualLink, "link", data, data.last, years.last) local hatnote = {} insert(hatnote, "This table includes results through the conclusion of the ")		end		insert(hatnote, annualName)		insert(hatnote, ".") insert(headerText, concat(hatnote)) end insert(headerText, "{|class=\"plainrowheaders wikitable sortable\" style=text-align:center") return concat(headerText, "\n"), rows, headerCell end

local function outputSpan(row, span, seenRounds) local cell = td(row):attr('colspan', span.span) tooltip(cell, nil,		span.span < span.info.minSpellCols and span.info.tooltip,		span.span >= span.info.minSpellCols and span.info.tooltip or span.round,		span.info) if seenRounds and span.span < span.info.minSpellCols then seenRounds[span.info.round] = span.info end end

local footnoteCount

-- Return the year a given tournament was first held. local function eventFirstYear(entries, yearInfos, year, yearEntry) year = tonumber(year) local startYear if #entries > 0 then local endYear = entries[#entries][4] for testYear = endYear + 1, year do			local testYearTournament = parse(yearInfos, testYear) if testYearTournament ~= entries[#entries][1] then entries[#entries][4] = testYear - 1 break; end end for testYear = year, endYear, -1 do			local testYearTournament = parse(yearInfos, testYear) if testYearTournament ~= yearEntry then startYear = testYear + 1 break; end end end return startYear end

local frame_

-- Format a footnote. local function footnoteText(footnoteChunks, wikilinkFn) if #footnoteChunks > 1 then local footnote = {} local footnoteChunk = {"Held as"} for pos,entry in ipairs(footnoteChunks) do			local link, name, abbr = wikilinkFn(entry) insert(footnoteChunk, format("%s%s", link and link .. "|" or "", name)) -- Add abbr if doesn't appear in name if abbr and not name:match(abbr) then insert(footnoteChunk, "(" .. abbr .. ")")			end if entry[3] == entry[4] then -- One-year event insert(footnoteChunk, "in") insert(footnoteChunk, entry[3]) else if pos > 1 then insert(footnoteChunk, "from") insert(footnoteChunk, entry[3]) end if pos < #footnoteChunks then insert(footnoteChunk, pos == 1 and "until" or "to") insert(footnoteChunk, entry[4]) end end insert(footnote, concat(footnoteChunk, (" "))) footnoteChunk = {} end footnote[#footnote] = "and " .. footnote[#footnote] return frame_:callParserFunction{name = '#tag:ref', args = { concat(footnote, #footnote > 2 and ", " or " "), group = "lower-alpha" }}	end end

local function winLossStatsSummary(row, stats, boldmarkup, summaryRowspan) boldmarkup = boldmarkup or "" -- &#8722; is (nonbreaking) minus sign. local srCell = td(row):attr('data-sort-value', stats.count > 0 and stats.champs / stats.count or -1) :addClass('nowrap') :wikitext(format("%s%d / %d%s", boldmarkup, stats.champs, stats.count, boldmarkup)) local matches = stats.wins + stats.losses local wlCell = td(row):attr('data-sort-value', matches > 0 and stats.wins / matches or -1) local wrCell = td(row) if summaryRowspan then wlCell:attr('rowspan', summaryRowspan) wrCell:attr('rowspan', summaryRowspan) srCell:attr('rowspan', summaryRowspan) end if matches > 0 then wlCell:wikitext(format("%s%d–%d%s", boldmarkup, stats.wins, stats.losses, boldmarkup)) wlCell:addClass("nowrap") wrCell:wikitext(format("%s%.2f%%%s", boldmarkup, stats.wins * 100 / matches, boldmarkup)) else wlCell:wikitext(format("%s–%s", boldmarkup, boldmarkup)) wrCell:attr('data-sort-value', '-1%') :wikitext(format("%s–%s", boldmarkup, boldmarkup)) end end

-- Prepare a Win-Loss row in the performance timeline table. local function winLossStatsRow(row, info, stats, bold, summaryRowspan) local boldmarkup = bold and "'''" or "" local headerName = info.name and info.name .. " " or "" th(row):attr('scope', 'row') :wikitext(format("%s%sWin–Loss%s", boldmarkup, headerName, boldmarkup)) local span for _,year in ipairs(years) do		local yStats = stats[year] local display = {} if yStats and yStats.wins + yStats.losses > 0 then display.text = format("%s%d–%d%s", boldmarkup, yStats.wins, yStats.losses, boldmarkup) bg_col = 'background-color:#EAECF0' else display.text = format("%s–%s", boldmarkup, boldmarkup) bg_col = 'background-color:#EAECF0' if info.absence then local aConfig = parse(info.absence, year) if aConfig then display.text = aConfig.round display.config = aConfig end end end if span then if span.info.round == display.text then span.span = span.span + 1 else outputSpan(row, span) span = nil end end if not span then if display.config and display.config.span then span = {round = display.text, span = 1, info = display.config} else td(row):wikitext(display.text) :addClass("nowrap") end end end if span then outputSpan(row, span) span = nil end winLossStatsSummary(row, stats, boldmarkup, summaryRowspan) end

-- Return true if the player appears in a given tournament. local function hasTournamentAppearance(tournament) local tournamentType = type(tournament) if tournamentType == "string" then if data[tournament] then return true end elseif tournamentType == "table" then if tournament.type == "chrono" then for _,entry in ipairs(tournament) do				if data[entry[2]] then return true end end if data[tournament.default] then return true end else -- TODO other table type end end return false end

-- Create a fresh statistics table. local function statsFactory return {count = 0, champs = 0, finals = 0, wins = 0, losses = 0} end

-- Generate performance timeline rows for a given tournament level. local function body(level, levelHeaderCell) local entries = {} local stats = statsFactory local levelInfo = parse(tOrders[level]) local levelInfos = {} local levelLastAppearance for pos,tournament in ipairs(levelInfo) do		if hasTournamentAppearance(tournament) then local tStats = statsFactory local row = tr if #entries == 0 and data.categories.count > 1 then levelHeaderCell = th(row):attr('scope', 'row') :css('width', '6.5em') :css('max-width', '10em') end local headerCell = th(row):attr('scope', 'row') local tInfos = {} local lastAppearance local seenRounds = {} local span local country = data.country.default for _,year in ipairs(years) do				country = data.country[year] or country local yearLevelName = parse(levelInfo.name or levelInfo.tooltip, year) if #entries == 0 and (#levelInfos == 0 or levelInfos[#levelInfos][1] ~= yearLevelName) then -- Add footnote noting series transition. local startYear = eventFirstYear(levelInfos, levelInfo.name or levelInfo.tooltip, year, yearLevelName) insert(levelInfos, {						yearLevelName,						{link = parse(levelInfo.link, year), abbr = parse(levelInfo.abbr, year)},						startYear, tonumber(year)					}) else levelInfos[#levelInfos][4] = tonumber(year) end local yearTournament = parse(tournament, year) local tInfo = checkNonNil(tournaments[yearTournament], "entry for " .. yearTournament) if #tInfos == 0 or tInfos[#tInfos][2] ~= tInfo then -- Add footnote noting tournament transition. local startYear = eventFirstYear(tInfos, tournament, year, yearTournament) insert(tInfos, {yearTournament, tInfo, startYear, tonumber(year)}) else tInfos[#tInfos][4] = tonumber(year) end local display = {} if data[yearTournament] and data[yearTournament][year] then if not levelLastAppearance or levelLastAppearance < year then levelLastAppearance = year end lastAppearance = tInfo local tyStats = data[yearTournament][year] if not rounds[tyStats.round].nocount then tStats.count = tStats.count + 1 stats.count = stats.count + 1 end if rounds[tyStats.round].strike then tStats.champs = tStats.champs + 1 stats.champs = stats.champs + 1 end if not stats[year] then stats[year] = statsFactory end tStats.wins = tStats.wins + tyStats.wins stats[year].wins = stats[year].wins + tyStats.wins stats.wins = stats.wins + tyStats.wins tStats.losses = tStats.losses + tyStats.losses stats[year].losses = stats[year].losses + tyStats.losses stats.losses = stats.losses + tyStats.losses local annualLink = annualTournamentLink(year, tyStats, tInfo) display.round = tyStats.round display.group = tyStats.group display.link = annualLink transform(display, "link", data, yearTournament, year, country) else display.round = parse(tInfo.absence, year) or "A" end local round = rounds[display.round] if round.absence then local absence = false if year < years.last or not data.last and tonumber(year) < curYear then absence = true elseif year == years.last and data.last and calendar[year] and calendar[year][data.gender] then local tWeek = calendar[year][data.gender].week[yearTournament] local lWeek = calendar[year][data.gender].week[data.last] if tWeek and lWeek and tWeek <= lWeek then absence = true end end if not absence then display.round = nil end end local roundInfo = {} setmetatable(roundInfo, {__index = rounds[display.round]}) transform(roundInfo, "tooltip", data, yearTournament, year) if roundInfo.group then roundInfo.round = display.round .. display.group else roundInfo.round = display.round end display.round = roundInfo.name or roundInfo.round if span then if span.round == display.round then span.span = span.span + 1 else outputSpan(row, span, seenRounds) span = nil end end if not span then if roundInfo.span then span = {round = display.round, span = 1, info = roundInfo} else local cell = td(row) if roundInfo.round then if roundInfo.bgcolor then cell:css('background', roundInfo.bgcolor) end tooltip(cell, display.link, roundInfo.tooltip, display.round, roundInfo) seenRounds[roundInfo.round] = roundInfo end end end end if span then outputSpan(row, span, seenRounds) span = nil end if lastAppearance then headerCell:wikitext(					format("%s%s", lastAppearance.link and lastAppearance.link .. "|" or lastAppearance.abbr and lastAppearance.name .. "|" or "", lastAppearance.abbr or lastAppearance.name)) if #tInfos > 1 then local footnote = footnoteText(tInfos,						function(entry)							local tInfo = entry[2]							return tInfo.link, tInfo.name, tInfo.abbr						end) headerCell:wikitext(footnote) footnoteCount = footnoteCount + 1 end winLossStatsSummary(row, tStats) insert(entries, row) for _,roundInfo in pairs(seenRounds) do					local round = roundInfo.name and not usedRounds[roundInfo.name] and roundInfo.name or roundInfo.round usedRounds[round] = roundInfo end end end end if #entries == 0 then return nil end if levelHeaderCell then local levelLink = parse(levelInfo.link, levelLastAppearance) local levelName = parse(levelInfo.name, levelLastAppearance) local levelAbbr = parse(levelInfo.abbr, levelLastAppearance) local levelTooltip = parse(levelInfo.tooltip, levelLastAppearance) local levelString = levelAbbr or levelName if data.categories.count > 1 then levelHeaderCell:attr('rowspan', #entries + 1) end tooltip(levelHeaderCell, levelLink or levelAbbr and levelName,			levelTooltip, levelString, {bold = true, abbr = true}) if #levelInfos > 1 then local footnote = footnoteText(levelInfos,				function(entry)					return entry[2].link, entry[1], entry[2].abbr				end) levelHeaderCell:wikitext(footnote) footnoteCount = footnoteCount + 1 end end

local row = tr winLossStatsRow(row, {}, stats, true) insert(entries, row) local result = {} for _,entry in ipairs(entries) do		insert(result, tostring(entry)) end return concat(result, "\n") end

-- Generate rows for career performance timeline. local function summary(envSummary, headerCell) local entries = {} local stats = statsFactory local environmentInfo = tOrders.environments local surfaceInfo = tOrders.surfaces local surfaceCount = 0 for _,environment in ipairs(environmentInfo) do		if data[environment] then for _,surface in ipairs(surfaceInfo) do				if data[environment][surface] then surfaceCount = surfaceCount + 1 end end end end if surfaceCount == 0 then return nil end -- Aggregate data. local eStats = {} local sStats = {} for _,env in ipairs(environmentInfo) do		if data[env] then for _,surface in ipairs(surfaceInfo) do				if data[env][surface] then for _,year in ipairs(years) do						local esyStats = data[env][surface][year] if esyStats then if not eStats[env] then eStats[env] = statsFactory end if not eStats[env][year] then eStats[env][year] = statsFactory end if not sStats[surface] then sStats[surface] = statsFactory end if not sStats[surface][year] then sStats[surface][year] = statsFactory end if not stats[year] then stats[year] = statsFactory end for key,value in pairs(esyStats) do								sStats[surface][year][key] = sStats[surface][year][key] + value sStats[surface][key] = sStats[surface][key] + value eStats[env][year][key] = eStats[env][year][key] + value eStats[env][key] = eStats[env][key] + value stats[year][key] = stats[year][key] + value stats[key] = stats[key] + value end end end end end end end local function venueWinLossStatsRow(venues, vInfo, vStats) for _,venue in ipairs(venues) do			if vStats[venue] then local row = tr if #entries == 0 and data.categories.count > 1 then -- Add header cell. headerCell = th(row):attr('scope', 'row') end winLossStatsRow(row, vInfo[venue], vStats[venue]) insert(entries, row) end end end venueWinLossStatsRow(surfaceInfo, surfaces, sStats) if envSummary then venueWinLossStatsRow(environmentInfo, environments, eStats) end

local row = tr winLossStatsRow(row, {name = "Overall"}, stats, true, 2) insert(entries, row)

local row = tr local wrHdrCell = th(row):attr('scope', 'row'):wikitext("Win %") for _,year in ipairs(years) do		local cellContent = "–" if stats[year] then local wins = stats[year].wins local losses = stats[year].losses local matches = wins + losses if matches > 0 then cellContent = format("%.1f%%", wins * 100 / matches) end end td(row):wikitext(cellContent) end insert(entries, row) if headerCell then if data.categories.count > 1 then headerCell:attr('rowspan', #entries) end headerCell:wikitext("Career") end

local function counterStatsRow(row, name, type, bold) local boldmarkup = bold and "'''" or "" local rowHdrCell = th(row):attr('scope', 'row'):css('text-align', 'right') :wikitext(format("%s%s%s", boldmarkup, name, boldmarkup)) if data.categories.count > 1 then rowHdrCell:attr('colspan', 2) end for _,year in ipairs(years) do			td(row):wikitext(format("%s%s%s", boldmarkup, stats[year] and tostring(stats[year][type]) or "–", boldmarkup)) end td(row):attr('colspan', 2) :wikitext(format("%s%d total%s", boldmarkup, stats[type], boldmarkup)) end

local row = tr:addClass('sortbottom') counterStatsRow(row, "Tournaments played", "count") td(row):wikitext(format("%.1f%%", stats.champs * 100 / stats.count)) insert(entries, row)

if stats.finals > 0 then local row = tr:addClass('sortbottom') counterStatsRow(row, "Finals reached", "finals") td(row):attr('rowspan', 2) :wikitext(format("%.1f%%", stats.champs * 100 / stats.finals)) insert(entries, row) local row = tr:addClass('sortbottom') counterStatsRow(row, "Titles", "champs", true) insert(entries, row) end

local row = tr:addClass('sortbottom') local yearEndHdrCell = th(row):attr('scope', 'row'):css('text-align', 'right') :wikitext("Year-end ranking") if data.categories.count > 1 then yearEndHdrCell:attr('colspan', 2) end for _,year in ipairs(years) do		local cell = td(row) if data.rank and data.rank[year] then local rank = data.rank[year] local rankConfig = tConfig.rankings[rank] or {} if rankConfig.bgcolor then cell:css('background', rankConfig.bgcolor) end local boldmarkup = rankConfig.bold and "'''" or "" cell:wikitext(format("%s%s%s", boldmarkup, rank, boldmarkup)) end end local cell = td(row):attr('colspan', 3) if data.prizemoney then tooltip(cell, nil, "Career prize money", data.prizemoney, {bold = true, abbr = true}) end insert(entries, row)

local result = {} for _,entry in ipairs(entries) do		insert(result, tostring(entry)) end

return concat(result, "\n") end

-- Generate wikitext to conclude performance timeline table, including footnotes. local function footer local result = {} insert(result, "|-\n|}") if footnoteCount > 0 then local reflistTag = mw.html.create("div") reflistTag:addClass("reflist"):css('list-style-type', 'lower-alpha') :wikitext(frame_:callParserFunction{				name = '#tag:references', args = {"", group = "lower-alpha"}			}) insert(result, tostring(reflistTag)) end return concat(result, "\n") end

-- Return true if the player appears in a given tournament level. local function hasLevelAppearance(level) local levelInfo = parse(tOrders[level]) for _,tournament in ipairs(levelInfo) do		if hasTournamentAppearance(tournament) then return true end end return false end

function p._main(args, frame) frame_ = frame data = {} years = {} usedRounds = {} footnoteCount = 0 data.gender = args.gender or "men" data.matchType = args.matchType or "singles" data.country = {} data.country.default = args.country or "UNK" local idx = 1 local environmentSummary = true local year while args[idx] do		local arg = args[idx] if arg == "year" then idx = idx + 1 year = checkYear(args[idx]) if years.last and year <= years.last then error(format("Nonincreasing year: %s appears after %s", year, years.last)) end years.last = year insert(years, year) elseif arg == "country" then idx = idx + 1 local country = checkNonNil(args[idx], year .. " country") data.country[year] = args[idx] elseif arg == "amateur" then years.amateur = year elseif arg == "professional" then years.professional = year elseif tournaments[arg] then local tournament = arg local diagMsg = year .." " .. tournament local tStats = {} idx = idx + 1 local round = args[idx] -- Handle zones for Davis Cup. if round and round:sub(1, 2) == "WG" then tStats.round = "WG" tStats.group = round:sub(3) elseif round and round:sub(1, 2) == "PO" then tStats.round = "PO" tStats.group = round:sub(3) elseif round and round:sub(1, 1) == "Z" then tStats.round = "Z" tStats.group = checkNum(round:sub(2), diagMsg .. " zone") else tStats.round = round end checkMember(tStats.round, rounds, "round", diagMsg) idx = idx + 1 tStats.wins = checkNum(args[idx], diagMsg) idx = idx + 1 tStats.losses = checkNum(args[idx], diagMsg) if data[tournament] == nil then data[tournament] = {} end data[tournament][year] = tStats elseif environments[arg] then local environment = arg local diagMsg = year .. " " .. " " .. environment idx = idx + 1 local surface = checkNonNil(args[idx], diagMsg .. " surface") if surfaces[surface] then diagMsg = diagMsg .. " " .. surface local sStats = {} idx = idx + 1 sStats.count = checkNum(args[idx], diagMsg) idx = idx + 1 sStats.wins = checkNum(args[idx], diagMsg) idx = idx + 1 sStats.losses = checkNum(args[idx], diagMsg) idx = idx + 1 sStats.champs = checkNum(args[idx], diagMsg) idx = idx + 1 sStats.finals = sStats.champs + checkNum(args[idx], diagMsg) if data[environment] == nil then data[environment] = {} end if data[environment][surface] == nil then data[environment][surface] = {} end data[environment][surface][year] = sStats else error(format("Unknown surface (%s %s): %s", year, environment, arg)) end elseif surfaces[arg] then local surface = arg local diagMsg = year .. " " .. surface local sStats = {} idx = idx + 1 sStats.count = checkNum(args[idx], diagMsg) idx = idx + 1 sStats.wins = checkNum(args[idx], diagMsg) idx = idx + 1 sStats.losses = checkNum(args[idx], diagMsg) idx = idx + 1 sStats.champs = checkNum(args[idx], diagMsg) idx = idx + 1 sStats.finals = sStats.champs + checkNum(args[idx], diagMsg) if data.outdoor == nil then data.outdoor = {} end if data["outdoor"][surface] == nil then data["outdoor"][surface] = {} end data["outdoor"][surface][year] = sStats -- Disable summary by environment. environmentSummary = false elseif arg == "rank" then idx = idx + 1 if data.rank == nil then data.rank = {} end data.rank[year] = checkNum(args[idx], year .. " rank") else error(format("Unknown argument at position %d (%s): %s", idx, year, arg)) end idx = idx + 1 end data.prizemoney = args.prizemoney data.last = args.last data.categories = {} if args.types then local count = 0 for _,type in ipairs(mw.text.split(args.types, ",")) do			if type == "Career" or tConfig.orders[type] and hasLevelAppearance(type) then data.categories[type] = true count = count + 1 end end data.categories.count = count else local count = 0 for _,type in ipairs(parse(tConfig.orders.order)) do			if hasLevelAppearance(type) then data.categories[type] = true count = count + 1 end end data.categories.Career = true data.categories.count = count + 1 end local result = {} local tableHeader, headerRows, headerCell = header insert(result, tableHeader) local function insertHeaderRowsIfNeeded if #result == 1 then for _,headerRow in ipairs(headerRows) do				insert(result, tostring(headerRow)) end end end for _,level in ipairs(parse(tConfig.orders.order)) do		if data.categories[level] then local levelRows = body(level, data.categories.count == 1 and headerCell) insertHeaderRowsIfNeeded insert(result, levelRows) end end if data.categories.Career then local careerRows = summary(environmentSummary, data.categories.count == 1 and headerCell) insertHeaderRowsIfNeeded if careerRows then insert(result, careerRows) end end insert(result, footer) return concat(result, "\n") end

function p.main(frame) -- Import module function to work with passed arguments local getArgs = require('Module:Arguments').getArgs local args = getArgs(frame) return p._main(args, frame) end

return p