From bad9ae918c6198c5b75377e97feebb5db2af4c1c Mon Sep 17 00:00:00 2001 From: mbergen Date: Fri, 28 Feb 2025 18:06:22 +0100 Subject: [PATCH 01/29] WIP: Add first draft of SquadAuto rewrite --- lua/wikis/commons/Squad/squad_auto.lua | 356 +++++++++++++++++++++++++ 1 file changed, 356 insertions(+) create mode 100644 lua/wikis/commons/Squad/squad_auto.lua diff --git a/lua/wikis/commons/Squad/squad_auto.lua b/lua/wikis/commons/Squad/squad_auto.lua new file mode 100644 index 00000000000..e967c29faa3 --- /dev/null +++ b/lua/wikis/commons/Squad/squad_auto.lua @@ -0,0 +1,356 @@ +--- +-- @Liquipedia +-- wiki=commons +-- page=Module:Squad/Auto +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +local Array = require('Module:Array') +local Class = require('Module:Class') +local Condition = require('Module:Condition') +local FnUtil = require('Module:FnUtil') +local Json = require('Module:Json') +local Logic = require('Module:Logic') +local Lpdb = require('Module:Lpdb') +local Table = require('Module:Table') +local Operator = require('Module:Operator') + +local SquadUtils = require('Module:Squad/Utils') +local SquadCustom = require('Module:Squad/Custom') + +local BooleanOperator = Condition.BooleanOperator +local Comparator = Condition.Comparator + +---@class SquadAuto +---@field args table +---@field config SquadAutoConfig +---@field manualPlayers table? +---@field manualTimeline table? +---@field playersTeamHistory table +local SquadAuto = Class.new(nil, function (self, args) + self.args = arg +end) + +---@class SquadAutoTeam +---@field team string +---@field role string? +---@field position string? +---@field date string? + +---TODO: Unify with SquadPerson +---@class SquadAutoPerson +---@field id string +---@field flag string? +---@field idleavedate string? +---@field page string +---@field thisTeam SquadAutoTeam +---@field oldTeam SquadAutoTeam? +---@field newTeam SquadAutoTeam? +---@field joindate string +---@field joindatedisplay string? +---@field joindateRef table? +---@field leavedate string? +---@field leavedatedisplay string? +---@field leavedateRef table? +---@field faction string? + +---@class SquadAutoConfig +---@field team string +---@field status SquadStatus +---@field type SquadType +---@field title string? +---@field teams string[]? + +---@enum TransferType +SquadAuto.TransferType = { + LEAVE = 'LEAVE', + JOIN = 'JOIN', + CHANGE = 'CHANGE', +} + +---@class TeamHistoryEntry +---@field pagename string +---@field displayname string +---@field flag string +---@field date string +---@field dateDisplay string +---@field type TransferType +---@field references table +---@field wholeTeam boolean +---@field position string + +---Entrypoint for the automated timelin +---TODO: Implement in submodule +function SquadAuto.timeline(args) + args.timeline = true + self:parseConfig(args) +end + +---Entrypoint for SquadAuto tables +---@param args table +function SquadAuto.run(args) + local autosquad = SquadAuto(args) + autosquad:parseConfig(args) + autosquad:queryTransfers() + + mw.logObject(autosquad:selectEntries()) + --return SquadCustom.runAuto(self:filterEntries(), self.config.status, self.config.type, self.config.title) +end + +---Parses the args into a SquadAutoConfig +---@param args table +function SquadAuto:parseConfig(args) + self.config = { + team = args.team or mw.title.getCurrentTitle().text, + status = SquadUtils.StatusToSquadStatus[args.status:lower()], + type = SquadUtils.TypeToSquadType[args.type:lower()], + title = args.title -- TODO: Switch to Former players instead of squad? + } + if args.timeline then + self.manualTimeline = self:readManualTimeline() + else + self.manualPlayers = self:readManualPlayers() + end + + local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) + if not historicalTemplates then + error("Missing team template: " .. self.config.team) + end + self.config.teams = Array.append(Array.extractValues(historicalTemplates), self.config.team) + + if self.config.status == SquadUtils.SquadStatus.FORMER_INACTIVE then + error("SquadStatus 'FORMER_INACTIVE' is not supported by SquadAuto.") + end +end + +function SquadAuto:readManualPlayers() +end + +function SquadAuto:readManualTimeline() +end + +function SquadAuto:queryTransfers() + ---Checks whether a given team is the currently queried team + ---@param team string? + ---@return boolean + local function isCurrentTeam(team) + if not team then + return false + end + return Array.find(self.config.teams, function(t) return t == team end) ~= nil + end + + ---@param side 'from' | 'to' + ---@param transfer transfer + ---@return string | nil, boolean + local function parseRelevantTeam(side, transfer) + local mainTeam = transfer[side .. 'teamtemplate'] + if mainTeam and isCurrentTeam(mainTeam) then + return mainTeam, true + end + + local secondaryTeam = transfer.extradata[side .. 'teamsectemplate'] + if secondaryTeam and isCurrentTeam(secondaryTeam) then + return secondaryTeam, false + end + + return nil, false + end + + ---Maps a transfer to a transfertype, with regards to the current team. + ---@param relevantFromTeam string? + ---@param relevantToTeam string? + ---@return TransferType + local function getTransferType(relevantFromTeam, relevantToTeam) + if relevantFromTeam then + if relevantToTeam then + return SquadAuto.TransferType.CHANGE + end + return SquadAuto.TransferType.LEAVE + end + return SquadAuto.TransferType.JOIN + end + + ---Parses the relevant role for the current team from a transfer + ---@param side 'from' | 'to' + ---@param transfer transfer + ---@param team string? + ---@param isMain boolean + ---@return string? + local function parseRelevantRole(side, transfer, team, isMain) + if not team then + return nil + end + + if isMain then + return side == 'from' and transfer.role1 or transfer.role2 + else + return side == 'from' and transfer.extradata.role1sec or transfer.extradata.role2sec + end + end + + --TODO: Cache transfers/teamhistory in pagevars + ---@type table + self.playersTeamHistory = {} + + Lpdb.executeMassQuery( + 'transfer', + { + conditions = self:buildConditions(), + order = 'date asc, objectname desc', + limit = 5000 + }, + function(record) + self.playersTeamHistory[record.player] = self.playersTeamHistory[record.player] or {} + record.extradata = record.extradata or {} + + + local relevantFromTeam, isFromMain = parseRelevantTeam('from', record) + local relevantToTeam, isToMain = parseRelevantTeam('to', record) + local transferType = getTransferType(relevantFromTeam, relevantToTeam) + + ---@type TeamHistoryEntry + local entry = { + type = transferType, + + -- Person related information + pagename = record.player, + displayname = record.extradata.displayname, + flag = record.nationality, + + -- Date and references + date = record.date, + dateDisplay = record.extradata.displaydate, + references = record.reference, + + -- Roles + fromRole = parseRelevantRole('from', record, relevantFromTeam, isFromMain), + toRole = parseRelevantRole('to', record, relevantToTeam, isToMain), + + -- Other + wholeTeam = Logic.readBool(record.wholeteam), + position = record.extradata.position, + } + + -- TODO: Skip this transfer if there is no relevant change + -- E.g. this is grabbed a secondary team, but only main team changed + table.insert(self.playersTeamHistory[record.player], entry) + end + ) + +end + +---Builds the conditions to fetch all transfers related +---to the given team, respecting historical templates. +---@return string +function SquadAuto:buildConditions() + local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) + + if not historicalTemplates then + error("Missing team template: " .. self.config.team) + end + + local conditions = Condition.Tree(BooleanOperator.any) + Array.forEach(Array.extendWith(Array.extractValues(historicalTemplates), self.config.team), function (templatename) + conditions:add{ + Condition.Node(Condition.ColumnName('fromteamtemplate'), Comparator.eq, templatename), + Condition.Node(Condition.ColumnName('extradata_fromteamsectemplate'), Comparator.eq, templatename), + Condition.Node(Condition.ColumnName('toteamtemplate'), Comparator.eq, templatename), + Condition.Node(Condition.ColumnName('extradata_toteamsectemplate'), Comparator.eq, templatename) + } + end) + + return conditions:toString() +end + +---comment +---@return table +function SquadAuto:selectEntries() + return Array.flatMap( + Array.extractValues(self.playersTeamHistory), + FnUtil.curry(self._selectHistoryEntries, self) + ) +end + +---Returns a function that maps a set of transfers to a list of +---SquadAutoPersons. +---Behavior depends on the current config: +---If the status is (in)active, then at most one entry will be returned +---If the status is former(_inactive), there might be multiple entries returned +---If the type does not match, no entries are returned +---@param entries TeamHistoryEntry[] +---@return SquadAutoPerson[] +function SquadAuto:_selectHistoryEntries(entries) + -- Select entries to match status + if self.config.status == SquadUtils.SquadStatus.ACTIVE + or self.config.status == SquadUtils.SquadStatus.INACTIVE then + -- Only most recent transfer is relevant + local last = entries[#entries] + if last.type == SquadAuto.TransferType.CHANGE + or last.type == SquadAuto.TransferType.JOIN then + -- When the last transfer is a leave transfer, the person wouldn't be (in)active + return {self:_mapToSquadAutoPerson(last)} + end + end + + if self.config.status == SquadUtils.SquadStatus.FORMER then + local history = {} + + local currentEntry = nil + for index, entry in ipairs(entries) do + if not currentEntry and entry.type ~= SquadAuto.TransferType.JOIN then + mw.log("Invalid transfer history for player " .. entry.pagename) + mw.logObject(entry, "Invalid entry: Missing previous JOIN") + end + end + + return history + end + + return {} +end + +---Maps one or a pair of TeamHistoryEntries to a single SquadAutoPerson +---@param joinEntry TeamHistoryEntry +---@param leaveEntry TeamHistoryEntry | nil +---@return SquadAutoPerson +function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) + leaveEntry = leaveEntry or {} + + ---@type SquadAutoPerson + local entry = { + page = joinEntry.pagename, + id = joinEntry.displayname, + flag = joinEntry.flag, + joindate = joinEntry.date, + joindatedisplay = joinEntry.dateDisplay, + joindateRef = joinEntry.references, + + idleavedate = joinEntry.dateDisplay, + leavedate = leaveEntry.date or nil, + leavedatedisplay = leaveEntry.dateDisplay, + leavedateRef = leaveEntry.references, + + thisTeam = { + --TODO + }, + oldTeam = { + --TODO + }, + newTeam = { + --TODO + }, + + faction = '' --TODO + } + + if leaveEntry and not entry.newTeam then + --TODO: Fetch next team for person + end + + return entry +end + + +return SquadAuto From c1f4fad73c8eb21f8562071ad719ae99a29fe427 Mon Sep 17 00:00:00 2001 From: mbergen Date: Fri, 28 Feb 2025 18:10:07 +0100 Subject: [PATCH 02/29] Add another TODO comment --- lua/wikis/commons/Squad/squad_auto.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/wikis/commons/Squad/squad_auto.lua b/lua/wikis/commons/Squad/squad_auto.lua index e967c29faa3..a697d04bac9 100644 --- a/lua/wikis/commons/Squad/squad_auto.lua +++ b/lua/wikis/commons/Squad/squad_auto.lua @@ -303,6 +303,10 @@ function SquadAuto:_selectHistoryEntries(entries) mw.log("Invalid transfer history for player " .. entry.pagename) mw.logObject(entry, "Invalid entry: Missing previous JOIN") end + + --TODO: add/merge entry with currentEntry + --Add currentEntry to history + --Reset currentEntry end return history From 00d286a7f9c35e6e2dbc87ad81532d350e00ba17 Mon Sep 17 00:00:00 2001 From: mbergen Date: Wed, 12 Mar 2025 15:03:39 +0100 Subject: [PATCH 03/29] Move file to new naming structure --- .../Squad/{squad_auto.lua => Auto.lua} | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) rename lua/wikis/commons/Squad/{squad_auto.lua => Auto.lua} (91%) diff --git a/lua/wikis/commons/Squad/squad_auto.lua b/lua/wikis/commons/Squad/Auto.lua similarity index 91% rename from lua/wikis/commons/Squad/squad_auto.lua rename to lua/wikis/commons/Squad/Auto.lua index a697d04bac9..93629cf0296 100644 --- a/lua/wikis/commons/Squad/squad_auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -6,6 +6,7 @@ -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- +local Arguments = require('Module:Arguments') local Array = require('Module:Array') local Class = require('Module:Class') local Condition = require('Module:Condition') @@ -28,8 +29,8 @@ local Comparator = Condition.Comparator ---@field manualPlayers table? ---@field manualTimeline table? ---@field playersTeamHistory table -local SquadAuto = Class.new(nil, function (self, args) - self.args = arg +local SquadAuto = Class.new(nil, function (self, frame) + self.args = Arguments.getArgs(frame) end) ---@class SquadAutoTeam @@ -44,6 +45,8 @@ end) ---@field flag string? ---@field idleavedate string? ---@field page string +---@field name string? +---@field localizedname string? ---@field thisTeam SquadAutoTeam ---@field oldTeam SquadAutoTeam? ---@field newTeam SquadAutoTeam? @@ -54,6 +57,7 @@ end) ---@field leavedatedisplay string? ---@field leavedateRef table? ---@field faction string? +---@field captain boolean? ---@class SquadAutoConfig ---@field team string @@ -82,29 +86,33 @@ SquadAuto.TransferType = { ---Entrypoint for the automated timelin ---TODO: Implement in submodule -function SquadAuto.timeline(args) - args.timeline = true - self:parseConfig(args) +function SquadAuto.timeline(frame) + -- return SquadAuto(frame):timeline() end ---Entrypoint for SquadAuto tables ----@param args table -function SquadAuto.run(args) - local autosquad = SquadAuto(args) - autosquad:parseConfig(args) +---@param frame table +function SquadAuto.run(frame) + local autosquad = SquadAuto(frame) + autosquad:parseConfig() autosquad:queryTransfers() - mw.logObject(autosquad:selectEntries()) - --return SquadCustom.runAuto(self:filterEntries(), self.config.status, self.config.type, self.config.title) + return autosquad:display() +end + +function SquadAuto:display() + local entries = self:selectEntries() + mw.logObject(entries) + return SquadCustom.runAuto(entries, self.config.status, self.config.type, self.config.title) end ---Parses the args into a SquadAutoConfig ----@param args table -function SquadAuto:parseConfig(args) +function SquadAuto:parseConfig() + local args = self.args self.config = { team = args.team or mw.title.getCurrentTitle().text, - status = SquadUtils.StatusToSquadStatus[args.status:lower()], - type = SquadUtils.TypeToSquadType[args.type:lower()], + status = SquadUtils.StatusToSquadStatus[(args.status or ''):lower()], + type = SquadUtils.TypeToSquadType[(args.type or ''):lower()], title = args.title -- TODO: Switch to Former players instead of squad? } if args.timeline then @@ -331,8 +339,8 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) joindatedisplay = joinEntry.dateDisplay, joindateRef = joinEntry.references, - idleavedate = joinEntry.dateDisplay, - leavedate = leaveEntry.date or nil, + idleavedate = leaveEntry.displayname, + leavedate = leaveEntry.date or '', leavedatedisplay = leaveEntry.dateDisplay, leavedateRef = leaveEntry.references, From ab979b51914690158fa49d9a1e44ba943475d64a Mon Sep 17 00:00:00 2001 From: mbergen Date: Sat, 22 Mar 2025 16:18:17 +0100 Subject: [PATCH 04/29] setting teams, adding annotations --- lua/wikis/commons/Squad/Auto.lua | 41 +++++++++++++++++++++++-------- lua/wikis/commons/Squad/Utils.lua | 29 +++++++++++++++++++--- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 93629cf0296..5a0c06d3161 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -73,7 +73,7 @@ SquadAuto.TransferType = { CHANGE = 'CHANGE', } ----@class TeamHistoryEntry +---@class (exact) TeamHistoryEntry ---@field pagename string ---@field displayname string ---@field flag string @@ -83,6 +83,10 @@ SquadAuto.TransferType = { ---@field references table ---@field wholeTeam boolean ---@field position string +---@field fromTeam string? +---@field fromRole string? +---@field toTeam string? +---@field toRole string? ---Entrypoint for the automated timelin ---TODO: Implement in submodule @@ -236,6 +240,9 @@ function SquadAuto:queryTransfers() fromRole = parseRelevantRole('from', record, relevantFromTeam, isFromMain), toRole = parseRelevantRole('to', record, relevantToTeam, isToMain), + fromTeam = relevantFromTeam, + toTeam = relevantToTeam, + -- Other wholeTeam = Logic.readBool(record.wholeteam), position = record.extradata.position, @@ -306,16 +313,25 @@ function SquadAuto:_selectHistoryEntries(entries) local history = {} local currentEntry = nil - for index, entry in ipairs(entries) do - if not currentEntry and entry.type ~= SquadAuto.TransferType.JOIN then - mw.log("Invalid transfer history for player " .. entry.pagename) - mw.logObject(entry, "Invalid entry: Missing previous JOIN") + + Array.forEach(entries, function (entry) + if not currentEntry then + if entry.type == SquadAuto.TransferType.JOIN then + currentEntry = entry + else + mw.log("Invalid transfer history for player " .. entry.pagename) + mw.logObject(entry, "Invalid entry: Missing previous JOIN. Skipping") + end + return end - --TODO: add/merge entry with currentEntry - --Add currentEntry to history - --Reset currentEntry - end + table.insert(history, self:_mapToSquadAutoPerson(currentEntry, entry)) + if entry.type == "CHANGE" then + currentEntry = entry + else + currentEntry = nil + end + end) return history end @@ -346,12 +362,17 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) thisTeam = { --TODO + team = joinEntry.toTeam, + role = joinEntry.toRole }, oldTeam = { --TODO + team = joinEntry.fromTeam, + role = joinEntry.fromRole }, newTeam = { - --TODO + team = leaveEntry.toTeam, + role = leaveEntry.toRole }, faction = '' --TODO diff --git a/lua/wikis/commons/Squad/Utils.lua b/lua/wikis/commons/Squad/Utils.lua index 29e577fb784..a605d8d5474 100644 --- a/lua/wikis/commons/Squad/Utils.lua +++ b/lua/wikis/commons/Squad/Utils.lua @@ -98,8 +98,8 @@ function SquadUtils.anyInactive(players) end) end ----@param player table ----@return table +---@param player SquadAutoPerson +---@return SquadPersonArgs function SquadUtils.convertAutoParameters(player) local newPlayer = Table.copy(player) local joinReference = TransferRefs.useReferences(player.joindateRef, player.joindate) @@ -122,7 +122,30 @@ function SquadUtils.convertAutoParameters(player) return newPlayer end ----@param args table +---@class SquadPersonArgs +---@field name string? +---@field id string? +---@field link string? +---@field flag string? +---@field position string? +---@field role string? +---@field captain string? +---@field igl string? +---@field newteam string? +---@field newteamrole string? +---@field newrole string? +---@field joindate string? +---@field leavedate string? +---@field inactivedate string? +---@field status string? +---@field type string? +---@field team string? +---@field teamrole string? +---@field newteamdate string? +---@field faction string? +---@field race string? + +---@param args SquadPersonArgs ---@return ModelRow function SquadUtils.readSquadPersonArgs(args) local function getTeamInfo(page, property) From 4bce5d40d52c64e8e3c4c27aa182b9b838c351dc Mon Sep 17 00:00:00 2001 From: mbergen Date: Sun, 6 Apr 2025 17:22:26 +0200 Subject: [PATCH 05/29] Add included/excluded roles --- lua/wikis/commons/Squad/Auto.lua | 90 ++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 5a0c06d3161..8f913c4c268 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -65,6 +65,7 @@ end) ---@field type SquadType ---@field title string? ---@field teams string[]? +---@field roles {excluded: string[], included: string[]} ---@enum TransferType SquadAuto.TransferType = { @@ -87,6 +88,31 @@ SquadAuto.TransferType = { ---@field fromRole string? ---@field toTeam string? ---@field toRole string? +---@field faction string? + +local DEFAULT_INCLUDED_ROLES = { + [SquadUtils.SquadType.PLAYER] = { + '', + 'Loan', + 'Substitute', + 'Trial', + 'Stand-in', + 'Uncontracted' + }, + [SquadUtils.SquadType.STAFF] = {}, +} + +local DEFAULT_EXCLUDED_ROLES = { + [SquadUtils.SquadType.PLAYER] = {}, + [SquadUtils.SquadType.STAFF] = { + '', + 'Loan', + 'Substitute', + 'Trial', + 'Stand-in', + 'Uncontracted' + }, +} ---Entrypoint for the automated timelin ---TODO: Implement in submodule @@ -113,11 +139,16 @@ end ---Parses the args into a SquadAutoConfig function SquadAuto:parseConfig() local args = self.args + local type = SquadUtils.TypeToSquadType[(args.type or ''):lower()] self.config = { team = args.team or mw.title.getCurrentTitle().text, status = SquadUtils.StatusToSquadStatus[(args.status or ''):lower()], - type = SquadUtils.TypeToSquadType[(args.type or ''):lower()], - title = args.title -- TODO: Switch to Former players instead of squad? + type = type, + title = args.title, -- TODO: Switch to Former players instead of squad? + roles = { + included = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.roles)) or DEFAULT_INCLUDED_ROLES[type], + excluded = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.not_roles)) or DEFAULT_EXCLUDED_ROLES[type], + } } if args.timeline then self.manualTimeline = self:readManualTimeline() @@ -134,6 +165,14 @@ function SquadAuto:parseConfig() if self.config.status == SquadUtils.SquadStatus.FORMER_INACTIVE then error("SquadStatus 'FORMER_INACTIVE' is not supported by SquadAuto.") end + + if self.config.status == SquadUtils.SquadStatus.INACTIVE then + table.insert(self.config.roles.included, 'Inactive') + else + table.insert(self.config.roles.excluded, 'Inactive') + end + + mw.logObject(self.config) end function SquadAuto:readManualPlayers() @@ -246,6 +285,7 @@ function SquadAuto:queryTransfers() -- Other wholeTeam = Logic.readBool(record.wholeteam), position = record.extradata.position, + faction = record.extradata.faction } -- TODO: Skip this transfer if there is no relevant change @@ -282,16 +322,40 @@ end ---comment ---@return table function SquadAuto:selectEntries() - return Array.flatMap( - Array.extractValues(self.playersTeamHistory), - FnUtil.curry(self._selectHistoryEntries, self) + return Array.filter( + Array.flatMap( + Array.extractValues(self.playersTeamHistory), + FnUtil.curry(self._selectHistoryEntries, self) + ), + ---@param entry SquadAutoPerson + function(entry) + local result = ( + Logic.isEmpty(self.config.roles.included) + or Array.any( + self.config.roles.included, + FnUtil.curry(Operator.eq, entry.thisTeam.role) + ) + ) and ( + Logic.isEmpty(self.config.roles.excluded) + or Array.all( + self.config.roles.excluded, + FnUtil.curry(Operator.neq, entry.thisTeam.role) + ) + ) + + if not result then + mw.logObject(entry, "Not included") + end + + return result + end ) end ---Returns a function that maps a set of transfers to a list of ---SquadAutoPersons. ----Behavior depends on the current config: ----If the status is (in)active, then at most one entry will be returned +---Behavior depends on therent config: +---If the status is (inive, then at most one entry will be returned ---If the status is former(_inactive), there might be multiple entries returned ---If the type does not match, no entries are returned ---@param entries TeamHistoryEntry[] @@ -321,6 +385,7 @@ function SquadAuto:_selectHistoryEntries(entries) else mw.log("Invalid transfer history for player " .. entry.pagename) mw.logObject(entry, "Invalid entry: Missing previous JOIN. Skipping") + mw.ext.TeamLiquidIntegration.add_category('SquadAuto with invalid player history') end return end @@ -363,19 +428,22 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) thisTeam = { --TODO team = joinEntry.toTeam, - role = joinEntry.toRole + role = joinEntry.toRole, + position = joinEntry.position }, oldTeam = { --TODO team = joinEntry.fromTeam, - role = joinEntry.fromRole + role = joinEntry.fromRole, }, newTeam = { team = leaveEntry.toTeam, - role = leaveEntry.toRole + role = leaveEntry.toRole, }, - faction = '' --TODO + -- From legacy: Prefer leaveEntry faction information + faction = leaveEntry.faction or joinEntry.faction, + race = leaveEntry.faction or joinEntry.faction } if leaveEntry and not entry.newTeam then From e31fcef26526206142941064a48c59bc46b71d69 Mon Sep 17 00:00:00 2001 From: mbergen Date: Thu, 10 Apr 2025 19:18:12 +0200 Subject: [PATCH 06/29] Start with manual input reading --- lua/wikis/commons/Squad/Auto.lua | 45 +++++++++++++++++++++++++++++--- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 8f913c4c268..c209eb86808 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -114,7 +114,7 @@ local DEFAULT_EXCLUDED_ROLES = { }, } ----Entrypoint for the automated timelin +---Entrypoint for the automated timeline ---TODO: Implement in submodule function SquadAuto.timeline(frame) -- return SquadAuto(frame):timeline() @@ -175,7 +175,48 @@ function SquadAuto:parseConfig() mw.logObject(self.config) end +---@return SquadAutoPerson[] function SquadAuto:readManualPlayers() + ---@type SquadAutoPerson[] + local players = {} + + -- TODO: Readd limit to roles? + Array.forEach(self.args, function (entry) + local player = Json.parseIfString(entry) + if Logic.isNotEmpty(player) then + table.insert(players, { + page = player.link or player.id, + id = player.id, + captain = Logic.readBoolOrNil(player.captain), + name = player.name, + localizedname = player.localizedname, + thisTeam = { + team = self.config.team, + role = player.role, + position = player.position + }, + newTeam = { + team = player.newteam, + role = player.newteamrole, + player.newteamdate + }, + flag = player.flag, + oldTeam = { + team = player.oldteam + }, + joindate = (player.joindate or ''):gsub('%?%?','01'), + joindatedisplay = player.joindate, + joindateRef = {}, + leavedate = (player.leavedate or ''):gsub('%?%?','01'), + leavedatedisplay = player.leavedate, + leavedateRef = {}, + faction = player.faction or player.race, + race = player.faction or player.race, + }) + end + end) + + return players end function SquadAuto:readManualTimeline() @@ -426,13 +467,11 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) leavedateRef = leaveEntry.references, thisTeam = { - --TODO team = joinEntry.toTeam, role = joinEntry.toRole, position = joinEntry.position }, oldTeam = { - --TODO team = joinEntry.fromTeam, role = joinEntry.fromRole, }, From 6778b949d4beadb16044f66620f6bec46ddf55cd Mon Sep 17 00:00:00 2001 From: mbergen Date: Thu, 8 May 2025 17:14:56 +0200 Subject: [PATCH 07/29] Implement tabs, manual player additions --- lua/wikis/commons/Squad/Auto.lua | 134 +++++++++++++++++++++++-------- 1 file changed, 101 insertions(+), 33 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index c209eb86808..ae3f1716ff1 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -14,8 +14,9 @@ local FnUtil = require('Module:FnUtil') local Json = require('Module:Json') local Logic = require('Module:Logic') local Lpdb = require('Module:Lpdb') -local Table = require('Module:Table') local Operator = require('Module:Operator') +local Table = require('Module:Table') +local Tabs = require('Module:Tabs') local SquadUtils = require('Module:Squad/Utils') local SquadCustom = require('Module:Squad/Custom') @@ -54,7 +55,7 @@ end) ---@field joindatedisplay string? ---@field joindateRef table? ---@field leavedate string? ----@field leavedatedisplay string? +---@field leavedatedisplay string ---@field leavedateRef table? ---@field faction string? ---@field captain boolean? @@ -114,12 +115,6 @@ local DEFAULT_EXCLUDED_ROLES = { }, } ----Entrypoint for the automated timeline ----TODO: Implement in submodule -function SquadAuto.timeline(frame) - -- return SquadAuto(frame):timeline() -end - ---Entrypoint for SquadAuto tables ---@param frame table function SquadAuto.run(frame) @@ -127,13 +122,10 @@ function SquadAuto.run(frame) autosquad:parseConfig() autosquad:queryTransfers() - return autosquad:display() -end + local entries = autosquad:selectEntries() + Array.forEach(entries, SquadAuto.enrichEntry) -function SquadAuto:display() - local entries = self:selectEntries() - mw.logObject(entries) - return SquadCustom.runAuto(entries, self.config.status, self.config.type, self.config.title) + return autosquad:display(entries) end ---Parses the args into a SquadAutoConfig @@ -144,16 +136,20 @@ function SquadAuto:parseConfig() team = args.team or mw.title.getCurrentTitle().text, status = SquadUtils.StatusToSquadStatus[(args.status or ''):lower()], type = type, - title = args.title, -- TODO: Switch to Former players instead of squad? + title = args.title, roles = { included = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.roles)) or DEFAULT_INCLUDED_ROLES[type], excluded = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.not_roles)) or DEFAULT_EXCLUDED_ROLES[type], } } - if args.timeline then - self.manualTimeline = self:readManualTimeline() - else - self.manualPlayers = self:readManualPlayers() + + self.manualPlayers = self:readManualPlayers() + + -- Override default 'Former Squad' title + if self.config.status == SquadUtils.SquadStatus.FORMER + and type == SquadUtils.SquadType.PLAYER + and not self.config.title then + self.config.title = 'Former Players' end local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) @@ -171,8 +167,71 @@ function SquadAuto:parseConfig() else table.insert(self.config.roles.excluded, 'Inactive') end +end + +---@param entries SquadAutoPerson[] +---@return Widget|Html|string? +function SquadAuto:display(entries) + if Logic.isEmpty(entries) then + return + end + + if self.config.status == SquadUtils.SquadStatus.FORMER then + return self:displayTabs(entries) + end + + entries = SquadAuto._sortEntries(entries) - mw.logObject(self.config) + return SquadCustom.runAuto(entries, self.config.status, self.config.type, self.config.title) +end + +---@param entries SquadAutoPerson[] +---@return Html|string? +function SquadAuto:displayTabs(entries) + local _, groupedEntries = Array.groupBy( + entries, + ---@param entry SquadAutoPerson + function (entry) + return entry.leavedate:match('(%d%d%d%d)') + end + ) + + ---@type table + local tabs = { + This = Table.size(groupedEntries), + removeEmptyTabs = true + } + + local idx = 1 + for year, group in Table.iter.spairs(groupedEntries) do + tabs['name' .. idx] = year + tabs['content' .. idx] = tostring(SquadCustom.runAuto( + SquadAuto._sortEntries(group), + self.config.status, + self.config.type, + self.config.title + )) + idx = idx + 1 + end + + return Tabs.dynamic(tabs) +end + +---@param entry SquadAutoPerson +function SquadAuto.enrichEntry(entry) + local personInfo = mw.ext.LiquipediaDB.lpdb('player', { + conditions = '[[pagename::' .. string.gsub(entry.page, ' ', '_') .. ']]', + limit = 1, + query = 'pagename, nationality, id, name, localizedname, extradata' + })[1] + + if personInfo then + entry.id = personInfo.id + entry.flag = personInfo.nationality + entry.name = personInfo.name + entry.localizedname = personInfo.localizedname + end + --TODO: Captain from pagevars? end ---@return SquadAutoPerson[] @@ -180,12 +239,13 @@ function SquadAuto:readManualPlayers() ---@type SquadAutoPerson[] local players = {} - -- TODO: Readd limit to roles? + -- TODO: Handle manual 'enrichments' for adding names + -- TODO: Readd limitations to specific roles? Array.forEach(self.args, function (entry) local player = Json.parseIfString(entry) if Logic.isNotEmpty(player) then table.insert(players, { - page = player.link or player.id, + page = player.link or player.id or player.name, id = player.id, captain = Logic.readBoolOrNil(player.captain), name = player.name, @@ -219,9 +279,6 @@ function SquadAuto:readManualPlayers() return players end -function SquadAuto:readManualTimeline() -end - function SquadAuto:queryTransfers() ---Checks whether a given team is the currently queried team ---@param team string? @@ -334,7 +391,6 @@ function SquadAuto:queryTransfers() table.insert(self.playersTeamHistory[record.player], entry) end ) - end ---Builds the conditions to fetch all transfers related @@ -360,13 +416,15 @@ function SquadAuto:buildConditions() return conditions:toString() end ----comment ----@return table +---@return SquadAutoPerson[] function SquadAuto:selectEntries() return Array.filter( - Array.flatMap( - Array.extractValues(self.playersTeamHistory), - FnUtil.curry(self._selectHistoryEntries, self) + Array.extend( + Array.flatMap( + Array.extractValues(self.playersTeamHistory), + FnUtil.curry(self._selectHistoryEntries, self) + ), + self.manualPlayers ), ---@param entry SquadAutoPerson function(entry) @@ -462,8 +520,8 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) joindateRef = joinEntry.references, idleavedate = leaveEntry.displayname, - leavedate = leaveEntry.date or '', - leavedatedisplay = leaveEntry.dateDisplay, + leavedate = leaveEntry.date, + leavedatedisplay = leaveEntry.dateDisplay or '', leavedateRef = leaveEntry.references, thisTeam = { @@ -492,5 +550,15 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) return entry end +---Sorts a list of SquadAutoPersons +-- Active entries (no leavedate) sorted by joindate, +-- Former entries sorted by leavedate +---@param entries SquadAutoPerson[] +---@return unknown[] +function SquadAuto._sortEntries(entries) + return Array.sortBy(entries, function (element) + return {element.leavedate or element.joindate, element.id} + end) +end return SquadAuto From 0beb652e5b8ab19ff46a75fd2355175bc264fb3d Mon Sep 17 00:00:00 2001 From: mbergen Date: Thu, 8 May 2025 18:55:43 +0200 Subject: [PATCH 08/29] Support type inactive via default roles --- lua/wikis/commons/Squad/Auto.lua | 33 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index ae3f1716ff1..d88e9af1a32 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -93,12 +93,17 @@ SquadAuto.TransferType = { local DEFAULT_INCLUDED_ROLES = { [SquadUtils.SquadType.PLAYER] = { - '', - 'Loan', - 'Substitute', - 'Trial', - 'Stand-in', - 'Uncontracted' + [SquadUtils.SquadStatus.ACTIVE] = { + '', + 'Loan', + 'Substitute', + 'Trial', + 'Stand-in', + 'Uncontracted' + }, + [SquadUtils.SquadStatus.INACTIVE] = { + 'Inactive' + } }, [SquadUtils.SquadType.STAFF] = {}, } @@ -111,7 +116,8 @@ local DEFAULT_EXCLUDED_ROLES = { 'Substitute', 'Trial', 'Stand-in', - 'Uncontracted' + 'Uncontracted', + 'Inactive' }, } @@ -132,21 +138,24 @@ end function SquadAuto:parseConfig() local args = self.args local type = SquadUtils.TypeToSquadType[(args.type or ''):lower()] + local status = SquadUtils.StatusToSquadStatus[(args.status or ''):lower()] self.config = { team = args.team or mw.title.getCurrentTitle().text, - status = SquadUtils.StatusToSquadStatus[(args.status or ''):lower()], type = type, + status = status, title = args.title, roles = { - included = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.roles)) or DEFAULT_INCLUDED_ROLES[type], - excluded = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.not_roles)) or DEFAULT_EXCLUDED_ROLES[type], + included = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.roles)) + or DEFAULT_INCLUDED_ROLES[type][status], + excluded = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.not_roles)) + or DEFAULT_EXCLUDED_ROLES[type], } } self.manualPlayers = self:readManualPlayers() -- Override default 'Former Squad' title - if self.config.status == SquadUtils.SquadStatus.FORMER + if status == SquadUtils.SquadStatus.FORMER and type == SquadUtils.SquadType.PLAYER and not self.config.title then self.config.title = 'Former Players' @@ -451,7 +460,7 @@ function SquadAuto:selectEntries() ) end ----Returns a function that maps a set of transfers to a list of +---Returns a function that maps a set of transfers to a list of ---SquadAutoPersons. ---Behavior depends on therent config: ---If the status is (inive, then at most one entry will be returned From 8060211fdb731618f213d34f1dd7d4a0153b9279 Mon Sep 17 00:00:00 2001 From: mbergen Date: Sat, 10 May 2025 12:43:41 +0200 Subject: [PATCH 09/29] Implement fetching of next team --- lua/definitions/liquipedia_db.lua | 2 +- lua/wikis/commons/Squad/Auto.lua | 48 ++++++++++++++++++++++++++----- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/lua/definitions/liquipedia_db.lua b/lua/definitions/liquipedia_db.lua index 3fff616e01b..0576b34f455 100644 --- a/lua/definitions/liquipedia_db.lua +++ b/lua/definitions/liquipedia_db.lua @@ -63,7 +63,7 @@ local lpdb = {} ---@field alternativeid string ---@field name string ---@field localizedname string ----@field iamge string +---@field image string ---@field type string ---@field nationality string ---@field nationality2 string diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index d88e9af1a32..8a83d476160 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -368,6 +368,13 @@ function SquadAuto:queryTransfers() local relevantToTeam, isToMain = parseRelevantTeam('to', record) local transferType = getTransferType(relevantFromTeam, relevantToTeam) + -- For leave transfers: Pass on new team for display as next team + if transferType == "LEAVE" and Logic.isEmpty(relevantToTeam) then + relevantToTeam = isFromMain + and Logic.nilIfEmpty(record.toteamtemplate) + or record.extradata.toteamsectemplate + end + ---@type TeamHistoryEntry local entry = { type = transferType, @@ -451,10 +458,6 @@ function SquadAuto:selectEntries() ) ) - if not result then - mw.logObject(entry, "Not included") - end - return result end ) @@ -547,18 +550,49 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) role = leaveEntry.toRole, }, - -- From legacy: Prefer leaveEntry faction information + -- From legacy: Prefer faction information from leaveEntry faction = leaveEntry.faction or joinEntry.faction, race = leaveEntry.faction or joinEntry.faction } - if leaveEntry and not entry.newTeam then - --TODO: Fetch next team for person + -- On leave: Fetch the next team a person joined + if Logic.isNotEmpty(leaveEntry) and Logic.isEmpty(entry.newTeam.team) then + local newTeam, newRole = SquadAuto._fetchNextTeam(joinEntry.pagename, leaveEntry.date) + if newTeam then + entry.newTeam.team = newTeam + entry.newTeam.role = newRole + end end return entry end +---Fetches the next team a person joined after a given date +---@param pagename string +---@param date string +---@return string? +---@return string? +function SquadAuto._fetchNextTeam(pagename, date) + local conditions = Condition.Tree(BooleanOperator.all) + :add{ + Condition.Node(Condition.ColumnName('player'), Comparator.eq, string.gsub(pagename, ' ', '_')), + Condition.Tree(BooleanOperator.any):add{ + Condition.Node(Condition.ColumnName('date'), Comparator.gt, date), + } + } + + local transfer = mw.ext.LiquipediaDB.lpdb('transfer', { + conditions = conditions:toString(), + limit = 1, + order = 'date asc, objectname desc', + query = 'toteamtemplate, role2' + })[1] or {} + + -- TODO: Check if fetched transfer is in fact a join transfer (empty fromTeam)? + + return transfer.toteamtemplate, transfer.role2 +end + ---Sorts a list of SquadAutoPersons -- Active entries (no leavedate) sorted by joindate, -- Former entries sorted by leavedate From 063b50530e95373c7ef554eac07043810a27bc65 Mon Sep 17 00:00:00 2001 From: mbergen Date: Sat, 10 May 2025 13:38:36 +0200 Subject: [PATCH 10/29] Fix handling of inactive persons --- lua/wikis/commons/Squad/Auto.lua | 38 +++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 8a83d476160..9b79e37c526 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -369,7 +369,7 @@ function SquadAuto:queryTransfers() local transferType = getTransferType(relevantFromTeam, relevantToTeam) -- For leave transfers: Pass on new team for display as next team - if transferType == "LEAVE" and Logic.isEmpty(relevantToTeam) then + if transferType == SquadAuto.TransferType.LEAVE and Logic.isEmpty(relevantToTeam) then relevantToTeam = isFromMain and Logic.nilIfEmpty(record.toteamtemplate) or record.extradata.toteamsectemplate @@ -402,8 +402,13 @@ function SquadAuto:queryTransfers() faction = record.extradata.faction } - -- TODO: Skip this transfer if there is no relevant change - -- E.g. this is grabbed a secondary team, but only main team changed + -- Skip this transfer if there is no relevant change, i.e. the role in this team didn't change + -- E.g. this is grabbed by secondary team, but only main team changed + if relevantFromTeam == relevantToTeam + and entry.fromRole == entry.toRole then + return + end + table.insert(self.playersTeamHistory[record.player], entry) end ) @@ -463,27 +468,32 @@ function SquadAuto:selectEntries() ) end ----Returns a function that maps a set of transfers to a list of ----SquadAutoPersons. ----Behavior depends on therent config: ----If the status is (inive, then at most one entry will be returned +---Returns a function that maps a set of transfers to a list of SquadAutoPersons. +---Behavior depends on the current config: +---If the status is (in)active, then at most one entry will be returned ---If the status is former(_inactive), there might be multiple entries returned ---If the type does not match, no entries are returned ---@param entries TeamHistoryEntry[] ---@return SquadAutoPerson[] function SquadAuto:_selectHistoryEntries(entries) -- Select entries to match status - if self.config.status == SquadUtils.SquadStatus.ACTIVE - or self.config.status == SquadUtils.SquadStatus.INACTIVE then + if self.config.status == SquadUtils.SquadStatus.ACTIVE then -- Only most recent transfer is relevant local last = entries[#entries] if last.type == SquadAuto.TransferType.CHANGE or last.type == SquadAuto.TransferType.JOIN then - -- When the last transfer is a leave transfer, the person wouldn't be (in)active + -- When the last transfer is a leave transfer, the person wouldn't be active return {self:_mapToSquadAutoPerson(last)} end end + if self.config.status == SquadUtils.SquadStatus.INACTIVE then + local last, secondToLast = entries[#entries], entries[#entries - 1] + if secondToLast and last.type == SquadAuto.TransferType.CHANGE then + return {self:_mapToSquadAutoPerson(secondToLast, last)} + end + end + if self.config.status == SquadUtils.SquadStatus.FORMER then local history = {} @@ -564,6 +574,14 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) end end + -- Special case: Person went inactive. + -- Set thisTeam.role to inactive and remove newTeam.role, + -- otherwise Squad doesn't display the entries + if leaveEntry.toRole == "Inactive" then + entry.thisTeam.role = entry.newTeam.role + entry.newTeam.role = "" + end + return entry end From 65abcafe458403f0a1f25ed09853bacf2571c159 Mon Sep 17 00:00:00 2001 From: mbergen Date: Sat, 10 May 2025 14:40:08 +0200 Subject: [PATCH 11/29] Only apply inactive modification for InactiveSquad --- lua/wikis/commons/Squad/Auto.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 9b79e37c526..eeccb7f0846 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -577,7 +577,8 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) -- Special case: Person went inactive. -- Set thisTeam.role to inactive and remove newTeam.role, -- otherwise Squad doesn't display the entries - if leaveEntry.toRole == "Inactive" then + if self.config.status == SquadUtils.SquadStatus.INACTIVE + and leaveEntry.toRole == "Inactive" then entry.thisTeam.role = entry.newTeam.role entry.newTeam.role = "" end From 2526983da51a4c0510153763cc2c7f76c3eb5951 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Mon, 12 May 2025 14:30:12 +0200 Subject: [PATCH 12/29] Update lua/wikis/commons/Squad/Auto.lua --- lua/wikis/commons/Squad/Auto.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index eeccb7f0846..a409dff107a 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -171,11 +171,6 @@ function SquadAuto:parseConfig() error("SquadStatus 'FORMER_INACTIVE' is not supported by SquadAuto.") end - if self.config.status == SquadUtils.SquadStatus.INACTIVE then - table.insert(self.config.roles.included, 'Inactive') - else - table.insert(self.config.roles.excluded, 'Inactive') - end end ---@param entries SquadAutoPerson[] From 5ad40abcfd09d9975f7a9b4126f5bf9b3ac6d53f Mon Sep 17 00:00:00 2001 From: mbergen Date: Wed, 14 May 2025 12:08:30 +0200 Subject: [PATCH 13/29] Convert indentation to tabs --- lua/wikis/commons/Squad/Auto.lua | 846 +++++++++++++++---------------- 1 file changed, 423 insertions(+), 423 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index a409dff107a..bb20c9c2921 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -31,7 +31,7 @@ local Comparator = Condition.Comparator ---@field manualTimeline table? ---@field playersTeamHistory table local SquadAuto = Class.new(nil, function (self, frame) - self.args = Arguments.getArgs(frame) + self.args = Arguments.getArgs(frame) end) ---@class SquadAutoTeam @@ -92,375 +92,375 @@ SquadAuto.TransferType = { ---@field faction string? local DEFAULT_INCLUDED_ROLES = { - [SquadUtils.SquadType.PLAYER] = { - [SquadUtils.SquadStatus.ACTIVE] = { - '', - 'Loan', - 'Substitute', - 'Trial', - 'Stand-in', - 'Uncontracted' - }, - [SquadUtils.SquadStatus.INACTIVE] = { - 'Inactive' - } - }, - [SquadUtils.SquadType.STAFF] = {}, + [SquadUtils.SquadType.PLAYER] = { + [SquadUtils.SquadStatus.ACTIVE] = { + '', + 'Loan', + 'Substitute', + 'Trial', + 'Stand-in', + 'Uncontracted' + }, + [SquadUtils.SquadStatus.INACTIVE] = { + 'Inactive' + } + }, + [SquadUtils.SquadType.STAFF] = {}, } local DEFAULT_EXCLUDED_ROLES = { - [SquadUtils.SquadType.PLAYER] = {}, - [SquadUtils.SquadType.STAFF] = { - '', - 'Loan', - 'Substitute', - 'Trial', - 'Stand-in', - 'Uncontracted', - 'Inactive' - }, + [SquadUtils.SquadType.PLAYER] = {}, + [SquadUtils.SquadType.STAFF] = { + '', + 'Loan', + 'Substitute', + 'Trial', + 'Stand-in', + 'Uncontracted', + 'Inactive' + }, } ---Entrypoint for SquadAuto tables ---@param frame table function SquadAuto.run(frame) - local autosquad = SquadAuto(frame) - autosquad:parseConfig() - autosquad:queryTransfers() + local autosquad = SquadAuto(frame) + autosquad:parseConfig() + autosquad:queryTransfers() - local entries = autosquad:selectEntries() - Array.forEach(entries, SquadAuto.enrichEntry) + local entries = autosquad:selectEntries() + Array.forEach(entries, SquadAuto.enrichEntry) - return autosquad:display(entries) + return autosquad:display(entries) end ---Parses the args into a SquadAutoConfig function SquadAuto:parseConfig() - local args = self.args - local type = SquadUtils.TypeToSquadType[(args.type or ''):lower()] - local status = SquadUtils.StatusToSquadStatus[(args.status or ''):lower()] - self.config = { - team = args.team or mw.title.getCurrentTitle().text, - type = type, - status = status, - title = args.title, - roles = { - included = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.roles)) - or DEFAULT_INCLUDED_ROLES[type][status], - excluded = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.not_roles)) - or DEFAULT_EXCLUDED_ROLES[type], - } - } - - self.manualPlayers = self:readManualPlayers() - - -- Override default 'Former Squad' title - if status == SquadUtils.SquadStatus.FORMER - and type == SquadUtils.SquadType.PLAYER - and not self.config.title then - self.config.title = 'Former Players' - end - - local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) - if not historicalTemplates then - error("Missing team template: " .. self.config.team) - end - self.config.teams = Array.append(Array.extractValues(historicalTemplates), self.config.team) - - if self.config.status == SquadUtils.SquadStatus.FORMER_INACTIVE then - error("SquadStatus 'FORMER_INACTIVE' is not supported by SquadAuto.") - end + local args = self.args + local type = SquadUtils.TypeToSquadType[(args.type or ''):lower()] + local status = SquadUtils.StatusToSquadStatus[(args.status or ''):lower()] + self.config = { + team = args.team or mw.title.getCurrentTitle().text, + type = type, + status = status, + title = args.title, + roles = { + included = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.roles)) + or DEFAULT_INCLUDED_ROLES[type][status], + excluded = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.not_roles)) + or DEFAULT_EXCLUDED_ROLES[type], + } + } + + self.manualPlayers = self:readManualPlayers() + + -- Override default 'Former Squad' title + if status == SquadUtils.SquadStatus.FORMER + and type == SquadUtils.SquadType.PLAYER + and not self.config.title then + self.config.title = 'Former Players' + end + + local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) + if not historicalTemplates then + error("Missing team template: " .. self.config.team) + end + self.config.teams = Array.append(Array.extractValues(historicalTemplates), self.config.team) + + if self.config.status == SquadUtils.SquadStatus.FORMER_INACTIVE then + error("SquadStatus 'FORMER_INACTIVE' is not supported by SquadAuto.") + end end ---@param entries SquadAutoPerson[] ---@return Widget|Html|string? function SquadAuto:display(entries) - if Logic.isEmpty(entries) then - return - end + if Logic.isEmpty(entries) then + return + end - if self.config.status == SquadUtils.SquadStatus.FORMER then - return self:displayTabs(entries) - end + if self.config.status == SquadUtils.SquadStatus.FORMER then + return self:displayTabs(entries) + end - entries = SquadAuto._sortEntries(entries) + entries = SquadAuto._sortEntries(entries) - return SquadCustom.runAuto(entries, self.config.status, self.config.type, self.config.title) + return SquadCustom.runAuto(entries, self.config.status, self.config.type, self.config.title) end ---@param entries SquadAutoPerson[] ---@return Html|string? function SquadAuto:displayTabs(entries) - local _, groupedEntries = Array.groupBy( - entries, - ---@param entry SquadAutoPerson - function (entry) - return entry.leavedate:match('(%d%d%d%d)') - end - ) - - ---@type table + local _, groupedEntries = Array.groupBy( + entries, + ---@param entry SquadAutoPerson + function (entry) + return entry.leavedate:match('(%d%d%d%d)') + end + ) + + ---@type table local tabs = { This = Table.size(groupedEntries), - removeEmptyTabs = true + removeEmptyTabs = true } - local idx = 1 + local idx = 1 for year, group in Table.iter.spairs(groupedEntries) do tabs['name' .. idx] = year - tabs['content' .. idx] = tostring(SquadCustom.runAuto( - SquadAuto._sortEntries(group), - self.config.status, - self.config.type, - self.config.title - )) - idx = idx + 1 + tabs['content' .. idx] = tostring(SquadCustom.runAuto( + SquadAuto._sortEntries(group), + self.config.status, + self.config.type, + self.config.title + )) + idx = idx + 1 end - return Tabs.dynamic(tabs) + return Tabs.dynamic(tabs) end ---@param entry SquadAutoPerson function SquadAuto.enrichEntry(entry) - local personInfo = mw.ext.LiquipediaDB.lpdb('player', { + local personInfo = mw.ext.LiquipediaDB.lpdb('player', { conditions = '[[pagename::' .. string.gsub(entry.page, ' ', '_') .. ']]', limit = 1, query = 'pagename, nationality, id, name, localizedname, extradata' })[1] if personInfo then - entry.id = personInfo.id - entry.flag = personInfo.nationality - entry.name = personInfo.name - entry.localizedname = personInfo.localizedname - end - --TODO: Captain from pagevars? + entry.id = personInfo.id + entry.flag = personInfo.nationality + entry.name = personInfo.name + entry.localizedname = personInfo.localizedname + end + --TODO: Captain from pagevars? end ---@return SquadAutoPerson[] function SquadAuto:readManualPlayers() - ---@type SquadAutoPerson[] - local players = {} - - -- TODO: Handle manual 'enrichments' for adding names - -- TODO: Readd limitations to specific roles? - Array.forEach(self.args, function (entry) - local player = Json.parseIfString(entry) - if Logic.isNotEmpty(player) then - table.insert(players, { - page = player.link or player.id or player.name, - id = player.id, - captain = Logic.readBoolOrNil(player.captain), - name = player.name, - localizedname = player.localizedname, - thisTeam = { - team = self.config.team, - role = player.role, - position = player.position - }, - newTeam = { - team = player.newteam, - role = player.newteamrole, - player.newteamdate - }, - flag = player.flag, - oldTeam = { - team = player.oldteam - }, - joindate = (player.joindate or ''):gsub('%?%?','01'), - joindatedisplay = player.joindate, - joindateRef = {}, - leavedate = (player.leavedate or ''):gsub('%?%?','01'), - leavedatedisplay = player.leavedate, - leavedateRef = {}, - faction = player.faction or player.race, - race = player.faction or player.race, - }) - end - end) - - return players + ---@type SquadAutoPerson[] + local players = {} + + -- TODO: Handle manual 'enrichments' for adding names + -- TODO: Readd limitations to specific roles? + Array.forEach(self.args, function (entry) + local player = Json.parseIfString(entry) + if Logic.isNotEmpty(player) then + table.insert(players, { + page = player.link or player.id or player.name, + id = player.id, + captain = Logic.readBoolOrNil(player.captain), + name = player.name, + localizedname = player.localizedname, + thisTeam = { + team = self.config.team, + role = player.role, + position = player.position + }, + newTeam = { + team = player.newteam, + role = player.newteamrole, + player.newteamdate + }, + flag = player.flag, + oldTeam = { + team = player.oldteam + }, + joindate = (player.joindate or ''):gsub('%?%?','01'), + joindatedisplay = player.joindate, + joindateRef = {}, + leavedate = (player.leavedate or ''):gsub('%?%?','01'), + leavedatedisplay = player.leavedate, + leavedateRef = {}, + faction = player.faction or player.race, + race = player.faction or player.race, + }) + end + end) + + return players end function SquadAuto:queryTransfers() - ---Checks whether a given team is the currently queried team - ---@param team string? - ---@return boolean - local function isCurrentTeam(team) - if not team then - return false - end - return Array.find(self.config.teams, function(t) return t == team end) ~= nil - end - - ---@param side 'from' | 'to' - ---@param transfer transfer - ---@return string | nil, boolean - local function parseRelevantTeam(side, transfer) - local mainTeam = transfer[side .. 'teamtemplate'] - if mainTeam and isCurrentTeam(mainTeam) then - return mainTeam, true - end - - local secondaryTeam = transfer.extradata[side .. 'teamsectemplate'] - if secondaryTeam and isCurrentTeam(secondaryTeam) then - return secondaryTeam, false - end - - return nil, false - end - - ---Maps a transfer to a transfertype, with regards to the current team. - ---@param relevantFromTeam string? - ---@param relevantToTeam string? - ---@return TransferType - local function getTransferType(relevantFromTeam, relevantToTeam) - if relevantFromTeam then - if relevantToTeam then - return SquadAuto.TransferType.CHANGE - end - return SquadAuto.TransferType.LEAVE - end - return SquadAuto.TransferType.JOIN - end - - ---Parses the relevant role for the current team from a transfer - ---@param side 'from' | 'to' - ---@param transfer transfer - ---@param team string? - ---@param isMain boolean - ---@return string? - local function parseRelevantRole(side, transfer, team, isMain) - if not team then - return nil - end - - if isMain then - return side == 'from' and transfer.role1 or transfer.role2 - else - return side == 'from' and transfer.extradata.role1sec or transfer.extradata.role2sec - end - end - - --TODO: Cache transfers/teamhistory in pagevars - ---@type table - self.playersTeamHistory = {} - - Lpdb.executeMassQuery( - 'transfer', - { - conditions = self:buildConditions(), - order = 'date asc, objectname desc', - limit = 5000 - }, - function(record) - self.playersTeamHistory[record.player] = self.playersTeamHistory[record.player] or {} - record.extradata = record.extradata or {} - - - local relevantFromTeam, isFromMain = parseRelevantTeam('from', record) - local relevantToTeam, isToMain = parseRelevantTeam('to', record) - local transferType = getTransferType(relevantFromTeam, relevantToTeam) - - -- For leave transfers: Pass on new team for display as next team - if transferType == SquadAuto.TransferType.LEAVE and Logic.isEmpty(relevantToTeam) then - relevantToTeam = isFromMain - and Logic.nilIfEmpty(record.toteamtemplate) - or record.extradata.toteamsectemplate - end - - ---@type TeamHistoryEntry - local entry = { - type = transferType, - - -- Person related information - pagename = record.player, - displayname = record.extradata.displayname, - flag = record.nationality, - - -- Date and references - date = record.date, - dateDisplay = record.extradata.displaydate, - references = record.reference, - - -- Roles - fromRole = parseRelevantRole('from', record, relevantFromTeam, isFromMain), - toRole = parseRelevantRole('to', record, relevantToTeam, isToMain), - - fromTeam = relevantFromTeam, - toTeam = relevantToTeam, - - -- Other - wholeTeam = Logic.readBool(record.wholeteam), - position = record.extradata.position, - faction = record.extradata.faction - } - - -- Skip this transfer if there is no relevant change, i.e. the role in this team didn't change - -- E.g. this is grabbed by secondary team, but only main team changed - if relevantFromTeam == relevantToTeam - and entry.fromRole == entry.toRole then - return - end - - table.insert(self.playersTeamHistory[record.player], entry) - end - ) + ---Checks whether a given team is the currently queried team + ---@param team string? + ---@return boolean + local function isCurrentTeam(team) + if not team then + return false + end + return Array.find(self.config.teams, function(t) return t == team end) ~= nil + end + + ---@param side 'from' | 'to' + ---@param transfer transfer + ---@return string | nil, boolean + local function parseRelevantTeam(side, transfer) + local mainTeam = transfer[side .. 'teamtemplate'] + if mainTeam and isCurrentTeam(mainTeam) then + return mainTeam, true + end + + local secondaryTeam = transfer.extradata[side .. 'teamsectemplate'] + if secondaryTeam and isCurrentTeam(secondaryTeam) then + return secondaryTeam, false + end + + return nil, false + end + + ---Maps a transfer to a transfertype, with regards to the current team. + ---@param relevantFromTeam string? + ---@param relevantToTeam string? + ---@return TransferType + local function getTransferType(relevantFromTeam, relevantToTeam) + if relevantFromTeam then + if relevantToTeam then + return SquadAuto.TransferType.CHANGE + end + return SquadAuto.TransferType.LEAVE + end + return SquadAuto.TransferType.JOIN + end + + ---Parses the relevant role for the current team from a transfer + ---@param side 'from' | 'to' + ---@param transfer transfer + ---@param team string? + ---@param isMain boolean + ---@return string? + local function parseRelevantRole(side, transfer, team, isMain) + if not team then + return nil + end + + if isMain then + return side == 'from' and transfer.role1 or transfer.role2 + else + return side == 'from' and transfer.extradata.role1sec or transfer.extradata.role2sec + end + end + + --TODO: Cache transfers/teamhistory in pagevars + ---@type table + self.playersTeamHistory = {} + + Lpdb.executeMassQuery( + 'transfer', + { + conditions = self:buildConditions(), + order = 'date asc, objectname desc', + limit = 5000 + }, + function(record) + self.playersTeamHistory[record.player] = self.playersTeamHistory[record.player] or {} + record.extradata = record.extradata or {} + + + local relevantFromTeam, isFromMain = parseRelevantTeam('from', record) + local relevantToTeam, isToMain = parseRelevantTeam('to', record) + local transferType = getTransferType(relevantFromTeam, relevantToTeam) + + -- For leave transfers: Pass on new team for display as next team + if transferType == SquadAuto.TransferType.LEAVE and Logic.isEmpty(relevantToTeam) then + relevantToTeam = isFromMain + and Logic.nilIfEmpty(record.toteamtemplate) + or record.extradata.toteamsectemplate + end + + ---@type TeamHistoryEntry + local entry = { + type = transferType, + + -- Person related information + pagename = record.player, + displayname = record.extradata.displayname, + flag = record.nationality, + + -- Date and references + date = record.date, + dateDisplay = record.extradata.displaydate, + references = record.reference, + + -- Roles + fromRole = parseRelevantRole('from', record, relevantFromTeam, isFromMain), + toRole = parseRelevantRole('to', record, relevantToTeam, isToMain), + + fromTeam = relevantFromTeam, + toTeam = relevantToTeam, + + -- Other + wholeTeam = Logic.readBool(record.wholeteam), + position = record.extradata.position, + faction = record.extradata.faction + } + + -- Skip this transfer if there is no relevant change, i.e. the role in this team didn't change + -- E.g. this is grabbed by secondary team, but only main team changed + if relevantFromTeam == relevantToTeam + and entry.fromRole == entry.toRole then + return + end + + table.insert(self.playersTeamHistory[record.player], entry) + end + ) end ---Builds the conditions to fetch all transfers related ---to the given team, respecting historical templates. ---@return string function SquadAuto:buildConditions() - local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) - - if not historicalTemplates then - error("Missing team template: " .. self.config.team) - end - - local conditions = Condition.Tree(BooleanOperator.any) - Array.forEach(Array.extendWith(Array.extractValues(historicalTemplates), self.config.team), function (templatename) - conditions:add{ - Condition.Node(Condition.ColumnName('fromteamtemplate'), Comparator.eq, templatename), - Condition.Node(Condition.ColumnName('extradata_fromteamsectemplate'), Comparator.eq, templatename), - Condition.Node(Condition.ColumnName('toteamtemplate'), Comparator.eq, templatename), - Condition.Node(Condition.ColumnName('extradata_toteamsectemplate'), Comparator.eq, templatename) - } - end) - - return conditions:toString() + local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) + + if not historicalTemplates then + error("Missing team template: " .. self.config.team) + end + + local conditions = Condition.Tree(BooleanOperator.any) + Array.forEach(Array.extendWith(Array.extractValues(historicalTemplates), self.config.team), function (templatename) + conditions:add{ + Condition.Node(Condition.ColumnName('fromteamtemplate'), Comparator.eq, templatename), + Condition.Node(Condition.ColumnName('extradata_fromteamsectemplate'), Comparator.eq, templatename), + Condition.Node(Condition.ColumnName('toteamtemplate'), Comparator.eq, templatename), + Condition.Node(Condition.ColumnName('extradata_toteamsectemplate'), Comparator.eq, templatename) + } + end) + + return conditions:toString() end ---@return SquadAutoPerson[] function SquadAuto:selectEntries() - return Array.filter( - Array.extend( - Array.flatMap( - Array.extractValues(self.playersTeamHistory), - FnUtil.curry(self._selectHistoryEntries, self) - ), - self.manualPlayers - ), - ---@param entry SquadAutoPerson - function(entry) - local result = ( - Logic.isEmpty(self.config.roles.included) - or Array.any( - self.config.roles.included, - FnUtil.curry(Operator.eq, entry.thisTeam.role) - ) - ) and ( - Logic.isEmpty(self.config.roles.excluded) - or Array.all( - self.config.roles.excluded, - FnUtil.curry(Operator.neq, entry.thisTeam.role) - ) - ) - - return result - end - ) + return Array.filter( + Array.extend( + Array.flatMap( + Array.extractValues(self.playersTeamHistory), + FnUtil.curry(self._selectHistoryEntries, self) + ), + self.manualPlayers + ), + ---@param entry SquadAutoPerson + function(entry) + local result = ( + Logic.isEmpty(self.config.roles.included) + or Array.any( + self.config.roles.included, + FnUtil.curry(Operator.eq, entry.thisTeam.role) + ) + ) and ( + Logic.isEmpty(self.config.roles.excluded) + or Array.all( + self.config.roles.excluded, + FnUtil.curry(Operator.neq, entry.thisTeam.role) + ) + ) + + return result + end + ) end ---Returns a function that maps a set of transfers to a list of SquadAutoPersons. @@ -471,53 +471,53 @@ end ---@param entries TeamHistoryEntry[] ---@return SquadAutoPerson[] function SquadAuto:_selectHistoryEntries(entries) - -- Select entries to match status - if self.config.status == SquadUtils.SquadStatus.ACTIVE then - -- Only most recent transfer is relevant - local last = entries[#entries] - if last.type == SquadAuto.TransferType.CHANGE - or last.type == SquadAuto.TransferType.JOIN then - -- When the last transfer is a leave transfer, the person wouldn't be active - return {self:_mapToSquadAutoPerson(last)} - end - end - - if self.config.status == SquadUtils.SquadStatus.INACTIVE then - local last, secondToLast = entries[#entries], entries[#entries - 1] - if secondToLast and last.type == SquadAuto.TransferType.CHANGE then - return {self:_mapToSquadAutoPerson(secondToLast, last)} - end - end - - if self.config.status == SquadUtils.SquadStatus.FORMER then - local history = {} - - local currentEntry = nil - - Array.forEach(entries, function (entry) - if not currentEntry then - if entry.type == SquadAuto.TransferType.JOIN then - currentEntry = entry - else - mw.log("Invalid transfer history for player " .. entry.pagename) - mw.logObject(entry, "Invalid entry: Missing previous JOIN. Skipping") - mw.ext.TeamLiquidIntegration.add_category('SquadAuto with invalid player history') - end - return - end - - table.insert(history, self:_mapToSquadAutoPerson(currentEntry, entry)) - if entry.type == "CHANGE" then - currentEntry = entry - else - currentEntry = nil - end - end) - - return history - end - - return {} + -- Select entries to match status + if self.config.status == SquadUtils.SquadStatus.ACTIVE then + -- Only most recent transfer is relevant + local last = entries[#entries] + if last.type == SquadAuto.TransferType.CHANGE + or last.type == SquadAuto.TransferType.JOIN then + -- When the last transfer is a leave transfer, the person wouldn't be active + return {self:_mapToSquadAutoPerson(last)} + end + end + + if self.config.status == SquadUtils.SquadStatus.INACTIVE then + local last, secondToLast = entries[#entries], entries[#entries - 1] + if secondToLast and last.type == SquadAuto.TransferType.CHANGE then + return {self:_mapToSquadAutoPerson(secondToLast, last)} + end + end + + if self.config.status == SquadUtils.SquadStatus.FORMER then + local history = {} + + local currentEntry = nil + + Array.forEach(entries, function (entry) + if not currentEntry then + if entry.type == SquadAuto.TransferType.JOIN then + currentEntry = entry + else + mw.log("Invalid transfer history for player " .. entry.pagename) + mw.logObject(entry, "Invalid entry: Missing previous JOIN. Skipping") + mw.ext.TeamLiquidIntegration.add_category('SquadAuto with invalid player history') + end + return + end + + table.insert(history, self:_mapToSquadAutoPerson(currentEntry, entry)) + if entry.type == "CHANGE" then + currentEntry = entry + else + currentEntry = nil + end + end) + + return history + end + + return {} end ---Maps one or a pair of TeamHistoryEntries to a single SquadAutoPerson @@ -525,60 +525,60 @@ end ---@param leaveEntry TeamHistoryEntry | nil ---@return SquadAutoPerson function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) - leaveEntry = leaveEntry or {} - - ---@type SquadAutoPerson - local entry = { - page = joinEntry.pagename, - id = joinEntry.displayname, - flag = joinEntry.flag, - joindate = joinEntry.date, - joindatedisplay = joinEntry.dateDisplay, - joindateRef = joinEntry.references, - - idleavedate = leaveEntry.displayname, - leavedate = leaveEntry.date, - leavedatedisplay = leaveEntry.dateDisplay or '', - leavedateRef = leaveEntry.references, - - thisTeam = { - team = joinEntry.toTeam, - role = joinEntry.toRole, - position = joinEntry.position - }, - oldTeam = { - team = joinEntry.fromTeam, - role = joinEntry.fromRole, - }, - newTeam = { - team = leaveEntry.toTeam, - role = leaveEntry.toRole, - }, - - -- From legacy: Prefer faction information from leaveEntry - faction = leaveEntry.faction or joinEntry.faction, - race = leaveEntry.faction or joinEntry.faction - } - - -- On leave: Fetch the next team a person joined - if Logic.isNotEmpty(leaveEntry) and Logic.isEmpty(entry.newTeam.team) then - local newTeam, newRole = SquadAuto._fetchNextTeam(joinEntry.pagename, leaveEntry.date) - if newTeam then - entry.newTeam.team = newTeam - entry.newTeam.role = newRole - end - end - - -- Special case: Person went inactive. - -- Set thisTeam.role to inactive and remove newTeam.role, - -- otherwise Squad doesn't display the entries - if self.config.status == SquadUtils.SquadStatus.INACTIVE - and leaveEntry.toRole == "Inactive" then - entry.thisTeam.role = entry.newTeam.role - entry.newTeam.role = "" - end - - return entry + leaveEntry = leaveEntry or {} + + ---@type SquadAutoPerson + local entry = { + page = joinEntry.pagename, + id = joinEntry.displayname, + flag = joinEntry.flag, + joindate = joinEntry.date, + joindatedisplay = joinEntry.dateDisplay, + joindateRef = joinEntry.references, + + idleavedate = leaveEntry.displayname, + leavedate = leaveEntry.date, + leavedatedisplay = leaveEntry.dateDisplay or '', + leavedateRef = leaveEntry.references, + + thisTeam = { + team = joinEntry.toTeam, + role = joinEntry.toRole, + position = joinEntry.position + }, + oldTeam = { + team = joinEntry.fromTeam, + role = joinEntry.fromRole, + }, + newTeam = { + team = leaveEntry.toTeam, + role = leaveEntry.toRole, + }, + + -- From legacy: Prefer faction information from leaveEntry + faction = leaveEntry.faction or joinEntry.faction, + race = leaveEntry.faction or joinEntry.faction + } + + -- On leave: Fetch the next team a person joined + if Logic.isNotEmpty(leaveEntry) and Logic.isEmpty(entry.newTeam.team) then + local newTeam, newRole = SquadAuto._fetchNextTeam(joinEntry.pagename, leaveEntry.date) + if newTeam then + entry.newTeam.team = newTeam + entry.newTeam.role = newRole + end + end + + -- Special case: Person went inactive. + -- Set thisTeam.role to inactive and remove newTeam.role, + -- otherwise Squad doesn't display the entries + if self.config.status == SquadUtils.SquadStatus.INACTIVE + and leaveEntry.toRole == "Inactive" then + entry.thisTeam.role = entry.newTeam.role + entry.newTeam.role = "" + end + + return entry end ---Fetches the next team a person joined after a given date @@ -587,24 +587,24 @@ end ---@return string? ---@return string? function SquadAuto._fetchNextTeam(pagename, date) - local conditions = Condition.Tree(BooleanOperator.all) - :add{ - Condition.Node(Condition.ColumnName('player'), Comparator.eq, string.gsub(pagename, ' ', '_')), - Condition.Tree(BooleanOperator.any):add{ - Condition.Node(Condition.ColumnName('date'), Comparator.gt, date), - } - } - - local transfer = mw.ext.LiquipediaDB.lpdb('transfer', { + local conditions = Condition.Tree(BooleanOperator.all) + :add{ + Condition.Node(Condition.ColumnName('player'), Comparator.eq, string.gsub(pagename, ' ', '_')), + Condition.Tree(BooleanOperator.any):add{ + Condition.Node(Condition.ColumnName('date'), Comparator.gt, date), + } + } + + local transfer = mw.ext.LiquipediaDB.lpdb('transfer', { conditions = conditions:toString(), limit = 1, - order = 'date asc, objectname desc', + order = 'date asc, objectname desc', query = 'toteamtemplate, role2' })[1] or {} - -- TODO: Check if fetched transfer is in fact a join transfer (empty fromTeam)? + -- TODO: Check if fetched transfer is in fact a join transfer (empty fromTeam)? - return transfer.toteamtemplate, transfer.role2 + return transfer.toteamtemplate, transfer.role2 end ---Sorts a list of SquadAutoPersons @@ -613,9 +613,9 @@ end ---@param entries SquadAutoPerson[] ---@return unknown[] function SquadAuto._sortEntries(entries) - return Array.sortBy(entries, function (element) - return {element.leavedate or element.joindate, element.id} - end) + return Array.sortBy(entries, function (element) + return {element.leavedate or element.joindate, element.id} + end) end return SquadAuto From c88a7d43c4fe7899809cd9524a41868e3ec364b2 Mon Sep 17 00:00:00 2001 From: mbergen Date: Sat, 17 May 2025 15:06:27 +0200 Subject: [PATCH 14/29] Add enrichment from manualInput --- lua/wikis/commons/Squad/Auto.lua | 113 ++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 41 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index bb20c9c2921..b4c31a17802 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -15,6 +15,7 @@ local Json = require('Module:Json') local Logic = require('Module:Logic') local Lpdb = require('Module:Lpdb') local Operator = require('Module:Operator') +local Page = require('Module:Page') local Table = require('Module:Table') local Tabs = require('Module:Tabs') @@ -40,14 +41,18 @@ end) ---@field position string? ---@field date string? ----TODO: Unify with SquadPerson ----@class SquadAutoPerson +---@class SquadAutoBase ---@field id string ---@field flag string? ----@field idleavedate string? ---@field page string ---@field name string? ---@field localizedname string? +---@field faction string? +---@field captain boolean? + +---TODO: Unify with SquadPerson +---@class SquadAutoPerson: SquadAutoBase +---@field idleavedate string? ---@field thisTeam SquadAutoTeam ---@field oldTeam SquadAutoTeam? ---@field newTeam SquadAutoTeam? @@ -57,8 +62,6 @@ end) ---@field leavedate string? ---@field leavedatedisplay string ---@field leavedateRef table? ----@field faction string? ----@field captain boolean? ---@class SquadAutoConfig ---@field team string @@ -129,7 +132,7 @@ function SquadAuto.run(frame) autosquad:queryTransfers() local entries = autosquad:selectEntries() - Array.forEach(entries, SquadAuto.enrichEntry) + Array.forEach(entries, FnUtil.curry(SquadAuto.enrichEntry, autosquad)) return autosquad:display(entries) end @@ -152,7 +155,7 @@ function SquadAuto:parseConfig() } } - self.manualPlayers = self:readManualPlayers() + self.manualPlayers, self.enrichmentInfo = self:readManualRowInput() -- Override default 'Former Squad' title if status == SquadUtils.SquadStatus.FORMER @@ -222,65 +225,93 @@ function SquadAuto:displayTabs(entries) end ---@param entry SquadAutoPerson -function SquadAuto.enrichEntry(entry) +function SquadAuto:enrichEntry(entry) + local pagename = Page.pageifyLink(entry.page) + local enrichment = self.enrichmentInfo[pagename] + if enrichment then + Table.mergeInto(entry, enrichment) + end + local personInfo = mw.ext.LiquipediaDB.lpdb('player', { - conditions = '[[pagename::' .. string.gsub(entry.page, ' ', '_') .. ']]', + conditions = '[[pagename::' .. pagename .. ']]', limit = 1, query = 'pagename, nationality, id, name, localizedname, extradata' })[1] if personInfo then - entry.id = personInfo.id - entry.flag = personInfo.nationality - entry.name = personInfo.name - entry.localizedname = personInfo.localizedname + entry.id = Logic.nilIfEmpty(entry.id) or personInfo.id + entry.flag = Logic.nilIfEmpty(entry.flag) or personInfo.nationality + entry.name = Logic.nilIfEmpty(entry.name) or personInfo.name + entry.localizedname = Logic.nilIfEmpty(entry.localizedname) or personInfo.localizedname end - --TODO: Captain from pagevars? + + --TODO: Captain from pagevar set in infobox? end ----@return SquadAutoPerson[] -function SquadAuto:readManualPlayers() +---@return SquadAutoPerson[] manualPersons +---@return table enrichmentInfo +function SquadAuto:readManualRowInput() ---@type SquadAutoPerson[] - local players = {} + local persons = {} + local enrichmentInfo = {} - -- TODO: Handle manual 'enrichments' for adding names - -- TODO: Readd limitations to specific roles? Array.forEach(self.args, function (entry) - local player = Json.parseIfString(entry) - if Logic.isNotEmpty(player) then - table.insert(players, { - page = player.link or player.id or player.name, - id = player.id, - captain = Logic.readBoolOrNil(player.captain), - name = player.name, - localizedname = player.localizedname, + local person = Json.parseIfString(entry) + + if Logic.isEmpty(person) then + return + end + + local page = Page.pageifyLink(person.link or person.id or person.name) + assert(page, "Missing identifier or link for SquadAutoRow " .. entry) + + if self.config.type == SquadUtils.SquadType.STAFF and Logic.isNotEmpty(person.role) then + -- Only allow manual entries for STAFF (organization) tables + table.insert(persons, { + page = page, + id = person.id, + captain = Logic.readBoolOrNil(person.captain), + name = person.name, + localizedname = person.localizedname, thisTeam = { team = self.config.team, - role = player.role, - position = player.position + role = person.role, + position = person.position }, newTeam = { - team = player.newteam, - role = player.newteamrole, - player.newteamdate + team = person.newteam, + role = person.newteamrole, + person.newteamdate }, - flag = player.flag, + flag = person.flag, oldTeam = { - team = player.oldteam + team = person.oldteam }, - joindate = (player.joindate or ''):gsub('%?%?','01'), - joindatedisplay = player.joindate, + joindate = (person.joindate or ''):gsub('%?%?','01'), + joindatedisplay = person.joindate, joindateRef = {}, - leavedate = (player.leavedate or ''):gsub('%?%?','01'), - leavedatedisplay = player.leavedate, + leavedate = (person.leavedate or ''):gsub('%?%?','01'), + leavedatedisplay = person.leavedate, leavedateRef = {}, - faction = player.faction or player.race, - race = player.faction or player.race, + faction = person.faction or person.race, + race = person.faction or person.race, }) + else + -- For PLAYER tables, or when no role is given: Treat as override + enrichmentInfo[page] = { + id = person.id, + captain = Logic.readBoolOrNil(person.captain), + name = person.name, + localizedname = person.localizedname, + flag = person.flag, + faction = person.faction or person.race, + } end end) - return players + mw.logObject(enrichmentInfo) + + return persons, enrichmentInfo end function SquadAuto:queryTransfers() From 69f1522c32e2c9f072b6aa2ce7255cb8e89b8afd Mon Sep 17 00:00:00 2001 From: mbergen Date: Tue, 20 May 2025 18:13:51 +0200 Subject: [PATCH 15/29] per review --- lua/wikis/commons/Squad/Auto.lua | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index b4c31a17802..2fa808552a4 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -18,6 +18,7 @@ local Operator = require('Module:Operator') local Page = require('Module:Page') local Table = require('Module:Table') local Tabs = require('Module:Tabs') +local TeamTemplate = require('Module:TeamTemplate') local SquadUtils = require('Module:Squad/Utils') local SquadCustom = require('Module:Squad/Custom') @@ -164,14 +165,14 @@ function SquadAuto:parseConfig() self.config.title = 'Former Players' end - local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) + local historicalTemplates = TeamTemplate.queryHistorical(self.config.team) if not historicalTemplates then - error("Missing team template: " .. self.config.team) + error(TeamTemplate.noTeamMessage(self.config.team)) end self.config.teams = Array.append(Array.extractValues(historicalTemplates), self.config.team) if self.config.status == SquadUtils.SquadStatus.FORMER_INACTIVE then - error("SquadStatus 'FORMER_INACTIVE' is not supported by SquadAuto.") + error('SquadStatus \'FORMER_INACTIVE\' is not supported by SquadAuto.') end end @@ -263,7 +264,7 @@ function SquadAuto:readManualRowInput() end local page = Page.pageifyLink(person.link or person.id or person.name) - assert(page, "Missing identifier or link for SquadAutoRow " .. entry) + assert(page, 'Missing identifier or link for SquadAutoRow ' .. entry) if self.config.type == SquadUtils.SquadType.STAFF and Logic.isNotEmpty(person.role) then -- Only allow manual entries for STAFF (organization) tables @@ -447,7 +448,7 @@ function SquadAuto:buildConditions() local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) if not historicalTemplates then - error("Missing team template: " .. self.config.team) + error('Missing team template: ' .. self.config.team) end local conditions = Condition.Tree(BooleanOperator.any) @@ -530,15 +531,15 @@ function SquadAuto:_selectHistoryEntries(entries) if entry.type == SquadAuto.TransferType.JOIN then currentEntry = entry else - mw.log("Invalid transfer history for player " .. entry.pagename) - mw.logObject(entry, "Invalid entry: Missing previous JOIN. Skipping") + mw.log('Invalid transfer history for player ' .. entry.pagename) + mw.logObject(entry, 'Invalid entry: Missing previous JOIN. Skipping') mw.ext.TeamLiquidIntegration.add_category('SquadAuto with invalid player history') end return end table.insert(history, self:_mapToSquadAutoPerson(currentEntry, entry)) - if entry.type == "CHANGE" then + if entry.type == 'CHANGE' then currentEntry = entry else currentEntry = nil @@ -604,9 +605,9 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) -- Set thisTeam.role to inactive and remove newTeam.role, -- otherwise Squad doesn't display the entries if self.config.status == SquadUtils.SquadStatus.INACTIVE - and leaveEntry.toRole == "Inactive" then + and leaveEntry.toRole == 'Inactive' then entry.thisTeam.role = entry.newTeam.role - entry.newTeam.role = "" + entry.newTeam.role = '' end return entry @@ -642,7 +643,7 @@ end -- Active entries (no leavedate) sorted by joindate, -- Former entries sorted by leavedate ---@param entries SquadAutoPerson[] ----@return unknown[] +---@return SquadAutoPerson[] function SquadAuto._sortEntries(entries) return Array.sortBy(entries, function (element) return {element.leavedate or element.joindate, element.id} From 3155f4c0489a513533c4da32c1df140571cdf0c7 Mon Sep 17 00:00:00 2001 From: mbergen Date: Tue, 20 May 2025 18:18:00 +0200 Subject: [PATCH 16/29] Remove tostring --- lua/wikis/commons/Squad/Auto.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 2fa808552a4..f65e345c626 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -213,12 +213,12 @@ function SquadAuto:displayTabs(entries) local idx = 1 for year, group in Table.iter.spairs(groupedEntries) do tabs['name' .. idx] = year - tabs['content' .. idx] = tostring(SquadCustom.runAuto( + tabs['content' .. idx] = SquadCustom.runAuto( SquadAuto._sortEntries(group), self.config.status, self.config.type, self.config.title - )) + ) idx = idx + 1 end From abf2d384c5df9d5bcf184a7016f8466feadfb4f8 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Wed, 29 Oct 2025 13:41:05 +0100 Subject: [PATCH 17/29] Apply suggestions from review --- lua/wikis/commons/Squad/Auto.lua | 53 ++++++++++++++------------------ 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index f65e345c626..e244898c2d4 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -1,27 +1,28 @@ --- -- @Liquipedia --- wiki=commons -- page=Module:Squad/Auto -- -- Please see https://github.com/Liquipedia/Lua-Modules to contribute -- -local Arguments = require('Module:Arguments') -local Array = require('Module:Array') -local Class = require('Module:Class') -local Condition = require('Module:Condition') -local FnUtil = require('Module:FnUtil') -local Json = require('Module:Json') -local Logic = require('Module:Logic') -local Lpdb = require('Module:Lpdb') -local Operator = require('Module:Operator') -local Page = require('Module:Page') -local Table = require('Module:Table') -local Tabs = require('Module:Tabs') -local TeamTemplate = require('Module:TeamTemplate') - -local SquadUtils = require('Module:Squad/Utils') -local SquadCustom = require('Module:Squad/Custom') +local Lua = require('Module:Lua') + +local Arguments = Lua.import('Module:Arguments') +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Condition = Lua.import('Module:Condition') +local FnUtil = Lua.import('Module:FnUtil') +local Json = Lua.import('Module:Json') +local Logic = Lua.import('Module:Logic') +local Lpdb = Lua.import('Module:Lpdb') +local Operator = Lua.import('Module:Operator') +local Page = Lua.import('Module:Page') +local Table = Lua.import('Module:Table') +local Tabs = Lua.import('Module:Tabs') +local TeamTemplate = Lua.import('Module:TeamTemplate') + +local SquadUtils = Lua.import('Module:Squad/Utils') +local SquadCustom = Lua.import('Module:Squad/Custom') local BooleanOperator = Condition.BooleanOperator local Comparator = Condition.Comparator @@ -127,6 +128,7 @@ local DEFAULT_EXCLUDED_ROLES = { ---Entrypoint for SquadAuto tables ---@param frame table +---@return Widget|Html|string? function SquadAuto.run(frame) local autosquad = SquadAuto(frame) autosquad:parseConfig() @@ -174,7 +176,6 @@ function SquadAuto:parseConfig() if self.config.status == SquadUtils.SquadStatus.FORMER_INACTIVE then error('SquadStatus \'FORMER_INACTIVE\' is not supported by SquadAuto.') end - end ---@param entries SquadAutoPerson[] @@ -310,8 +311,6 @@ function SquadAuto:readManualRowInput() end end) - mw.logObject(enrichmentInfo) - return persons, enrichmentInfo end @@ -323,7 +322,7 @@ function SquadAuto:queryTransfers() if not team then return false end - return Array.find(self.config.teams, function(t) return t == team end) ~= nil + return Array.any(self.config.teams, function(t) return t == team end) end ---@param side 'from' | 'to' @@ -445,14 +444,8 @@ end ---to the given team, respecting historical templates. ---@return string function SquadAuto:buildConditions() - local historicalTemplates = mw.ext.TeamTemplate.raw_historical(self.config.team) - - if not historicalTemplates then - error('Missing team template: ' .. self.config.team) - end - local conditions = Condition.Tree(BooleanOperator.any) - Array.forEach(Array.extendWith(Array.extractValues(historicalTemplates), self.config.team), function (templatename) + Array.forEach(self.config.teams, function (templatename) conditions:add{ Condition.Node(Condition.ColumnName('fromteamtemplate'), Comparator.eq, templatename), Condition.Node(Condition.ColumnName('extradata_fromteamsectemplate'), Comparator.eq, templatename), @@ -539,7 +532,7 @@ function SquadAuto:_selectHistoryEntries(entries) end table.insert(history, self:_mapToSquadAutoPerson(currentEntry, entry)) - if entry.type == 'CHANGE' then + if entry.type == SquadAuto.TransferType.CHANGE then currentEntry = entry else currentEntry = nil @@ -634,7 +627,7 @@ function SquadAuto._fetchNextTeam(pagename, date) query = 'toteamtemplate, role2' })[1] or {} - -- TODO: Check if fetched transfer is in fact a join transfer (empty fromTeam)? + -- TODO: (Optional) Check if fetched transfer is in fact a join transfer (empty fromTeam)? return transfer.toteamtemplate, transfer.role2 end From 355cf0808c6275257c363290698fb2e8215b75dc Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Wed, 29 Oct 2025 15:45:33 +0100 Subject: [PATCH 18/29] Fix handling of former inactive players --- lua/wikis/commons/Squad/Auto.lua | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index e244898c2d4..ade978c0301 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -96,6 +96,8 @@ SquadAuto.TransferType = { ---@field toRole string? ---@field faction string? +local ROLE_INACTIVE = 'Inactive' + local DEFAULT_INCLUDED_ROLES = { [SquadUtils.SquadType.PLAYER] = { [SquadUtils.SquadStatus.ACTIVE] = { @@ -107,7 +109,7 @@ local DEFAULT_INCLUDED_ROLES = { 'Uncontracted' }, [SquadUtils.SquadStatus.INACTIVE] = { - 'Inactive' + ROLE_INACTIVE } }, [SquadUtils.SquadType.STAFF] = {}, @@ -116,13 +118,15 @@ local DEFAULT_INCLUDED_ROLES = { local DEFAULT_EXCLUDED_ROLES = { [SquadUtils.SquadType.PLAYER] = {}, [SquadUtils.SquadType.STAFF] = { - '', - 'Loan', - 'Substitute', - 'Trial', - 'Stand-in', - 'Uncontracted', - 'Inactive' + DEFAULT = { + '', + 'Loan', + 'Substitute', + 'Trial', + 'Stand-in', + 'Uncontracted', + ROLE_INACTIVE + }, }, } @@ -152,9 +156,9 @@ function SquadAuto:parseConfig() title = args.title, roles = { included = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.roles)) - or DEFAULT_INCLUDED_ROLES[type][status], + or DEFAULT_INCLUDED_ROLES[type][status]or DEFAULT_INCLUDED_ROLES[type].DEFAULT, excluded = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.not_roles)) - or DEFAULT_EXCLUDED_ROLES[type], + or DEFAULT_EXCLUDED_ROLES[type][status] or DEFAULT_EXCLUDED_ROLES[type].DEFAULT, } } @@ -509,7 +513,7 @@ function SquadAuto:_selectHistoryEntries(entries) if self.config.status == SquadUtils.SquadStatus.INACTIVE then local last, secondToLast = entries[#entries], entries[#entries - 1] - if secondToLast and last.type == SquadAuto.TransferType.CHANGE then + if secondToLast and last.type == SquadAuto.TransferType.CHANGE and last.toRole == ROLE_INACTIVE then return {self:_mapToSquadAutoPerson(secondToLast, last)} end end From cd3b10c635cc70bf720c43d53f2899a86e2e5ce7 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Wed, 29 Oct 2025 15:55:22 +0100 Subject: [PATCH 19/29] Do not use single tabs --- lua/wikis/commons/Squad/Auto.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index ade978c0301..e398ac92265 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -199,7 +199,7 @@ function SquadAuto:display(entries) end ---@param entries SquadAutoPerson[] ----@return Html|string? +---@return Widget|Html|string? function SquadAuto:displayTabs(entries) local _, groupedEntries = Array.groupBy( entries, @@ -209,9 +209,19 @@ function SquadAuto:displayTabs(entries) end ) + local tabCount = Table.size(groupedEntries) + if tabCount == 1 then + return SquadCustom.runAuto( + SquadAuto._sortEntries(entries), + self.config.status, + self.config.type, + self.config.title + ) + end + ---@type table local tabs = { - This = Table.size(groupedEntries), + This = tabCount, removeEmptyTabs = true } From a1e2121b0f84fff27430959584c8d15237e9b594 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Wed, 29 Oct 2025 15:55:44 +0100 Subject: [PATCH 20/29] extract ROLES_PLAYER table --- lua/wikis/commons/Squad/Auto.lua | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index e398ac92265..cc034aa19f5 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -98,16 +98,19 @@ SquadAuto.TransferType = { local ROLE_INACTIVE = 'Inactive' +-- TODO: Replace with Module:Roles +local ROLES_PLAYER = { + '', + 'Loan', + 'Substitute', + 'Trial', + 'Stand-in', + 'Uncontracted' +} + local DEFAULT_INCLUDED_ROLES = { [SquadUtils.SquadType.PLAYER] = { - [SquadUtils.SquadStatus.ACTIVE] = { - '', - 'Loan', - 'Substitute', - 'Trial', - 'Stand-in', - 'Uncontracted' - }, + DEFAULT = ROLES_PLAYER, [SquadUtils.SquadStatus.INACTIVE] = { ROLE_INACTIVE } @@ -118,15 +121,10 @@ local DEFAULT_INCLUDED_ROLES = { local DEFAULT_EXCLUDED_ROLES = { [SquadUtils.SquadType.PLAYER] = {}, [SquadUtils.SquadType.STAFF] = { - DEFAULT = { - '', - 'Loan', - 'Substitute', - 'Trial', - 'Stand-in', - 'Uncontracted', + DEFAULT = Array.extend( + ROLES_PLAYER, ROLE_INACTIVE - }, + ), }, } From 7b9b50e5de50a5f063cb3f1911fd4b9bb906ce2e Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Wed, 29 Oct 2025 15:55:54 +0100 Subject: [PATCH 21/29] Support non-historical TTs --- lua/wikis/commons/Squad/Auto.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index cc034aa19f5..f267d7f0931 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -169,11 +169,12 @@ function SquadAuto:parseConfig() self.config.title = 'Former Players' end - local historicalTemplates = TeamTemplate.queryHistorical(self.config.team) - if not historicalTemplates then + local historicalTemplates = TeamTemplate.queryHistorical(self.config.team) or {} + self.config.teams = Array.append(Array.extractValues(historicalTemplates), TeamTemplate.resolve(self.config.team)) + + if Logic.isEmpty(self.config.teams) then error(TeamTemplate.noTeamMessage(self.config.team)) end - self.config.teams = Array.append(Array.extractValues(historicalTemplates), self.config.team) if self.config.status == SquadUtils.SquadStatus.FORMER_INACTIVE then error('SquadStatus \'FORMER_INACTIVE\' is not supported by SquadAuto.') From 8524e1e6bf7a96341aca27e1805b97c09e4fb49f Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Wed, 29 Oct 2025 15:56:12 +0100 Subject: [PATCH 22/29] Use newer names for display, fix querying of newteam --- lua/wikis/commons/Squad/Auto.lua | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index f267d7f0931..0e7798fcc8a 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -568,7 +568,7 @@ function SquadAuto:_mapToSquadAutoPerson(joinEntry, leaveEntry) ---@type SquadAutoPerson local entry = { page = joinEntry.pagename, - id = joinEntry.displayname, + id = leaveEntry.displayname or joinEntry.displayname, flag = joinEntry.flag, joindate = joinEntry.date, joindatedisplay = joinEntry.dateDisplay, @@ -627,7 +627,10 @@ end function SquadAuto._fetchNextTeam(pagename, date) local conditions = Condition.Tree(BooleanOperator.all) :add{ - Condition.Node(Condition.ColumnName('player'), Comparator.eq, string.gsub(pagename, ' ', '_')), + Condition.Tree(BooleanOperator.any):add{ + Condition.Node(Condition.ColumnName('player'), Comparator.eq, pagename), + Condition.Node(Condition.ColumnName('player'), Comparator.eq, string.gsub(pagename, ' ', '_')), + }, Condition.Tree(BooleanOperator.any):add{ Condition.Node(Condition.ColumnName('date'), Comparator.gt, date), } From f4eae2f3dcc24892e515d39bcb613d67099e929d Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Wed, 29 Oct 2025 18:05:00 +0100 Subject: [PATCH 23/29] Cache queried transfers in variables --- lua/wikis/commons/Squad/Auto.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 0e7798fcc8a..44c36eede91 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -17,6 +17,7 @@ local Logic = Lua.import('Module:Logic') local Lpdb = Lua.import('Module:Lpdb') local Operator = Lua.import('Module:Operator') local Page = Lua.import('Module:Page') +local PageVariableNamespace = Lua.import('Module:PageVariableNamespace') local Table = Lua.import('Module:Table') local Tabs = Lua.import('Module:Tabs') local TeamTemplate = Lua.import('Module:TeamTemplate') @@ -27,6 +28,8 @@ local SquadCustom = Lua.import('Module:Squad/Custom') local BooleanOperator = Condition.BooleanOperator local Comparator = Condition.Comparator +local pageVars = PageVariableNamespace() + ---@class SquadAuto ---@field args table ---@field config SquadAutoConfig @@ -387,9 +390,14 @@ function SquadAuto:queryTransfers() end end - --TODO: Cache transfers/teamhistory in pagevars + local teamHistoryKey = self.config.team .. '_all_transfers' + ---@type table - self.playersTeamHistory = {} + self.playersTeamHistory = Json.parseIfTable(pageVars:get(teamHistoryKey)) or {} + + if Logic.isNotEmpty(self.playersTeamHistory) then + return + end Lpdb.executeMassQuery( 'transfer', @@ -451,6 +459,7 @@ function SquadAuto:queryTransfers() table.insert(self.playersTeamHistory[record.player], entry) end ) + pageVars:set(teamHistoryKey, Json.stringify(self.playersTeamHistory)) end ---Builds the conditions to fetch all transfers related From 8357ccd62a029b4c140fcbc745ebcc80470348b7 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Wed, 29 Oct 2025 18:17:58 +0100 Subject: [PATCH 24/29] Correctly display formerly inactive players --- lua/wikis/commons/Squad/Auto.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 44c36eede91..9f8547242d9 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -116,7 +116,11 @@ local DEFAULT_INCLUDED_ROLES = { DEFAULT = ROLES_PLAYER, [SquadUtils.SquadStatus.INACTIVE] = { ROLE_INACTIVE - } + }, + [SquadUtils.SquadStatus.FORMER] = Array.extend( + ROLES_PLAYER, + ROLE_INACTIVE + ), }, [SquadUtils.SquadType.STAFF] = {}, } From cfeb803aa6a33a8f5cbabc81289ac0b1cfdf6ec3 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Thu, 30 Oct 2025 13:34:16 +0100 Subject: [PATCH 25/29] Apply suggestions from code review Co-authored-by: ElectricalBoy <15651807+ElectricalBoy@users.noreply.github.com> --- lua/wikis/commons/Squad/Auto.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 9f8547242d9..c884365124d 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -342,7 +342,7 @@ function SquadAuto:queryTransfers() if not team then return false end - return Array.any(self.config.teams, function(t) return t == team end) + return Array.any(self.config.teams, FnUtil.curry(Operator.eq, team)) end ---@param side 'from' | 'to' @@ -640,9 +640,7 @@ end function SquadAuto._fetchNextTeam(pagename, date) local conditions = Condition.Tree(BooleanOperator.all) :add{ - Condition.Tree(BooleanOperator.any):add{ - Condition.Node(Condition.ColumnName('player'), Comparator.eq, pagename), - Condition.Node(Condition.ColumnName('player'), Comparator.eq, string.gsub(pagename, ' ', '_')), + Condition.Util.anyOf(Condition.ColumnName('player'), {pagename, string.gsub(pagename, ' ', '_')}), }, Condition.Tree(BooleanOperator.any):add{ Condition.Node(Condition.ColumnName('date'), Comparator.gt, date), From aec078ba76a58caaa71b5cae180a3f19a19638d2 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Thu, 30 Oct 2025 13:46:28 +0100 Subject: [PATCH 26/29] Use emptyOr --- lua/wikis/commons/Squad/Auto.lua | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index c884365124d..832e692d9c5 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -74,7 +74,7 @@ end) ---@field type SquadType ---@field title string? ---@field teams string[]? ----@field roles {excluded: string[], included: string[]} +---@field roles {excluded: string[]?, included: string[]?} ---@enum TransferType SquadAuto.TransferType = { @@ -160,10 +160,16 @@ function SquadAuto:parseConfig() status = status, title = args.title, roles = { - included = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.roles)) - or DEFAULT_INCLUDED_ROLES[type][status]or DEFAULT_INCLUDED_ROLES[type].DEFAULT, - excluded = Logic.nilIfEmpty(Array.parseCommaSeparatedString(args.not_roles)) - or DEFAULT_EXCLUDED_ROLES[type][status] or DEFAULT_EXCLUDED_ROLES[type].DEFAULT, + included = Logic.emptyOr( + Array.parseCommaSeparatedString(args.roles), + DEFAULT_INCLUDED_ROLES[type][status], + DEFAULT_INCLUDED_ROLES[type].DEFAULT + ), + excluded = Logic.emptyOr( + Array.parseCommaSeparatedString(args.not_roles), + DEFAULT_EXCLUDED_ROLES[type][status], + DEFAULT_EXCLUDED_ROLES[type].DEFAULT + ) } } From 594898472a24b3db3b82536c30d69e8de2ea75b4 Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Thu, 30 Oct 2025 13:46:46 +0100 Subject: [PATCH 27/29] Use Side enum --- lua/wikis/commons/Squad/Auto.lua | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 832e692d9c5..350e2c6954e 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -99,6 +99,12 @@ SquadAuto.TransferType = { ---@field toRole string? ---@field faction string? +---@enum Side +local Side = { + from = 'from', + to = 'to', +} + local ROLE_INACTIVE = 'Inactive' -- TODO: Replace with Module:Roles @@ -136,7 +142,7 @@ local DEFAULT_EXCLUDED_ROLES = { } ---Entrypoint for SquadAuto tables ----@param frame table +---@param frame Frame|table ---@return Widget|Html|string? function SquadAuto.run(frame) local autosquad = SquadAuto(frame) @@ -351,7 +357,7 @@ function SquadAuto:queryTransfers() return Array.any(self.config.teams, FnUtil.curry(Operator.eq, team)) end - ---@param side 'from' | 'to' + ---@param side Side ---@param transfer transfer ---@return string | nil, boolean local function parseRelevantTeam(side, transfer) @@ -383,7 +389,7 @@ function SquadAuto:queryTransfers() end ---Parses the relevant role for the current team from a transfer - ---@param side 'from' | 'to' + ---@param side Side ---@param transfer transfer ---@param team string? ---@param isMain boolean @@ -394,9 +400,9 @@ function SquadAuto:queryTransfers() end if isMain then - return side == 'from' and transfer.role1 or transfer.role2 + return side == Side.from and transfer.role1 or transfer.role2 else - return side == 'from' and transfer.extradata.role1sec or transfer.extradata.role2sec + return side == Side.from and transfer.extradata.role1sec or transfer.extradata.role2sec end end @@ -421,8 +427,8 @@ function SquadAuto:queryTransfers() record.extradata = record.extradata or {} - local relevantFromTeam, isFromMain = parseRelevantTeam('from', record) - local relevantToTeam, isToMain = parseRelevantTeam('to', record) + local relevantFromTeam, isFromMain = parseRelevantTeam(Side.from, record) + local relevantToTeam, isToMain = parseRelevantTeam(Side.to, record) local transferType = getTransferType(relevantFromTeam, relevantToTeam) -- For leave transfers: Pass on new team for display as next team @@ -447,8 +453,8 @@ function SquadAuto:queryTransfers() references = record.reference, -- Roles - fromRole = parseRelevantRole('from', record, relevantFromTeam, isFromMain), - toRole = parseRelevantRole('to', record, relevantToTeam, isToMain), + fromRole = parseRelevantRole(Side.from, record, relevantFromTeam, isFromMain), + toRole = parseRelevantRole(Side.to, record, relevantToTeam, isToMain), fromTeam = relevantFromTeam, toTeam = relevantToTeam, @@ -647,7 +653,6 @@ function SquadAuto._fetchNextTeam(pagename, date) local conditions = Condition.Tree(BooleanOperator.all) :add{ Condition.Util.anyOf(Condition.ColumnName('player'), {pagename, string.gsub(pagename, ' ', '_')}), - }, Condition.Tree(BooleanOperator.any):add{ Condition.Node(Condition.ColumnName('date'), Comparator.gt, date), } From 3420480c0a9062dbd15daf5387ed85c8fcdc202f Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Thu, 30 Oct 2025 13:47:12 +0100 Subject: [PATCH 28/29] Make enum name more specific --- lua/wikis/commons/Squad/Auto.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 350e2c6954e..981c29981d7 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -99,7 +99,7 @@ SquadAuto.TransferType = { ---@field toRole string? ---@field faction string? ----@enum Side +---@enum TransferSide local Side = { from = 'from', to = 'to', @@ -357,7 +357,7 @@ function SquadAuto:queryTransfers() return Array.any(self.config.teams, FnUtil.curry(Operator.eq, team)) end - ---@param side Side + ---@param side TransferSide ---@param transfer transfer ---@return string | nil, boolean local function parseRelevantTeam(side, transfer) @@ -389,7 +389,7 @@ function SquadAuto:queryTransfers() end ---Parses the relevant role for the current team from a transfer - ---@param side Side + ---@param side TransferSide ---@param transfer transfer ---@param team string? ---@param isMain boolean From 18ac09d5b34232ec84a0d9d6b115bd78c85ff50e Mon Sep 17 00:00:00 2001 From: SyntacticSalt Date: Fri, 31 Oct 2025 16:28:04 +0100 Subject: [PATCH 29/29] Implement sort using SquadAuto/rank --- lua/wikis/commons/Squad/Auto.lua | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lua/wikis/commons/Squad/Auto.lua b/lua/wikis/commons/Squad/Auto.lua index 981c29981d7..f58c2a69e50 100644 --- a/lua/wikis/commons/Squad/Auto.lua +++ b/lua/wikis/commons/Squad/Auto.lua @@ -25,6 +25,8 @@ local TeamTemplate = Lua.import('Module:TeamTemplate') local SquadUtils = Lua.import('Module:Squad/Utils') local SquadCustom = Lua.import('Module:Squad/Custom') +local SquadAutoRank = Lua.import('Module:SquadAuto/rank', {loadData=true}) + local BooleanOperator = Condition.BooleanOperator local Comparator = Condition.Comparator @@ -105,6 +107,9 @@ local Side = { to = 'to', } +-- Default key for SquadAuto/rank +local DEFAULT_RANK_KEY = '' + local ROLE_INACTIVE = 'Inactive' -- TODO: Replace with Module:Roles @@ -211,7 +216,8 @@ function SquadAuto:display(entries) return self:displayTabs(entries) end - entries = SquadAuto._sortEntries(entries) + local useRankSort = self.config.status == SquadUtils.SquadStatus.ACTIVE + entries = SquadAuto._sortEntries(entries, useRankSort) return SquadCustom.runAuto(entries, self.config.status, self.config.type, self.config.title) end @@ -674,10 +680,16 @@ end -- Active entries (no leavedate) sorted by joindate, -- Former entries sorted by leavedate ---@param entries SquadAutoPerson[] +---@param useRankSort boolean? ---@return SquadAutoPerson[] -function SquadAuto._sortEntries(entries) +function SquadAuto._sortEntries(entries, useRankSort) return Array.sortBy(entries, function (element) - return {element.leavedate or element.joindate, element.id} + return { + useRankSort and SquadAutoRank[element.thisTeam.position] or SquadAutoRank[DEFAULT_RANK_KEY], + useRankSort and SquadAutoRank[element.thisTeam.role] or SquadAutoRank[DEFAULT_RANK_KEY], + element.leavedate or element.joindate, + element.id + } end) end