Documentation for this module may be created at Module:CurrentRostersPortal/doc
local util_args = require('Module:ArgsUtil')
local util_cargo = require("Module:CargoUtil")
local util_esports = require("Module:EsportsUtil")
local util_html = require("Module:HtmlUtil")
local util_map = require("Module:MapUtil")
local util_news = require("Module:NewsUtil")
local util_source = require("Module:SourceUtil")
local util_table = require("Module:TableUtil")
local util_text = require("Module:TextUtil")
local util_title = require("Module:TitleUtil")
local util_vars = require("Module:VarsUtil")
local i18n = require('Module:i18nUtil')
local ListOfRoles = require('Module:ListOfRoles')
local OD = require('Module:OrderedDict')
local m_team = require('Module:Team')
local Role = require('Module:Role')
local PARITY
local OVER = false
local PRELOADS_TO_IGNORE = {
'opportunities', 'set_to_leave', 'to_main_also_stay', 'to_academy_also_stay', 'gcd_remove_notleave',
join = {},
leave = {},
}
local h = {}
local p = {}
function p.intro(frame)
local args = util_args.merge()
util_vars.setVar('season1', args[1])
util_vars.setVar('season2', args[2])
return '<!-- -->'
end
function p.main(frame)
local args = util_args.merge()
i18n.init('CurrentRostersPortal')
h.castArgs(args)
h.setConstants(args)
local playersBefore = util_news.getPlayersFromArg(args.players)
h.addTagsToPlayersBefore(playersBefore)
local processed = h.getProcessedDataFromPlayers(playersBefore)
local listOfChanges = h.queryForChanges(args, playersBefore)
h.formatDataFromQuery(listOfChanges)
local netStatuses = h.computeNetStatuses(listOfChanges)
h.addChangesToProcessedPlayersList(processed, netStatuses)
h.moveJoiningPlayersToEmptySlots(processed)
return h.makeOutput(processed, args)
end
function h.castArgs(args)
args.team = m_team.teamlinkname(args.team)
args.team2 = args.team2 and m_team.teamlinkname(args.team2)
args.over = util_args.castAsBool(args.over)
end
function h.setConstants(args)
OVER = args.over
end
function h.addTagsToPlayersBefore(playersBefore)
h.mergeTagsIntoPlayersBefore(playersBefore, h.queryTagsForPlayersBefore(playersBefore))
end
function h.mergeTagsIntoPlayersBefore(playersBefore, newData)
for _, player, playerData in ipairs(playersBefore) do
util_table.merge(playerData, newData[mw.ustring.lower(player)])
end
end
function h.queryTagsForPlayersBefore(playersBefore)
return util_cargo.getRowDict(h.getBeforeTagsQuery(playersBefore), 'PlayerPageOrRedirect')
end
function h.getBeforeTagsQuery(playersBefore)
local query = {
tables = { 'Players=P', 'PlayerRedirects=PR' },
join = { 'P._pageName=PR._pageName' },
where = h.getBeforeTagsWhere(playersBefore),
fields = {
'Residency [region]',
'PR.AllName=PlayerPageOrRedirect [unicodelowercase]',
},
}
return query
end
function h.getBeforeTagsWhere(playersBefore)
return playersBefore:formatAndConcatKeys(' OR ', 'PR.AllName="%s"')
end
-- args for before
function h.getProcessedDataFromPlayers(playersBefore)
local processed = {
playerLookup = {},
byRole = h.initProcessedByRole()
}
for _, _, playerData in ipairs(playersBefore) do
h.addBeforePlayerToRole(processed, playerData)
end
return processed
end
function h.initProcessedByRole()
local ret = OD()
for _, role in ipairs(ListOfRoles.current_rosters_list) do
ret:set(role, {})
end
return ret
end
function h.addBeforePlayerToRole(processed, playerData)
if not processed.byRole:get(playerData.Role) then
h.initProcessedByRoleForUntrackedRole(processed.byRole, playerData.Role)
end
local roleData = processed.byRole:get(playerData.Role)
roleData[#roleData+1] = { before = playerData }
processed.playerLookup[playerData.PlayerLink] = {
role = playerData.Role,
i = #roleData,
Residency = playerData.Residency
}
end
function h.initProcessedByRoleForUntrackedRole(byRole, role)
byRole:set(role, { untracked = true })
end
-- query for after
function h.queryForChanges(args, playersBefore)
return util_cargo.queryAndCast(h.getChangesQuery(args, playersBefore))
end
function h.getChangesQuery(args, playersBefore)
local query = {
fields = {
'RC.Date_Sort',
'RC.Player',
'PR._pageName=PlayerLink',
'RC.Team',
'RC.Role',
'RC.Source',
'RC.Preload',
'RC.Direction',
'News.Date_Display',
'News.Region',
'News._pageName',
'News.NewsId',
'P.Residency [region]',
},
tables = {
'RosterChanges=RC',
'NewsItems=News',
'PlayerRedirects=PR',
'Players=P',
'RosterChangePortalDates=Dates',
},
join = {
'RC.NewsId=News.NewsId',
'RC.Player=PR.AllName',
'PR._pageName=P._pageName',
'News.Region=Dates.Region'
},
where = h.getChangesWhere(args, playersBefore),
orderBy = 'RC.Date_Sort ASC, News.N_LineInDate ASC, RC.N_LineInNews ASC',
}
return query
end
function h.getChangesWhere(args, playersBefore)
local tbl = {
h.getTeamWhereCondition(args),
'PR._pageName IS NOT NULL',
'News.ExcludePortal IS NULL OR News.ExcludePortal != "1"',
-- disallow trainees here. TODO: put this into PRELOADS_TO_IGNORE
'RC.RoleModifier != "Trainee" OR RC.RoleModifier IS NULL',
util_news.getExcludedPreloadsWhereCondition(PRELOADS_TO_IGNORE),
}
util_table.mergeArrays(tbl, h.getDateWhereCondition(args))
return util_cargo.concatWhere(tbl)
end
function h.getTeamWhereCondition(args)
local tbl = {
('RC.Team="%s"'):format(args.team),
util_cargo.whereFromArg('RC.Team="%s"', args.team2)
}
return util_cargo.concatWhereOr(tbl)
end
function h.getDateWhereCondition(args)
local ret = {
('Dates.PeriodName="%s"'):format(args.period),
'RC.Date_Sort > Dates.DateStart',
'RC.Date_Sort < Dates.DateEnd',
}
return ret
end
function h.formatDataFromQuery(listOfChanges)
util_map.rowsInPlace(listOfChanges, h.formatOneRowOfDataFromQuery)
end
function h.formatOneRowOfDataFromQuery(row)
if not row.Direction then
error(i18n.print('error_missingDirection', row.NewsId))
end
for _, v in ipairs({ 'Role', 'Team' }) do
row[v .. row.Direction] = row[v]
end
end
function h.computeNetStatuses(listOfChanges)
local currentPlayerStatuses = {}
for _, row in ipairs(listOfChanges) do
currentPlayerStatuses[row.PlayerLink] = row
end
return currentPlayerStatuses
end
-- concat before & after data
function h.addChangesToProcessedPlayersList(processed, netStatuses)
for player, status in pairs(netStatuses) do
h.updatePlayerWithStatus(processed, player, status)
end
end
function h.updatePlayerWithStatus(processed, player, status)
local oldIndex = processed.playerLookup[player]
if not oldIndex then
h.addNewAfterPlayer(processed.byRole, status, 'join')
return
end
h.updateOldLocation(
processed.byRole,
processed.byRole:get(oldIndex.role)[oldIndex.i],
status
)
end
function h.addNewAfterPlayer(byRole, status, statusAfter)
-- only actually add something if the player is on the team at the end
if not status.TeamJoin then return end
h.addNewAfterPlayerToLocation(byRole:get(status.RoleLeave or status.RoleJoin), status, statusAfter)
end
function h.addNewAfterPlayerToLocation(location, status, statusAfter)
if not location then return end
location[#location+1] = { after = status, statusAfter = statusAfter }
end
function h.updateOldLocation(byRole, row, status)
if not status.TeamJoin or status.Preload == 'loaned_to' then
row.statusBefore = 'leave'
return
end
if status.RoleJoin ~= row.before.Role then
row.statusBefore = 'roleswap'
h.addNewAfterPlayer(byRole, status, 'roleswap')
return
end
row.after = status
row.skip = true
row.statusBefore = 'stay'
end
function h.moveJoiningPlayersToEmptySlots(processed)
for _, role, roleData in ipairs(processed.byRole) do
h.moveJoiningPlayersToEmptySlotsInRole(roleData)
end
end
function h.moveJoiningPlayersToEmptySlotsInRole(roleData)
local emptySlots = h.getListOfEmptySlots(roleData)
local rowsToRemove = {}
for i, row in ipairs(roleData) do
if not row.before and next(emptySlots) then
rowsToRemove[#rowsToRemove+1] = i
local oldSlot = table.remove(emptySlots, 1)
util_table.merge(roleData[oldSlot], row)
end
end
h.removeConsolidatedRows(roleData, rowsToRemove)
end
function h.getListOfEmptySlots(roleData)
local emptySlots = {}
for i, row in ipairs(roleData) do
if row.statusBefore == 'leave' or row.statusBefore == 'roleswap' then
emptySlots[#emptySlots+1] = i
end
end
return emptySlots
end
function h.removeConsolidatedRows(roleData, rowsToRemove)
for i = #rowsToRemove, 1, -1 do
table.remove(roleData, rowsToRemove[i])
end
end
-- print
function h.makeOutput(processed, args)
local output = mw.html.create()
local tbl = output:tag('table')
:addClass('wikitable2')
:addClass('rosterswap-current')
h.printHeading(tbl, args)
h.printRoles(tbl, processed)
return output
end
function h.printHeading(tbl, args)
h.printTopLevelHeading(tbl, args)
h.printSeasons(tbl)
end
function h.printTopLevelHeading(tbl, args)
if args.team2 then
h.printHeadingTwoTeams(tbl, args)
return
end
h.printHeadingOneTeam(tbl, args)
end
function h.printHeadingTwoTeams(tbl, args)
local tr = tbl:tag('tr')
tr:tag('th'):attr('rowspan',2)
:addClass('rosterswap-current-teamlogo-corner')
tr:tag('th')
:wikitext(m_team.onlyimagelinked(args.team))
:addClass('rosterswap-current-old')
tr:tag('th')
:wikitext(m_team.onlyimagelinked(args.team2))
:addClass('rosterswap-current-new')
end
function h.printHeadingOneTeam(tbl, args)
local tr = tbl:tag('tr')
tr:tag('th'):attr('rowspan',2):wikitext(m_team.onlyimagelinked(args.team))
:addClass('rosterswap-current-teamlogo-corner')
tr:tag('th'):attr('colspan',2):wikitext(m_team.mediumplainlinked(args.team))
end
function h.printSeasons(tbl)
local tr = tbl:tag('tr')
tr:tag('th')
:addClass('rosterswap-current-season')
:wikitext(util_vars.getVar('season1') or i18n.print('before'))
tr:tag('th')
:addClass('rosterswap-current-season')
:addClass('rosterswap-current-new')
:wikitext(util_vars.getVar('season2') or i18n.print('after'))
end
function h.printRoles(tbl, processed)
for i, role, data in ipairs(processed.byRole) do
PARITY = i % 2
h.printOneRole(tbl, role, data)
end
end
function h.printOneRole(tbl, role, data)
if data.untracked then return end
local tr = tbl:tag('tr')
:addClass('rosterswap-current-firstline')
h.printRowParityClass(tr)
h.printRowLabel(tr, role, data)
h.printRolePlayers(tbl, tr, data)
end
function h.printRowParityClass(tr)
tr:addClass('rosterswap-current-parity-' .. PARITY)
end
function h.printRowLabel(tr, role, data)
local td = tr:tag('td')
:wikitext(Role(role):get('portal'))
if #data == 0 then return end
td:attr('rowspan', #data)
td:addClass('rosterswap-current-rolename')
end
function h.printRolePlayers(tbl, tr, data)
if #data == 0 then
h.printEmptyRow(tr)
return
end
h.printNonemptyRow(tbl, tr, data)
end
function h.printEmptyRow(tr)
h.printOneRolePlayer(tr, {})
end
function h.printNonemptyRow(tbl, tr, data)
for i, row in ipairs(data) do
if i == 1 then
h.printOneRolePlayer(tr, row)
else
tr = tbl:tag('tr')
h.printRowParityClass(tr)
h.printOneRolePlayer(tr, row)
end
end
end
function h.printOneRolePlayer(tr, row)
h.printBefore(tr, row, row.statusBefore, row.before)
h.printAfter(tr, row, row.statusAfter, row.after)
end
function h.printBefore(tr, row, status, data)
local td = tr:tag('td')
:addClass('rosterswap-current-old')
if not data then return end
td:addClass(h.getStatusClass(status))
h.setCellAsStayIfNeeded(td, status, row)
h.printPlayer(td, status, data)
end
function h.getStatusClass(status)
if not status then return nil end
return ('rosterswap-current-%s'):format(status)
end
function h.setCellAsStayIfNeeded(td, status, row)
if status == 'leave' then return end
if status == 'stay' or (OVER and not row.after) then
h.setCellAsStay(td)
end
end
function h.setCellAsStay(td)
td:attr('colspan', 2)
end
function h.printAfter(tr, row, status, data)
if h.doWeSkipAfter(row, data) then return end
local td = tr:tag('td')
:addClass('rosterswap-current-new')
if not data then return end
h.printPlayer(td, status, data)
end
function h.doWeSkipAfter(row, data)
if row.skip then return true end
if data then return false end
if row.status == 'leave' then return false end
return OVER
end
function h.printPlayer(td, status, playerData)
td:addClass(h.getStatusClass(status))
if playerData.Residency then
td:wikitext(playerData.Residency:image())
end
td:wikitext(util_esports.playerLinked(playerData.Player))
end
return p