From 4f7ddcfbb3b76738e596100410694beb008a0ab1 Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Sun, 10 May 2026 04:45:59 +0200 Subject: [PATCH 01/22] # This is a combination of 16 commits. # This is the 1st commit message: Create TeamListConversion.md # This is the commit message #2: Update TeamListConversion.md # This is the commit message #3: Update TeamListConversion.md # This is the commit message #4: Update TeamListConversion.md # This is the commit message #5: Update TeamListConversion.md # This is the commit message #6: Update TeamListConversion.md # This is the commit message #7: Update TeamListConversion.md # This is the commit message #8: Update TeamListConversion.md # This is the commit message #9: Update TeamListConversion.md # This is the commit message #10: Update TeamListConversion.md # This is the commit message #11: Update TeamListConversion.md # This is the commit message #12: Update TeamListConversion.md # This is the commit message #13: Update TeamListConversion.md # This is the commit message #14: Update TeamListConversion.md # This is the commit message #15: Update TeamListConversion.md # This is the commit message #16: Update TeamListConversion.md --- TeamListConversion.md | 85 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 TeamListConversion.md diff --git a/TeamListConversion.md b/TeamListConversion.md new file mode 100644 index 00000000000..391390faed3 --- /dev/null +++ b/TeamListConversion.md @@ -0,0 +1,85 @@ +# Conversion of Template:TeamList + +## Affected Wikis +- stormgate: 8 pages +- starcraft2: ~600 pages +- starcraft: ~2k pages + +## Steps +- [x] Convert all Template:TeamList/Team calls to Template:TeamCard calls +- [x] Delete Template:TeamList/Team +- [x] Convert all usages of pure TeamCard calls (usually with box stuff arround them) to use TeamList wrapper (TeamCard already uses TeamList under the hood) +- [x] Clean up `Toggle group start`/`Toggle group end` usages in combi with TeamList +- [ ] wait for necessary features of TeamParticipants + - [ ] #6872 + - [ ] #7319 + - [ ] check if the sc(2) specific TC "roles" (captain, 2v2) work in TeamParticipants, if not see how to make them work + - [ ] check if combi of DNP & captain "role" work in TeamParticipants, if not see how to make it work + - [ ] check how player notes are handled and if this is doable ... +- [ ] Write a conversion wrapper (as dev of TeamList modules) +- [ ] Test conversion wrapper (incl perf test) +- [ ] Inplace replace TeamList modules with the conversion wrapper +- [ ] Replace TeamCard and TeamList/Section usage with jsons (`subst:#json:`, TeamCard already does ecaxtly that when reaching this point, TeamList/Section only adds a single param) + - [ ] sc2 + - [ ] sg + - [ ] bw +- [ ] Delete Template:TeamCard on all 3 wikis & delete Template:TeamList/Section on commons + - [ ] sc2 + - [ ] sg + - [ ] bw + - [ ] commons +- [ ] If there are no issues mentioned within X months after inplace conversion start a (subst) replace run to use the option of the conversion wrapper to generate the wiki code + - [ ] sc2 + - [ ] sg + - [ ] bw +- [ ] Archive/Delete the TeamList modules (i.e. the conversion wrappers) and Template:TeamList + +## Conversion wrapper +- Basically mirror what TeamList modules do (import auto dnp etc pp) just without display and without storage +- Instead of display/storage convert the collected data to new params and call TeamParticipants with them + - If section stuff is used each section has to call TeamParticipants stuff and then wrap all the TeamParticipants calls into Tabs dynamic +- Add an **option** to generate wiki code instead of calling TeamParticipants +- Add a check that adds a cleanup category if it finds `'<%s*br%s*/?>'` in any of the inputs + +### Param Mapping +#### new params +##### top level +- minimumplayers --> useless +- showplayerinfo --> useless +- mergeStaffTabIfOnlyOneStaff --> useless +- sortAlphabetically (#7319) --> legacy wrapper will do the sorting beforehand +- date +- store +- |X= --> Json of team data + +##### team level +- contenders --> useless +- qualification --> useless +- syncPlayerTeam --> false (legacy wrapper will do it beforehand) +- import --> always false (just fucks things up on these 3 wikis (plus warcraft) ...), better would be to forbid this entirely ... +- autoplayed --> (#6872) false (legacy wrapper will do it beforehand) +- date +- aliases +- notes + - --> {{Json|note1|note2|note3}} + - --> note conversion has to be done manually as most of the notes are below the TeamLists and only a numebr is set in the TeamCards ... + - --> check how to handle the player notes (possibly a cluster fuck ...) ... +- players --> Json-Array of player level inputs +- template --> team template + +##### player level +- trophies --> nil, unwanted +- number --> nil, unwanted +- type --> always `'player'`, we do not have nor want the staff shit at all ... +- played --> provided by wrapper info +- results --> nil (defaults to played input) +- role --> 'Captain'/'2v2'/nil +- status --> 'former'/'sub'/nil, unknown how to handle this atm +- name +- flag +- faction +- link +- team --> if different from main team ... + +### data available when mapping +todo - will be added at a later date (when i have the time to sift through the current module + check stuff with logging data) From 7976c3f7607010e1b244db22788684e52337c686 Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Mon, 11 May 2026 22:10:24 +0200 Subject: [PATCH 02/22] Create TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md Update TeamListConversion.md some more --- lua/wikis/commons/TeamList/Starcraft.lua | 334 ++++++++++++++++++ .../commons/TeamList/Starcraft/TeamCard.lua | 302 ++++++++++++++++ 2 files changed, 636 insertions(+) create mode 100644 lua/wikis/commons/TeamList/Starcraft.lua create mode 100644 lua/wikis/commons/TeamList/Starcraft/TeamCard.lua diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua new file mode 100644 index 00000000000..ae9001e4301 --- /dev/null +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -0,0 +1,334 @@ +--- +-- for @Liquipedia by @hjpalpha +-- page=Module:TeamList/Starcraft +-- + +--[[ + +todo: +- sections marked with todo (especially mapping ...) +- debug +- double check if i missed some new args (i.e. compare to the md) + +]] + +local Arguments = require('Module:Arguments') +local Array = require('Module:Array') +local Class = require('Module:Class') +local Json = require('Module:Json') +local Logic = require('Module:Logic') +local Lua = require('Module:Lua') +local Table = require('Module:Table') + +local TeamCard = Lua.import('Module:TeamList/Starcraft/TeamCard') +local TournamentStructure = Lua.import('Module:TournamentStructure') + +local Opponent = Lua.import('Module:Opponent/Custom') + +local TeamParticipantsController = Lua.import('Module:TeamParticipants/Controller') +local Tabs = Lua.import('Module:Tabs') + +local TeamListWrapper = {} + +---@class StarcraftTeamList +---@operator call(table): StarcraftTeamList +---@field args table +---@field config StarcraftTeamListConfig +---@field sections StarcraftTeamListSection[] +---@field root Html? +local TeamList = Class.new( + function(self, args) + self.args = args + end +) + +---@param frame Frame +---@return Html? +function TeamListWrapper.TemplateTeamList(frame) + local args = Arguments.getArgs(frame) + local teamList = TeamList(args):read() + + mw.logObject(teamList) -- todo: remove once mapping works + + local newArgs = teamList:map() -- todo + teamList = nil + + mw.logObject(newArgs) -- todo: remove once mapping works + + if Logic.readBool(args.generate) then + TeamListWrapper.generate(newArgs) -- todo + end + + if not newArgs[2] then + return TeamParticipantsController.fromTemplate(newArgs[1]) + end + + local tabArgs = {} + Array.forEach(newArgs, function(tpArgs, index) + if not tpArgs.title then + -- todo: add tracking category + end + tabArgs['name' .. index] = tpArgs.title + tabArgs['content' .. index] = TeamParticipantsController.fromTemplate(tpArgs) + end) + + return Tabs.dynamic(tabArgs) +end + +---@param table[] +---@return string +function TeamListWrapper.generate(args) + if not args[2] then + return TeamListWrapper.generateSingle(args[1]) + end + + local parts = {'{{Tabs dynamic'} + Array.forEach(args, function(tpArgs, index) + table.insert(parts, '|name' .. index .. '=' .. tpArgs.title) + end) + table.insert(parts, '|This=1') + table.insert(parts, '}}') + + Array.forEach(args, function(tpArgs, index) + table.insert(parts, '{{Tabs dynamic/tab|' .. index .. '}}') + table.insert(parts, TeamListWrapper.generateSingle(tpArgs)) + end) + + table.insert(parts, '{{Tabs dynamic/end}}') + + return table.concat(parts, '\n') +end + +---@param table +---@return string +function TeamListWrapper.generateSingle(args) + local parts = { + '{{TeamParticipants', + TeamListWrapper.generateOuterConfig(args), --todo + } + + Array.forEach(args, function(oppArgs) + table.insert(parts, TeamListWrapper.generateOpponent(oppArgs)) + end) + table.insert(parts, '}}') + + return table.concat(parts, '\n') +end + +---@param table +---@return string? +function TeamListWrapper.generateOuterConfig(args) +--todo +end + + +---@param table +---@return string +function TeamListWrapper.generateOpponent(args) + local parts = { + '\n|{{Opponent|' .. args[1], + '\n\n|players={{Persons', + } + + Array.forEach(args.players, function(playerArgs) + table.insert(parts, TeamListWrapper.generatePlayer(playerArgs)) + end) + + table.insert(parts, '\t\t}}') + table.insert(parts, '\t}}') + + return table.concat(parts, '\n') +end + +---@param table +---@return string +function TeamListWrapper.generatePlayer(args) + local parts = { + '\t\t\t|{{Person|', + } + + -- todo (flag, role?, link?, faction, team?) + + table.insert(parts, '}}') + + return table.concat(parts) +end + +---@return self +function TeamList:read() + self.config = TeamList.readConfig(self.args) + self:readSections() + + return self +end + +---@return table[] +function TeamList.map() +-- todo +end + +---@class StarcraftTeamListConfig: StarcraftTeamCardConfig +---@field showCountBySection boolean +---@field count number? +---@field title string? +---@field sortTeams boolean +---@field playerInfoButton boolean +---@field matchGroupSpec {matchGroupIds: string[], pageNames: string[]}? +---@field import boolean +---@field importOnlyQualified boolean + +---@param args table +---@param parentConfig StarcraftTeamListConfig? +---@return StarcraftTeamListConfig +function TeamList.readConfig(args, parentConfig) + parentConfig = parentConfig or {} + + local matchGroupSpec = TournamentStructure.readMatchGroupsSpec(args) + local import = matchGroupSpec ~= nil + + local config = { + --display + showCountBySection = Logic.readBool(args.showCountBySection or parentConfig.showCountBySection), + count = tonumber(args.count), + title = args.title, + sortTeams = Logic.nilOr(Logic.readBoolOrNil(args.sortTeams), parentConfig.sortTeams, true), + playerInfoButton = Logic.readBool(args.playerInfoButton), + isAdhoc = Logic.nilOr(Logic.readBoolOrNil(args.adhoc), parentConfig.isAdhoc, false), + --import + matchGroupSpec = matchGroupSpec, + import = import, + importOnlyQualified = Logic.readBool(args.onlyQualified), + autoDnp = Logic.nilOr(Logic.readBoolOrNil(args.autoDnp), import or parentConfig.import or nil), + } + + return Table.merge(TeamCard.readConfig(args, parentConfig), config) +end + +function TeamList:readSections() + self.sections = {} + + local firstSection = Json.parseIfTable(self.args[1]) + if not firstSection then + return + elseif firstSection.type ~= 'section' then + --assume no sections and treat whole list as first section + table.insert(self.sections, self:readSection(self.args)) + return + end + + Array.forEach(self.args, function(potentialSection) + local sectionArgs = Json.parseIfTable(potentialSection) + assert(sectionArgs and sectionArgs.type == 'section', 'Invalid input: "' .. potentialSection .. '" is not a section') + table.insert(self.sections, self:readSection(sectionArgs)) + end) +end + +---@class StarcraftTeamListSection +---@field config StarcraftTeamListConfig +---@field title string? +---@field entries StarcraftTeamCard[] + +---@param sectionArgs table +---@return StarcraftTeamListSection +function TeamList:readSection(sectionArgs) + local section = {config = TeamList.readConfig(sectionArgs, self.config), title = sectionArgs.title} + local entriesByName = {} + + sectionArgs = Array.extractValues(Table.filterByKey(sectionArgs, function(key) return type(key) == 'number' end)) + + Array.forEach(sectionArgs, function(teamCardArgs) + local entry = TeamCard(Table.merge({date = section.config.resolveDate}, Json.parseIfTable(teamCardArgs))) + entriesByName[entry.name] = entry + end) + + section.entries = Array.map(self:import(section.config, entriesByName), function(entry) + return entry:getConfig(section.config):sync(self.config.matchGroupSpec) + end) + + return section +end + +---@param config StarcraftTeamListConfig +---@param entriesByName table +---@return StarcraftTeamCard[] +function TeamList:import(config, entriesByName) + if not config.import then + return Array.extractValues(entriesByName) + end + + local matchRecords = TeamList._fetchMatchRecords(config.matchGroupSpec) + if Table.isEmpty(matchRecords) then + return Array.extractValues(entriesByName) + end + ---@cast matchRecords -nil + return TeamList._entriesFromMatchRecords(matchRecords, config, entriesByName) +end + +---@param matchGroupSpec {matchGroupIds: string[], pageNames: string[]} +---@return table[] +function TeamList._fetchMatchRecords(matchGroupSpec) + return mw.ext.LiquipediaDB.lpdb('match2', { + conditions = tostring(TournamentStructure.getMatch2Filter(matchGroupSpec)), + query = 'pagename, match2bracketdata, match2opponents, winner', + order = 'date asc', + limit = 5000, + }) +end + +---@param matchRecords table[] +---@param config StarcraftTeamListConfig +---@param entriesByName table +---@return StarcraftTeamCard[] +function TeamList._entriesFromMatchRecords(matchRecords, config, entriesByName) + Array.forEach(matchRecords, function(matchRecord) + Array.forEach(matchRecord.match2opponents, function(opponentRecord, opponentIndex) + if not TeamList._shouldInclude(opponentIndex, matchRecord, config.importOnlyQualified) then + return + end + + if entriesByName[opponentRecord.name] then + return + end + + entriesByName[opponentRecord.name] = TeamList._entryFromOpponentRecord(opponentRecord, config.resolveDate) + end) + end) + + return Array.extractValues(entriesByName) +end + +---@param opponentIndex integer +---@param matchRecord table +---@param importOnlyQualified boolean? +---@return boolean +function TeamList._shouldInclude(opponentIndex, matchRecord, importOnlyQualified) + local bracketData = matchRecord.match2bracketdata + return not importOnlyQualified or Logic.readBool(bracketData.quallose) or + Logic.readBool(bracketData.qualwin) and tonumber(matchRecord.winner) == opponentIndex +end + +---@param opponentRecord table +---@param resolveDate string +---@return StarcraftTeamCard? +function TeamList._entryFromOpponentRecord(opponentRecord, resolveDate) + if opponentRecord.type ~= Opponent.team or not opponentRecord.template or opponentRecord.template:lower() == 'tbd' then + return + end + + local opponentArgs = { + team = opponentRecord.template, + date = resolveDate + } + + Array.forEach(opponentRecord.match2players, function(playerRecord, playerIndex) + local prefix = 'p' .. playerIndex + opponentArgs[prefix] = playerRecord.displayname + opponentArgs[prefix .. 'link'] = playerRecord.name + opponentArgs[prefix .. 'flag'] = playerRecord.flag + opponentArgs[prefix .. 'faction'] = (playerRecord.extradata or {}).faction + end) + + return TeamCard(opponentArgs) +end + +return TeamListWrapper diff --git a/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua new file mode 100644 index 00000000000..b8aa5c7db72 --- /dev/null +++ b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua @@ -0,0 +1,302 @@ +--- +-- for @Liquipedia by @hjpalpha +-- page=Module:TeamList/Starcraft/TeamCard +-- + +local Lua = require('Module:Lua') + +local Array = Lua.import('Module:Array') +local Class = Lua.import('Module:Class') +local Faction = Lua.import('Module:Faction') +local Flags = Lua.import('Module:Flags') +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 Namespace = Lua.import('Module:Namespace') +local String = Lua.import('Module:StringUtils') +local Table = Lua.import('Module:Table') +local TeamTemplate = Lua.import('Module:TeamTemplate') +local Variables = Lua.import('Module:Variables') + +local PlayerExt = Lua.import('Module:Player/Ext') +local PlayerExtCustom = Lua.import('Module:Player/Ext/Custom') +local TournamentStructure = Lua.import('Module:TournamentStructure') + +local Opponent = Lua.import('Module:Opponent/Custom') + +local getContextualDateOrNow = function() + local date = Variables.varDefault('tournament_enddate') + or Variables.varDefault('tournament_startdate') + + if Logic.isNotEmpty(date) then return date end + + local pageName = mw.title.getCurrentTitle().prefixedText:gsub(' ', '_') + + local data = mw.ext.LiquipediaDB.lpdb('tournament', { + conditions = '.[[pagename::' .. pageName .. ']]', + query = 'startdate, enddate', + limit = 1, + })[1] or {} + + return Logic.nilIfEmpty(data.enddate) + or Logic.nilIfEmpty(data.enddate) + or os.date('%F') +end + +---@class StarcraftTeamCard +---@operator call(table): StarcraftTeamCard +---@field args table +---@field config StarcraftTeamCardConfig +---@field opponent StarcraftTeamCardOpponent +---@field name string +---@field root Html? +local TeamCard = Class.new( + function(self, args) + self.args = args + self.opponent = self:readOpponent() + local opponentName = Opponent.toName(self.opponent) + assert(opponentName, 'Missing Team Template for "' .. (args.team or '') .. '"') + self.name = opponentName:gsub(' ', '_') + end +) + +---@class StarcraftTeamCardOpponent: StarcraftStandardOpponent +---@field players StarcraftTeamCardPlayer[] +---@field note string? +---@field dq boolean +---@field subtitle string? +---@field date string + +---@return StarcraftTeamCardOpponent +function TeamCard:readOpponent() + local args = self.args + local date = args.date or getContextualDateOrNow() + local team = (args.team or 'tbd'):lower():gsub('_', ' ') + local opponent = Opponent.resolve(Opponent.readOpponentArgs{team, type = Opponent.team}, date) + + opponent.dq = Logic.readBool(args.dq) + opponent.date = date + opponent.note = args.note + + opponent.players = Array.extractValues(Table.mapArgumentsByPrefix(args, {'p', 'player'}, function(key, index) + return self:readPlayer(key, index, date) + end)) + + if #opponent.players >= 35 then + mw.ext.TeamLiquidIntegration.add_category('TeamCards with 35 players') + elseif #opponent.players >= 25 then + mw.ext.TeamLiquidIntegration.add_category('TeamCards with 25 players') + elseif #opponent.players >= 20 then + mw.ext.TeamLiquidIntegration.add_category('TeamCards with 20 players') + end + + return opponent +end + +---@class StarcraftTeamCardPlayer: StarcraftStandardPlayer +---@field ace boolean? +---@field captain boolean? +---@field dnp boolean? +---@field dq boolean? +---@field joker boolean? +---@field mainTeam string? +---@field mainTeamPage string? +---@field note boolean? +---@field tag string? +---@field tagTitle string? +---@field two boolean? +---@field withdraw boolean? + +---@param key any +---@param index integer +---@param date string +---@return StarcraftTeamCardPlayer +function TeamCard:readPlayer(key, index, date) + local args = self.args + + local getArg = function(field) + return args['p' .. index .. field] or args[field .. index] + end + + local mainTeamInput = getArg('team') + if mainTeamInput and mainTeamInput:lower() == 'noteam' then + mainTeamInput = nil + end + + local mainTeam, mainTeamPage + if mainTeamInput then + mainTeam = TeamTemplate.resolve(mainTeamInput, date) + assert(mainTeam, 'missing team template "' .. mainTeamInput .. '"') + mainTeamPage = TeamTemplate.getPageName(mainTeam) or nil + end + + return { + displayName = args[key], + flag = String.nilIfEmpty(Flags.CountryName{flag = getArg('flag')}), + pageName = getArg('link'), + faction = Faction.read(getArg('faction') or getArg('race')), + + ace = Logic.readBoolOrNil(getArg('ace')), + captain = Logic.readBoolOrNil(getArg('captain')), + dnp = Logic.readBoolOrNil(getArg('dnp')), + dq = Logic.readBoolOrNil(getArg('dq') or getArg('out')), + joker = Logic.readBoolOrNil(getArg('joker')), + mainTeam = mainTeam, + mainTeamPage = mainTeamPage, + note = getArg('note'), + tag = getArg('tag'), + tagTitle = getArg('tagTitle'), + two = Logic.readBoolOrNil(getArg('two')), + withdraw = Logic.readBoolOrNil(getArg('withdraw')), + } +end + +---@class StarcraftTeamCardConfig +---@field cardWidth string +---@field teamStyle string? +---@field showFlags boolean +---@field display boolean +---@field collapsed boolean +---@field collapsible boolean? +---@field autoDnp boolean +---@field syncPlayers boolean +---@field resolveDate string +---@field sortPlayers boolean +---@field noStorage boolean +---@field isAdhoc boolean? + +---@param parentConfig StarcraftTeamListConfig? +---@return self +function TeamCard:getConfig(parentConfig) + self.config = TeamCard.readConfig(self.args, parentConfig) + + return self +end + +---@param args table +---@param parentConfig StarcraftTeamListConfig? +---@return StarcraftTeamListConfig +function TeamCard.readConfig(args, parentConfig) + parentConfig = parentConfig or {} + + local width = tonumber(args.cardWidth or args.width) + + return { + --display + cardWidth = width and (width .. 'px') or args.cardWidth or args.width or parentConfig.cardWidth or '240px', + teamStyle = Logic.readBool(args.short) and 'short' or parentConfig.teamStyle, + showFlags = Logic.nilOr(Logic.readBoolOrNil(args.showFlags), parentConfig.showFlags, true), + display = not Logic.readBool(args.hidden), + collapsed =Logic.nilOr(Logic.readBoolOrNil(args.collapsed), not Logic.readBoolOrNil(args.uncollapsed)), + collapsible = Logic.nilOr(Logic.readBoolOrNil(args.collapsible), parentConfig.collapsible, true), + --sync + autoDnp = Logic.nilOr(Logic.readBoolOrNil(args.autoDnp), parentConfig.autoDnp, true), + syncPlayers = Logic.nilOr(Logic.readBoolOrNil(args.syncPlayers), parentConfig.syncPlayers, true), + resolveDate = args.date or parentConfig.resolveDate or getContextualDateOrNow(), + sortPlayers = Logic.nilOr(Logic.readBoolOrNil(args.sortPlayers), parentConfig.sortPlayers, true), + --storage + noStorage = Logic.readBool(args.noStorage or parentConfig.noStorage or + Lpdb.isStorageDisabled() or not Namespace.isMain()), + isAdhoc = Logic.nilOr(Logic.readBoolOrNil(args.adhoc), parentConfig.isAdhoc), + } +end + +---@param parentMatchGroupSpec {matchGroupIds: string[], pageNames: string[]}? +---@return self +function TeamCard:sync(parentMatchGroupSpec) + local config = self.config + + local players = self.opponent.players + + if Table.isEmpty(players) then + return self + end + + local date = self.opponent.date + + if config.syncPlayers then + players = Array.map(players, function(player) + player = Table.merge(player, PlayerExtCustom.syncPlayer(player, {date = date})) + player.pageName = player.pageName:gsub(' ', '_') + player.mainTeam = config.isAdhoc and PlayerExt.syncTeam(player.pageName, player.mainTeam) or player.mainTeam + player.mainTeamPage = player.mainTeamPage or + player.mainTeam and TeamTemplate.getPageName(TeamTemplate.resolve(player.mainTeam, date)) or + nil + + return player + end) + end + + if config.autoDnp then + local matchGroupSpec = parentMatchGroupSpec or TournamentStructure.currentPageSpec() + players = self:dnp(players, matchGroupSpec) + end + + if config.sortPlayers then + Array.sortInPlaceBy(players, function(player) return player.displayName:lower() end) + end + + self.opponent.players = players + + return self +end + +---@param players StarcraftTeamCardPlayer[] +---@param matchGroupSpec {matchGroupIds: string[], pageNames: string[]} +---@return StarcraftTeamCardPlayer[] +function TeamCard:dnp(players, matchGroupSpec) + local dnpData = TeamCard.fetchDnp(matchGroupSpec) + + Array.map(players, function(player) + player.dnp = player.dnp or (dnpData[self.name] and not dnpData[self.name][player.pageName]) + + return player + end) + + return players +end + +TeamCard.fetchDnp = FnUtil.memoize(function(matchGroupSpec) + return TeamCard.fetchDnpData(matchGroupSpec) +end) + +---@param matchGroupSpec {matchGroupIds: string[], pageNames: string[]} +---@return table> +function TeamCard.fetchDnpData(matchGroupSpec) + local matchRecords = mw.ext.LiquipediaDB.lpdb('match2', { + conditions = tostring(TournamentStructure.getMatch2Filter(matchGroupSpec)), + query = 'pagename, match2bracketdata, match2opponents, winner, match2games', + order = 'date asc', + limit = 5000, + }) + + local playersByTeam = {} + Array.forEach(matchRecords, function(matchRecord) + local teams = Array.map(matchRecord.match2opponents, function(opponent, opponentIndex) + playersByTeam[opponent.name] = playersByTeam[opponent.name] or {} + return {name = opponent.name, players = Array.map(opponent.match2players, function(player) return player.name end)} + end) + + Array.forEach(matchRecord.match2games, function(game) + local gameOpponents = game.opponents + if type(gameOpponents) ~= 'table' then + gameOpponents = Json.parseIfTable(gameOpponents) or {} + end + Array.forEach(gameOpponents, function(opp, opponentIndex) + for playerIndex, player in pairs(opp.players or {}) do + if Logic.isNotEmpty(player) then + local matchPlayer = teams[opponentIndex].players[playerIndex] + if player then + playersByTeam[teams[opponentIndex].name][matchPlayer] = true + end + end + end + end) + end) + end) + + return playersByTeam +end + +return TeamCard From e9b721bb33d9fe5981a194aa04594338ac828a37 Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Mon, 11 May 2026 22:20:03 +0200 Subject: [PATCH 03/22] Update TeamCard.lua Update TeamCard.lua Update Starcraft.lua --- lua/wikis/commons/TeamList/Starcraft.lua | 67 ++++++++++++++++--- .../commons/TeamList/Starcraft/TeamCard.lua | 4 +- 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index ae9001e4301..9a7cf8dd16c 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -8,7 +8,6 @@ todo: - sections marked with todo (especially mapping ...) - debug -- double check if i missed some new args (i.e. compare to the md) ]] @@ -56,7 +55,15 @@ function TeamListWrapper.TemplateTeamList(frame) mw.logObject(newArgs) -- todo: remove once mapping works if Logic.readBool(args.generate) then - TeamListWrapper.generate(newArgs) -- todo + TeamListWrapper.generate(newArgs) + end + + if Array.any(newArgs, function(section) + return Array.any(section, function(opp) + return Logic.isNotEmpty(opp.notes) + end) + end) then + mw.ext.TeamLiquidIntegration.add_category('TeamList with notes') end if not newArgs[2] then @@ -66,7 +73,7 @@ function TeamListWrapper.TemplateTeamList(frame) local tabArgs = {} Array.forEach(newArgs, function(tpArgs, index) if not tpArgs.title then - -- todo: add tracking category + mw.ext.TeamLiquidIntegration.add_category('TeamList with missing section title') end tabArgs['name' .. index] = tpArgs.title tabArgs['content' .. index] = TeamParticipantsController.fromTemplate(tpArgs) @@ -104,7 +111,7 @@ end function TeamListWrapper.generateSingle(args) local parts = { '{{TeamParticipants', - TeamListWrapper.generateOuterConfig(args), --todo + TeamListWrapper.generateOuterConfig(args), } Array.forEach(args, function(oppArgs) @@ -118,7 +125,21 @@ end ---@param table ---@return string? function TeamListWrapper.generateOuterConfig(args) ---todo + local params = { + 'showplayerinfo', + 'date', + } + + local parts = Array.map(params, function(param) + local value = args[param] + if Logic.isEmpty(value) then return end + return '|' .. param .. '=' .. value + end) + + local store = args.store == false and 'false' or 'false' -- todo: double check if includeonly or onlyinclude ... + table.insert(parts, '|store=' .. store) + + return table.concat(parts) end @@ -126,15 +147,25 @@ end ---@return string function TeamListWrapper.generateOpponent(args) local parts = { - '\n|{{Opponent|' .. args[1], - '\n\n|players={{Persons', + '\t|{{Opponent|' .. args.template, + '\t\t|import=false', + Logic.isNotEmpty(args.date) and ('\t\t|date=' .. args.date) or nil, } + table.insert(parts, '\t\t|players={{Persons') Array.forEach(args.players, function(playerArgs) table.insert(parts, TeamListWrapper.generatePlayer(playerArgs)) end) - table.insert(parts, '\t\t}}') + + if Logic.isNotEmpty(args.notes) then + table.insert(parts, '\t\t|notes={{Json') + Array.forEach(notes, function(note) + table.insert(parts, '\t\t\t|' .. note) + end) + table.insert(parts, '\t\t}}') + end + table.insert(parts, '\t}}') return table.concat(parts, '\n') @@ -144,10 +175,26 @@ end ---@return string function TeamListWrapper.generatePlayer(args) local parts = { - '\t\t\t|{{Person|', + '\t\t\t|{{Person|' .. args.name, } - -- todo (flag, role?, link?, faction, team?) + local add = function(param) + local value = args[param] + if Logic.isEmpty(value) then return end + table.insert(parts, '|' .. param .. '=' .. value) + end + + local params = { + 'link', + 'flag', + 'faction', + 'team', + 'role', + 'played', + 'results', + 'status', + } + Array.forEach(params, add) table.insert(parts, '}}') diff --git a/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua index b8aa5c7db72..89996eb0153 100644 --- a/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua +++ b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua @@ -25,6 +25,8 @@ local TournamentStructure = Lua.import('Module:TournamentStructure') local Opponent = Lua.import('Module:Opponent/Custom') +-- can't use the DateExt function +-- due to the wiki vars not existing if using subst bot run local getContextualDateOrNow = function() local date = Variables.varDefault('tournament_enddate') or Variables.varDefault('tournament_startdate') @@ -40,7 +42,7 @@ local getContextualDateOrNow = function() })[1] or {} return Logic.nilIfEmpty(data.enddate) - or Logic.nilIfEmpty(data.enddate) + or Logic.nilIfEmpty(data.startdate) or os.date('%F') end From bdfdf1c9a7624711bbe800cebdcc82a7971e046c Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 07:58:45 +0200 Subject: [PATCH 04/22] some window dressing --- lua/wikis/commons/TeamList/Starcraft.lua | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index 9a7cf8dd16c..831c41a7b8a 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -7,6 +7,7 @@ todo: - sections marked with todo (especially mapping ...) +- Add a check that adds a cleanup category if it finds `'<%s*br%s*/?>'` in any of the inputs - debug ]] @@ -42,7 +43,7 @@ local TeamList = Class.new( ) ---@param frame Frame ----@return Html? +---@return Renderable? function TeamListWrapper.TemplateTeamList(frame) local args = Arguments.getArgs(frame) local teamList = TeamList(args):read() @@ -50,6 +51,8 @@ function TeamListWrapper.TemplateTeamList(frame) mw.logObject(teamList) -- todo: remove once mapping works local newArgs = teamList:map() -- todo + -- throw the class away to not clog up memory + ---@diagnostic disable-next-line: cast-local-type teamList = nil mw.logObject(newArgs) -- todo: remove once mapping works @@ -82,7 +85,7 @@ function TeamListWrapper.TemplateTeamList(frame) return Tabs.dynamic(tabArgs) end ----@param table[] +---@param args table[] ---@return string function TeamListWrapper.generate(args) if not args[2] then @@ -106,7 +109,7 @@ function TeamListWrapper.generate(args) return table.concat(parts, '\n') end ----@param table +---@param args table ---@return string function TeamListWrapper.generateSingle(args) local parts = { @@ -122,7 +125,7 @@ function TeamListWrapper.generateSingle(args) return table.concat(parts, '\n') end ----@param table +---@param args table ---@return string? function TeamListWrapper.generateOuterConfig(args) local params = { @@ -143,7 +146,7 @@ function TeamListWrapper.generateOuterConfig(args) end ----@param table +---@param args table ---@return string function TeamListWrapper.generateOpponent(args) local parts = { @@ -160,7 +163,7 @@ function TeamListWrapper.generateOpponent(args) if Logic.isNotEmpty(args.notes) then table.insert(parts, '\t\t|notes={{Json') - Array.forEach(notes, function(note) + Array.forEach(args.notes, function(note) table.insert(parts, '\t\t\t|' .. note) end) table.insert(parts, '\t\t}}') @@ -171,7 +174,7 @@ function TeamListWrapper.generateOpponent(args) return table.concat(parts, '\n') end ----@param table +---@param args table ---@return string function TeamListWrapper.generatePlayer(args) local parts = { @@ -282,7 +285,7 @@ function TeamList:readSection(sectionArgs) local entriesByName = {} sectionArgs = Array.extractValues(Table.filterByKey(sectionArgs, function(key) return type(key) == 'number' end)) - + Array.forEach(sectionArgs, function(teamCardArgs) local entry = TeamCard(Table.merge({date = section.config.resolveDate}, Json.parseIfTable(teamCardArgs))) entriesByName[entry.name] = entry From 7f8bf493d4069c90a65a8531bdd668d9f0c6bd41 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 07:58:59 +0200 Subject: [PATCH 05/22] old params and their mapping to new ones --- TeamListConversion.md | 147 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 145 insertions(+), 2 deletions(-) diff --git a/TeamListConversion.md b/TeamListConversion.md index 391390faed3..a94b7ab8cd2 100644 --- a/TeamListConversion.md +++ b/TeamListConversion.md @@ -45,9 +45,9 @@ #### new params ##### top level - minimumplayers --> useless -- showplayerinfo --> useless - mergeStaffTabIfOnlyOneStaff --> useless - sortAlphabetically (#7319) --> legacy wrapper will do the sorting beforehand +- showplayerinfo - date - store - |X= --> Json of team data @@ -82,4 +82,147 @@ - team --> if different from main team ... ### data available when mapping -todo - will be added at a later date (when i have the time to sift through the current module + check stuff with logging data) +#### top level +- secitions: section[] +- config: + - drop in mapping (read from section level instead) + +#### section level +- title: string? + -> section.title +- entries TC[] + -> section.X +- config: + - showCountBySection: bool + -> adjust title in mapping + - count: number? + -> adjust title in mapping + - title: string? + -> section.title + - sortTeams: bool + -> if sorting already done before drop, else sort and drop + - playerInfoButton: bool + -> section.showplayerinfo + - matchGroupSpec: {matchGroupIds: string[], pageNames: string[]}? + -> drop + - import: bool + -> drop + - importOnlyQualified: bool + -> drop + - cardWidth: string + -> drop + - teamStyle: string? + -> drop + - showFlags: bool + -> drop + - display: bool + -> drop + - collapsed: bool + -> drop + - collapsible: bool + -> drop + - autoDnp: bool + -> drop + - syncPlayers: bool + -> drop + - resolveDate: string + -> section.date + - sortPlayers: bool + -> drop + - noStorage: bool + -> section.store (invert if not empty ...) + -> check if we have to adjust the base processing to get nil if unset here + - isAdhoc: bool + -> drop + +#### TC level +- name: string + -> drop +- opponent: StarcraftTeamCardOpponent + - players: player[] + -> map into .players + - note: string? + -> into .notes + - dq: bool + -> ??? (tracking category!) + - subtitle: string? + -> drop + - date: string + -> .date + - template: string + -> .template + -rest + -> drop +- config + - showCountBySection: bool + -> drop + - count: number? + -> drop + - title: string? + -> drop + - sortTeams: bool + -> drop + - playerInfoButton: bool + -> drop + - matchGroupSpec: {matchGroupIds: string[], pageNames: string[]}? + -> drop + - import: bool + -> drop + - importOnlyQualified: bool + -> drop + - cardWidth: string + -> drop + - teamStyle: string? + -> drop + - showFlags: bool + -> drop + - display: bool + -> drop + - collapsed: bool + -> drop + - collapsible: bool + -> drop + - autoDnp: bool + -> drop + - syncPlayers: bool + -> drop + - resolveDate: string + -> .date + - sortPlayers: bool + -> if sorting already done before drop, else sort and drop + - noStorage: bool + -> drop + - isAdhoc: bool + -> check if teams are already determind, if not do it + -> drop + +#### player level +- ace: boolean? + -> drop +- captain: boolean? +- dnp: boolean? +- dq: boolean? +- joker: boolean? + -> drop +- mainTeam: string? + -> drop +- mainTeamPage: string? + -> .team == mainTeamPage ~= TC.name and mainTeamPage or nil +- note: boolean? + -> into TC notes +- tag: string? + -> drop +- tagTitle: string? + -> drop +- two: boolean? + -> ??? (role?) +- withdraw: boolean? + -> ??? (|played=true|result=false???) +- displayName: string? + -> .name +- flag: string? + -> .flag +- pageName: string? + -> .link +- faction: string? + -> .faction From ef984b2233d6fbf6ff266230ec2af8057cac9f79 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 08:01:13 +0200 Subject: [PATCH 06/22] another tracking category stfu warnings --- lua/wikis/commons/TeamList/Starcraft.lua | 8 +++++++- lua/wikis/commons/TeamList/Starcraft/TeamCard.lua | 12 +++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index 831c41a7b8a..a13fc3e54db 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -7,7 +7,6 @@ todo: - sections marked with todo (especially mapping ...) -- Add a check that adds a cleanup category if it finds `'<%s*br%s*/?>'` in any of the inputs - debug ]] @@ -46,6 +45,13 @@ local TeamList = Class.new( ---@return Renderable? function TeamListWrapper.TemplateTeamList(frame) local args = Arguments.getArgs(frame) + + for _, item in pairs(args) do + if item:find('<%s*br%s*/?>') then + mw.ext.TeamLiquidIntegration.add_category('TeamList with br') + end + end + local teamList = TeamList(args):read() mw.logObject(teamList) -- todo: remove once mapping works diff --git a/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua index 89996eb0153..e8ccb51c9be 100644 --- a/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua +++ b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua @@ -43,7 +43,7 @@ local getContextualDateOrNow = function() return Logic.nilIfEmpty(data.enddate) or Logic.nilIfEmpty(data.startdate) - or os.date('%F') + or os.date('%F') --[[@as string]] end ---@class StarcraftTeamCard @@ -75,7 +75,7 @@ function TeamCard:readOpponent() local args = self.args local date = args.date or getContextualDateOrNow() local team = (args.team or 'tbd'):lower():gsub('_', ' ') - local opponent = Opponent.resolve(Opponent.readOpponentArgs{team, type = Opponent.team}, date) + local opponent = Opponent.resolve(Opponent.readOpponentArgs{team, type = Opponent.team}, date) --[[@as StarcraftTeamCardOpponent]] opponent.dq = Logic.readBool(args.dq) opponent.date = date @@ -221,9 +221,9 @@ function TeamCard:sync(parentMatchGroupSpec) players = Array.map(players, function(player) player = Table.merge(player, PlayerExtCustom.syncPlayer(player, {date = date})) player.pageName = player.pageName:gsub(' ', '_') - player.mainTeam = config.isAdhoc and PlayerExt.syncTeam(player.pageName, player.mainTeam) or player.mainTeam + player.mainTeam = config.isAdhoc and PlayerExt.syncTeam(player.pageName, player.mainTeam, {}) or player.mainTeam player.mainTeamPage = player.mainTeamPage or - player.mainTeam and TeamTemplate.getPageName(TeamTemplate.resolve(player.mainTeam, date)) or + player.mainTeam and TeamTemplate.getPageName(TeamTemplate.resolve(player.mainTeam, date) --[[@as string]]) or nil return player @@ -250,10 +250,8 @@ end function TeamCard:dnp(players, matchGroupSpec) local dnpData = TeamCard.fetchDnp(matchGroupSpec) - Array.map(players, function(player) + Array.forEach(players, function(player) player.dnp = player.dnp or (dnpData[self.name] and not dnpData[self.name][player.pageName]) - - return player end) return players From e5ebfa5725605bb252836936aa8c7417dbe0951b Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 09:27:44 +0200 Subject: [PATCH 07/22] start on the mapping rest of mapping (i hope) remove mapping stuff from md as it is in the modules now --- TeamListConversion.md | 186 ----------------------- lua/wikis/commons/TeamList/Starcraft.lua | 73 ++++++++- 2 files changed, 71 insertions(+), 188 deletions(-) diff --git a/TeamListConversion.md b/TeamListConversion.md index a94b7ab8cd2..5e0de8ba3b6 100644 --- a/TeamListConversion.md +++ b/TeamListConversion.md @@ -40,189 +40,3 @@ - If section stuff is used each section has to call TeamParticipants stuff and then wrap all the TeamParticipants calls into Tabs dynamic - Add an **option** to generate wiki code instead of calling TeamParticipants - Add a check that adds a cleanup category if it finds `'<%s*br%s*/?>'` in any of the inputs - -### Param Mapping -#### new params -##### top level -- minimumplayers --> useless -- mergeStaffTabIfOnlyOneStaff --> useless -- sortAlphabetically (#7319) --> legacy wrapper will do the sorting beforehand -- showplayerinfo -- date -- store -- |X= --> Json of team data - -##### team level -- contenders --> useless -- qualification --> useless -- syncPlayerTeam --> false (legacy wrapper will do it beforehand) -- import --> always false (just fucks things up on these 3 wikis (plus warcraft) ...), better would be to forbid this entirely ... -- autoplayed --> (#6872) false (legacy wrapper will do it beforehand) -- date -- aliases -- notes - - --> {{Json|note1|note2|note3}} - - --> note conversion has to be done manually as most of the notes are below the TeamLists and only a numebr is set in the TeamCards ... - - --> check how to handle the player notes (possibly a cluster fuck ...) ... -- players --> Json-Array of player level inputs -- template --> team template - -##### player level -- trophies --> nil, unwanted -- number --> nil, unwanted -- type --> always `'player'`, we do not have nor want the staff shit at all ... -- played --> provided by wrapper info -- results --> nil (defaults to played input) -- role --> 'Captain'/'2v2'/nil -- status --> 'former'/'sub'/nil, unknown how to handle this atm -- name -- flag -- faction -- link -- team --> if different from main team ... - -### data available when mapping -#### top level -- secitions: section[] -- config: - - drop in mapping (read from section level instead) - -#### section level -- title: string? - -> section.title -- entries TC[] - -> section.X -- config: - - showCountBySection: bool - -> adjust title in mapping - - count: number? - -> adjust title in mapping - - title: string? - -> section.title - - sortTeams: bool - -> if sorting already done before drop, else sort and drop - - playerInfoButton: bool - -> section.showplayerinfo - - matchGroupSpec: {matchGroupIds: string[], pageNames: string[]}? - -> drop - - import: bool - -> drop - - importOnlyQualified: bool - -> drop - - cardWidth: string - -> drop - - teamStyle: string? - -> drop - - showFlags: bool - -> drop - - display: bool - -> drop - - collapsed: bool - -> drop - - collapsible: bool - -> drop - - autoDnp: bool - -> drop - - syncPlayers: bool - -> drop - - resolveDate: string - -> section.date - - sortPlayers: bool - -> drop - - noStorage: bool - -> section.store (invert if not empty ...) - -> check if we have to adjust the base processing to get nil if unset here - - isAdhoc: bool - -> drop - -#### TC level -- name: string - -> drop -- opponent: StarcraftTeamCardOpponent - - players: player[] - -> map into .players - - note: string? - -> into .notes - - dq: bool - -> ??? (tracking category!) - - subtitle: string? - -> drop - - date: string - -> .date - - template: string - -> .template - -rest - -> drop -- config - - showCountBySection: bool - -> drop - - count: number? - -> drop - - title: string? - -> drop - - sortTeams: bool - -> drop - - playerInfoButton: bool - -> drop - - matchGroupSpec: {matchGroupIds: string[], pageNames: string[]}? - -> drop - - import: bool - -> drop - - importOnlyQualified: bool - -> drop - - cardWidth: string - -> drop - - teamStyle: string? - -> drop - - showFlags: bool - -> drop - - display: bool - -> drop - - collapsed: bool - -> drop - - collapsible: bool - -> drop - - autoDnp: bool - -> drop - - syncPlayers: bool - -> drop - - resolveDate: string - -> .date - - sortPlayers: bool - -> if sorting already done before drop, else sort and drop - - noStorage: bool - -> drop - - isAdhoc: bool - -> check if teams are already determind, if not do it - -> drop - -#### player level -- ace: boolean? - -> drop -- captain: boolean? -- dnp: boolean? -- dq: boolean? -- joker: boolean? - -> drop -- mainTeam: string? - -> drop -- mainTeamPage: string? - -> .team == mainTeamPage ~= TC.name and mainTeamPage or nil -- note: boolean? - -> into TC notes -- tag: string? - -> drop -- tagTitle: string? - -> drop -- two: boolean? - -> ??? (role?) -- withdraw: boolean? - -> ??? (|played=true|result=false???) -- displayName: string? - -> .name -- flag: string? - -> .flag -- pageName: string? - -> .link -- faction: string? - -> .faction diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index a13fc3e54db..a251ebf6d77 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -219,8 +219,77 @@ function TeamList:read() end ---@return table[] -function TeamList.map() --- todo +function TeamList:map() + return Array.map(self.sections, function(section) + local config = section.config + + local args = { + title = section.title or config.title, + showplayerinfo = config.playerInfoButton, + date = config.resolveDate, + store = not config.noStorage, + } + + if args.title and config.showCountBySection then + args.title = args.title .. ' (' .. (config.count or #section.entries) .. ')' + end + + if config.sortTeams then + Array.sortInPlaceBy(section.entries, function(entry) return entry.name:lower() end) + end + + Table.mergeInto(args, Array.map(section.entries, TeamList.mapEntry)) + end) +end + +---@param entry StarcraftTeamCard +---@return table +function TeamList.mapEntry(entry) + local opp = entry.opponent + local notes = {opp.note} + local args = { + players = Array.map(opp.players, function(player) + table.insert(notes, player.note) + return TeamList.mapPlayer(player) + end), + template = opp.template, + date = opp.date, + } + args.notes = notes + + if opp.dq then + mw.ext.TeamLiquidIntegration.add_category('TeamList with dq opponent') + end + + return args +end + +---@param player StarcraftTeamCardPlayer +---@return table +function TeamList.mapPlayer(player) + local args = { + name = player.displayName or player.pageName, + link = player.pageName, + flag = player.flag, + faction = player.faction, + team = player.mainTeamPage, + } + + if player.dnp then + args.played = 'false' + end + local role = player.captain and 'Captain' + or player['2v2'] and '2v2' + or nil + + if player.dq then + args.status = 'former' + args.result = 'false' + elseif player.withdraw then + args.status = 'former' + end + + return args end ---@class StarcraftTeamListConfig: StarcraftTeamCardConfig From eef0c56693ba582637f798954e439ade0c947764 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 09:49:28 +0200 Subject: [PATCH 08/22] lets kick this --- lua/wikis/commons/TeamList/Starcraft.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index a251ebf6d77..5129f34780f 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -278,9 +278,7 @@ function TeamList.mapPlayer(player) if player.dnp then args.played = 'false' end - local role = player.captain and 'Captain' - or player['2v2'] and '2v2' - or nil + local role = player.captain and 'Captain' or nil if player.dq then args.status = 'former' From b9c05c26e7c4135495b8e657d3342dbe979c8f5d Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 09:49:50 +0200 Subject: [PATCH 09/22] update the steps in the md --- TeamListConversion.md | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/TeamListConversion.md b/TeamListConversion.md index 5e0de8ba3b6..46cb1e59fba 100644 --- a/TeamListConversion.md +++ b/TeamListConversion.md @@ -10,20 +10,23 @@ - [x] Delete Template:TeamList/Team - [x] Convert all usages of pure TeamCard calls (usually with box stuff arround them) to use TeamList wrapper (TeamCard already uses TeamList under the hood) - [x] Clean up `Toggle group start`/`Toggle group end` usages in combi with TeamList -- [ ] wait for necessary features of TeamParticipants - - [ ] #6872 - - [ ] #7319 +- [ ] Write conversion wrapper (as dev of `TeamList` modules) + - [x] initial + - [ ] test & debug + - [ ] perf test +- [ ] wait for necessary features of TeamParticipants for using wrapper - [ ] check if the sc(2) specific TC "roles" (captain, 2v2) work in TeamParticipants, if not see how to make them work - [ ] check if combi of DNP & captain "role" work in TeamParticipants, if not see how to make it work - - [ ] check how player notes are handled and if this is doable ... -- [ ] Write a conversion wrapper (as dev of TeamList modules) -- [ ] Test conversion wrapper (incl perf test) - [ ] Inplace replace TeamList modules with the conversion wrapper -- [ ] Replace TeamCard and TeamList/Section usage with jsons (`subst:#json:`, TeamCard already does ecaxtly that when reaching this point, TeamList/Section only adds a single param) +- [ ] wait for necessary features of TeamParticipants + - [ ] #6872 + - [ ] #7319 +- [ ] start using TeamParticipants on new pages +- [ ] Replace `TeamCard` and `TeamList/Section` usage with jsons (`subst:#json:`, TeamCard already does ecaxtly that when reaching this point, `TeamList/Section` only adds a single param (`|type=section`)) - [ ] sc2 - [ ] sg - [ ] bw -- [ ] Delete Template:TeamCard on all 3 wikis & delete Template:TeamList/Section on commons +- [ ] Delete `Template:TeamCard` on all 3 wikis & delete `Template:TeamList/Section` on commons - [ ] sc2 - [ ] sg - [ ] bw @@ -32,7 +35,11 @@ - [ ] sc2 - [ ] sg - [ ] bw -- [ ] Archive/Delete the TeamList modules (i.e. the conversion wrappers) and Template:TeamList +- [ ] Archive/Delete the TeamList modules (i.e. the conversion wrappers) on commons and `Template:TeamList` on the 3 wikis + - [ ] commons + - [ ] sc2 + - [ ] sg + - [ ] bw ## Conversion wrapper - Basically mirror what TeamList modules do (import auto dnp etc pp) just without display and without storage From 1f5b44909ec8e834da9f7043b8b20853cd38e13f Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 09:53:41 +0200 Subject: [PATCH 10/22] fix todos & linter stuff --- lua/wikis/commons/TeamList/Starcraft.lua | 18 +++--------------- .../commons/TeamList/Starcraft/TeamCard.lua | 6 ++++-- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index 5129f34780f..15874e6be6a 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -3,14 +3,6 @@ -- page=Module:TeamList/Starcraft -- ---[[ - -todo: -- sections marked with todo (especially mapping ...) -- debug - -]] - local Arguments = require('Module:Arguments') local Array = require('Module:Array') local Class = require('Module:Class') @@ -54,15 +46,11 @@ function TeamListWrapper.TemplateTeamList(frame) local teamList = TeamList(args):read() - mw.logObject(teamList) -- todo: remove once mapping works - - local newArgs = teamList:map() -- todo + local newArgs = teamList:map() -- throw the class away to not clog up memory ---@diagnostic disable-next-line: cast-local-type teamList = nil - mw.logObject(newArgs) -- todo: remove once mapping works - if Logic.readBool(args.generate) then TeamListWrapper.generate(newArgs) end @@ -145,7 +133,7 @@ function TeamListWrapper.generateOuterConfig(args) return '|' .. param .. '=' .. value end) - local store = args.store == false and 'false' or 'false' -- todo: double check if includeonly or onlyinclude ... + local store = args.store == false and 'false' or 'false' table.insert(parts, '|store=' .. store) return table.concat(parts) @@ -278,7 +266,7 @@ function TeamList.mapPlayer(player) if player.dnp then args.played = 'false' end - local role = player.captain and 'Captain' or nil + args.role = player.captain and 'Captain' or nil if player.dq then args.status = 'former' diff --git a/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua index e8ccb51c9be..519e3a6b07c 100644 --- a/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua +++ b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua @@ -75,7 +75,9 @@ function TeamCard:readOpponent() local args = self.args local date = args.date or getContextualDateOrNow() local team = (args.team or 'tbd'):lower():gsub('_', ' ') - local opponent = Opponent.resolve(Opponent.readOpponentArgs{team, type = Opponent.team}, date) --[[@as StarcraftTeamCardOpponent]] + local opponent = Opponent.resolve( + Opponent.readOpponentArgs{team, type = Opponent.team}, date + ) --[[@as StarcraftTeamCardOpponent]] opponent.dq = Logic.readBool(args.dq) opponent.date = date @@ -86,7 +88,7 @@ function TeamCard:readOpponent() end)) if #opponent.players >= 35 then - mw.ext.TeamLiquidIntegration.add_category('TeamCards with 35 players') + mw.ext.TeamLiquidItegration.add_category('TeamCards with 35 players') elseif #opponent.players >= 25 then mw.ext.TeamLiquidIntegration.add_category('TeamCards with 25 players') elseif #opponent.players >= 20 then From 0c65bd087041f9e149a68517eec35389f9cf4cc0 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 09:56:24 +0200 Subject: [PATCH 11/22] kick the md (moved to PR summary) --- TeamListConversion.md | 49 ------------------------------------------- 1 file changed, 49 deletions(-) delete mode 100644 TeamListConversion.md diff --git a/TeamListConversion.md b/TeamListConversion.md deleted file mode 100644 index 46cb1e59fba..00000000000 --- a/TeamListConversion.md +++ /dev/null @@ -1,49 +0,0 @@ -# Conversion of Template:TeamList - -## Affected Wikis -- stormgate: 8 pages -- starcraft2: ~600 pages -- starcraft: ~2k pages - -## Steps -- [x] Convert all Template:TeamList/Team calls to Template:TeamCard calls -- [x] Delete Template:TeamList/Team -- [x] Convert all usages of pure TeamCard calls (usually with box stuff arround them) to use TeamList wrapper (TeamCard already uses TeamList under the hood) -- [x] Clean up `Toggle group start`/`Toggle group end` usages in combi with TeamList -- [ ] Write conversion wrapper (as dev of `TeamList` modules) - - [x] initial - - [ ] test & debug - - [ ] perf test -- [ ] wait for necessary features of TeamParticipants for using wrapper - - [ ] check if the sc(2) specific TC "roles" (captain, 2v2) work in TeamParticipants, if not see how to make them work - - [ ] check if combi of DNP & captain "role" work in TeamParticipants, if not see how to make it work -- [ ] Inplace replace TeamList modules with the conversion wrapper -- [ ] wait for necessary features of TeamParticipants - - [ ] #6872 - - [ ] #7319 -- [ ] start using TeamParticipants on new pages -- [ ] Replace `TeamCard` and `TeamList/Section` usage with jsons (`subst:#json:`, TeamCard already does ecaxtly that when reaching this point, `TeamList/Section` only adds a single param (`|type=section`)) - - [ ] sc2 - - [ ] sg - - [ ] bw -- [ ] Delete `Template:TeamCard` on all 3 wikis & delete `Template:TeamList/Section` on commons - - [ ] sc2 - - [ ] sg - - [ ] bw - - [ ] commons -- [ ] If there are no issues mentioned within X months after inplace conversion start a (subst) replace run to use the option of the conversion wrapper to generate the wiki code - - [ ] sc2 - - [ ] sg - - [ ] bw -- [ ] Archive/Delete the TeamList modules (i.e. the conversion wrappers) on commons and `Template:TeamList` on the 3 wikis - - [ ] commons - - [ ] sc2 - - [ ] sg - - [ ] bw - -## Conversion wrapper -- Basically mirror what TeamList modules do (import auto dnp etc pp) just without display and without storage -- Instead of display/storage convert the collected data to new params and call TeamParticipants with them - - If section stuff is used each section has to call TeamParticipants stuff and then wrap all the TeamParticipants calls into Tabs dynamic -- Add an **option** to generate wiki code instead of calling TeamParticipants -- Add a check that adds a cleanup category if it finds `'<%s*br%s*/?>'` in any of the inputs From 85916b356a749963a8dabd7d823b4992a60b1be7 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 09:57:39 +0200 Subject: [PATCH 12/22] shut up linter (i want to throw it away...) --- lua/wikis/commons/TeamList/Starcraft.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index 15874e6be6a..eebab772658 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -48,8 +48,10 @@ function TeamListWrapper.TemplateTeamList(frame) local newArgs = teamList:map() -- throw the class away to not clog up memory + -- luacheck: ignore ---@diagnostic disable-next-line: cast-local-type teamList = nil + -- luacheck: pop if Logic.readBool(args.generate) then TeamListWrapper.generate(newArgs) From d8473efdfbaf44d16411efbdfd89e7f87676696b Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 09:58:38 +0200 Subject: [PATCH 13/22] this seems better --- lua/wikis/commons/TeamList/Starcraft.lua | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index eebab772658..8b90bda5e78 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -44,14 +44,7 @@ function TeamListWrapper.TemplateTeamList(frame) end end - local teamList = TeamList(args):read() - - local newArgs = teamList:map() - -- throw the class away to not clog up memory - -- luacheck: ignore - ---@diagnostic disable-next-line: cast-local-type - teamList = nil - -- luacheck: pop + local newArgs = TeamList(args):read():map() if Logic.readBool(args.generate) then TeamListWrapper.generate(newArgs) From 2e4f75c5b44ce66371508ecd6f7edd81a555897d Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 10:02:55 +0200 Subject: [PATCH 14/22] deploy headers --- lua/wikis/commons/TeamList/Starcraft.lua | 4 +++- lua/wikis/commons/TeamList/Starcraft/TeamCard.lua | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index 8b90bda5e78..3d460851d8b 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -1,7 +1,9 @@ --- --- for @Liquipedia by @hjpalpha +-- @Liquipedia -- page=Module:TeamList/Starcraft -- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- local Arguments = require('Module:Arguments') local Array = require('Module:Array') diff --git a/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua index 519e3a6b07c..f18e7a578c2 100644 --- a/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua +++ b/lua/wikis/commons/TeamList/Starcraft/TeamCard.lua @@ -1,7 +1,9 @@ --- --- for @Liquipedia by @hjpalpha +-- @Liquipedia -- page=Module:TeamList/Starcraft/TeamCard -- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- local Lua = require('Module:Lua') From 87bff66d0efb87409785e4d5db70c7839985f2b2 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 11:01:59 +0200 Subject: [PATCH 15/22] never import with the new stuff as it fucks things up --- lua/wikis/commons/TeamList/Starcraft.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index 3d460851d8b..3f96be8fde4 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -233,6 +233,7 @@ function TeamList.mapEntry(entry) local opp = entry.opponent local notes = {opp.note} local args = { + import = 'false', --- disallow this shit as it just fucks up things ... players = Array.map(opp.players, function(player) table.insert(notes, player.note) return TeamList.mapPlayer(player) From 5a79afeb80c8e0f81c01ed19bbb89f7197bfbef2 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 11:26:57 +0200 Subject: [PATCH 16/22] a return is a good idea ... --- lua/wikis/commons/TeamList/Starcraft.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index 3f96be8fde4..bd0fd106317 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -223,7 +223,7 @@ function TeamList:map() Array.sortInPlaceBy(section.entries, function(entry) return entry.name:lower() end) end - Table.mergeInto(args, Array.map(section.entries, TeamList.mapEntry)) + return Table.mergeInto(args, Array.map(section.entries, TeamList.mapEntry)) end) end From ccd6aaca9a1eb82649a1a8248a70e31a7c996836 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 11:34:15 +0200 Subject: [PATCH 17/22] sc2 ingame role ... --- lua/wikis/starcraft2/InGameRoles.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 lua/wikis/starcraft2/InGameRoles.lua diff --git a/lua/wikis/starcraft2/InGameRoles.lua b/lua/wikis/starcraft2/InGameRoles.lua new file mode 100644 index 00000000000..cbe3ef6b41d --- /dev/null +++ b/lua/wikis/starcraft2/InGameRoles.lua @@ -0,0 +1,13 @@ +--- +-- @Liquipedia +-- page=Module:InGameRoles +-- +-- Please see https://github.com/Liquipedia/Lua-Modules to contribute +-- + +---@type table +local inGameRoles = { + ['captain'] = {category = 'Captain', display = 'Captain'}, +} + +return inGameRoles From a52ac74743e76cf6070625a0c7f781a189ae12c1 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 12 May 2026 11:39:20 +0200 Subject: [PATCH 18/22] stringify (so bools do not break the generator) --- lua/wikis/commons/TeamList/Starcraft.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index bd0fd106317..57f05d89cac 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -175,7 +175,7 @@ function TeamListWrapper.generatePlayer(args) local add = function(param) local value = args[param] if Logic.isEmpty(value) then return end - table.insert(parts, '|' .. param .. '=' .. value) + table.insert(parts, '|' .. param .. '=' .. tostring(value)) end local params = { @@ -210,7 +210,7 @@ function TeamList:map() local args = { title = section.title or config.title, - showplayerinfo = config.playerInfoButton, + showplayerinfo = config.playerInfoButton and 'true' or nil, date = config.resolveDate, store = not config.noStorage, } From c4a8c539f133946d800d9def637b248af90c6d99 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Fri, 15 May 2026 13:51:34 +0200 Subject: [PATCH 19/22] disable `enrichPlayerDates` --- lua/wikis/commons/TeamList/Starcraft.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index 57f05d89cac..f33ad6cd6a8 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -122,6 +122,7 @@ function TeamListWrapper.generateOuterConfig(args) local params = { 'showplayerinfo', 'date', + 'enrichPlayerDates', } local parts = Array.map(params, function(param) @@ -213,6 +214,7 @@ function TeamList:map() showplayerinfo = config.playerInfoButton and 'true' or nil, date = config.resolveDate, store = not config.noStorage, + enrichPlayerDates = 'false', } if args.title and config.showCountBySection then From 47613761184edbb5bce52b1c6d493684d858b8b7 Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Mon, 18 May 2026 12:41:18 +0200 Subject: [PATCH 20/22] Delete lua/wikis/starcraft2/InGameRoles.lua --- lua/wikis/starcraft2/InGameRoles.lua | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 lua/wikis/starcraft2/InGameRoles.lua diff --git a/lua/wikis/starcraft2/InGameRoles.lua b/lua/wikis/starcraft2/InGameRoles.lua deleted file mode 100644 index cbe3ef6b41d..00000000000 --- a/lua/wikis/starcraft2/InGameRoles.lua +++ /dev/null @@ -1,13 +0,0 @@ ---- --- @Liquipedia --- page=Module:InGameRoles --- --- Please see https://github.com/Liquipedia/Lua-Modules to contribute --- - ----@type table -local inGameRoles = { - ['captain'] = {category = 'Captain', display = 'Captain'}, -} - -return inGameRoles From 745c1f644e0420072fadd5d1e60dd4a67f673c82 Mon Sep 17 00:00:00 2001 From: hjpalpha Date: Tue, 19 May 2026 13:25:20 +0200 Subject: [PATCH 21/22] actually return the generated wiki code --- lua/wikis/commons/TeamList/Starcraft.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index f33ad6cd6a8..7587fea4a51 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -49,7 +49,7 @@ function TeamListWrapper.TemplateTeamList(frame) local newArgs = TeamList(args):read():map() if Logic.readBool(args.generate) then - TeamListWrapper.generate(newArgs) + return TeamListWrapper.generate(newArgs) end if Array.any(newArgs, function(section) From 9e5222a264631b81fc13e419a3fcfc587b5c1b68 Mon Sep 17 00:00:00 2001 From: hjpalpha <75081997+hjpalpha@users.noreply.github.com> Date: Thu, 21 May 2026 23:35:55 +0200 Subject: [PATCH 22/22] fix notes handling --- lua/wikis/commons/TeamList/Starcraft.lua | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lua/wikis/commons/TeamList/Starcraft.lua b/lua/wikis/commons/TeamList/Starcraft.lua index 7587fea4a51..9b7138a26c7 100644 --- a/lua/wikis/commons/TeamList/Starcraft.lua +++ b/lua/wikis/commons/TeamList/Starcraft.lua @@ -58,6 +58,12 @@ function TeamListWrapper.TemplateTeamList(frame) end) end) then mw.ext.TeamLiquidIntegration.add_category('TeamList with notes') + Array.forEach(newArgs, function(section) + Array.forEach(section, function(opp) + if Logic.isEmpty(opp.notes) then return end + opp.notes = Array.map(opp.notes, function(note) return {note} end) + end) + end) end if not newArgs[2] then @@ -154,9 +160,9 @@ function TeamListWrapper.generateOpponent(args) table.insert(parts, '\t\t}}') if Logic.isNotEmpty(args.notes) then - table.insert(parts, '\t\t|notes={{Json') + table.insert(parts, '\t\t|notes={{Notes') Array.forEach(args.notes, function(note) - table.insert(parts, '\t\t\t|' .. note) + table.insert(parts, '\t\t\t|{{Note|' .. note .. '}}') end) table.insert(parts, '\t\t}}') end