Modulo:Wikilib/lists
Vai alla navigazione
Vai alla ricerca
Questo modulo non ha ancora un manuale. Creane uno!
--[[
Modulo di utilità per creare elenchi. Sfrutta
l'object oriented programming per offrire delle
funzioni che fanno buona parte dei task più
comuni della creazione di liste, come creare
delle entry scorrendo un elenco di un modulo
dati e concatenare le entry così ottenute.
Aiuta anche nell'ordinamento delle entry,
definendo implicitamente delle interfacce che
rendono possibile l'automazione del sorting.
--]]
local l = {}
-- stylua: ignore start
local w = require('Modulo:Wikilib')
local txt = require('Modulo:Wikilib/strings')
local tab = require('Modulo:Wikilib/tables')
local form = require('Modulo:Wikilib/forms')
local oop = require('Modulo:Wikilib/oop')
local alts = require('Modulo:AltForms/data')
local pokes = mw.loadData('Modulo:Poké/data')
-- stylua: ignore end
--[[-----------------------------------------
Utility
--]]
-----------------------------------------
--[[
Replaces the label with the parameter when
all forms share the same box. If only one form
exists it deletes all the labels instead, unless
the third parameter is true.
The boxes are returned for composition convenience.
--]]
local allForms = function(boxes, label, empty)
if #boxes == 1 then
local box = boxes[1]
if box:labelFormsCount() == 1 then
if empty then
box:emptyLabel()
end
else
box:replaceLabel(label)
end
end
return boxes
end
--[[-----------------------------------------
Iterators
--]]
-----------------------------------------
--[[
Stateless iterator sui soli nomi dei Pokémon
nei moduli dati, ovvero le chiavi strings
che non iniziano con delle cifre
--]]
local nextPokeName = function(tab, key)
local nextKey, nextValue = key
repeat
nextKey, nextValue = next(tab, nextKey)
until not nextKey or not txt.parseInt(nextKey)
return nextKey, nextValue
end
-- local next_poke_name = nextPokeName
-- Stateless iterator da usare nei generic for loops
l.pokeNames = function(tab)
return nextPokeName, tab
end
l.poke_names = l.pokeNames
--[[-----------------------------------------
Ordinamento
--]]
-----------------------------------------
--[[
Funzione da passare a table.sort per ordinare le
entry in base alla forma alternativa: non viene
fatto nessun controllo sulla presenza delle stesse.
Sfrutta la seguente interfaccia:
- formsData: Dati delle forme alternative del
modulo AltForms/data del Pokémon a cui la
entry è relativa
- formAbbr: sigla della forma alternativa a cui
la entry si riferisce
--]]
l.sortForm = function(a, b)
--[[
A volte lua confronta un elemento con se stesso,
e se non si fa così non funziona più niente
Using rawequal because often entries overwrite the __eq metamethod
--]]
if rawequal(a, b) then
return false
end
return tab.search(a.formsData.gamesOrder, a.formAbbr)
< tab.search(b.formsData.gamesOrder, b.formAbbr)
end
l.sort_form = l.sortForm
--[[
Funzione da passare a table.sort per ordinare le
entry in base al numero di dex nazionale e forma
alternativa. Gli ndex non definiti vengono posti
dopo quelli definiti, e si può specificare un
criterio di ordinamento tra ndex inesistenti.
Sfrutta la seguente interfaccia:
- ndex: Numero di dex nazionale del Pokémon
cui la entry è relativa
- fallbackSort: property da utilizzare per
l'ordinamento delle entry senza ndex
- formsData: Dati delle forme alternative del
modulo AltForms/data del Pokémon a cui la
entry è relativa
- formAbbr: sigla della forma alternativa a cui
la entry si riferisce
--]]
l.sortNdex = function(a, b)
--[[
A volte lua confronta un elemento con se stesso,
e se non si fa così non funziona più niente
Using rawequal because often entries overwrite the __eq metamethod
--]]
if rawequal(a, b) then
return false
end
-- If at least one ndex is not defined
if not (a.ndex and b.ndex) then
-- The defined ndex gets sorted first
if a.ndex then
return true
elseif b.ndex then
return false
-- No ndex defined, using fallback
else
return a.fallbackSort < b.fallbackSort
end
end
if a.ndex ~= b.ndex then
return a.ndex < b.ndex
end
return l.sortForm(a, b)
end
l.sort_ndex = l.sortNdex
--[[-----------------------------------------
List-creating functions
--]]
-----------------------------------------
--[[
Creates a list where every entry is a row in an HTML table.
Arguments (names because they are many):
- source: table to scan to retrieve the data.
- makeEntry: constructor of the class representing an entry.
- entryArgs: optional, value to be passed to the entry constructor as last
argument. Defaults to nil.
- iterator: optional, the iterator used to traverse source. Default to
pairs.
- header: wikicode to be used as table-header.
- separator: optional, the separator to be used when concatenating entries.
It's both prefixed and appended newlines, Defaults to
|- style="height: 100%"
- footer: optional, the footer for the HTML table. A newline is prepended
to it, but not the separator. Defaults to '|}'.
The class representing the entries, needs to implement the following interface:
- constructor(): Takes as parameters an element of source, its key and
entryArgs when specified. Must return nil if the entry should not be
included in the list.
- __lt(): Used to sort the entries list by means of table.sort.
- __tostring(): Returns the wikicode representing the entry.
--]]
l.makeList = function(args)
args.footer = "\n" .. (args.footer or "|}")
-- "height: 100%" is just CSS making fun of us, can't really hurt anything
args.separator = table.concat({
"\n",
args.separator or '|- style="height: 100%;"',
"\n",
})
local makeEntry = function(sourceData, sourceKey)
return args.makeEntry(sourceData, sourceKey, args.entryArgs)
end
local entries = tab.mapToNum(args.source, makeEntry, args.iterator)
table.sort(entries)
table.insert(entries, 1, args.header)
return w.mapAndConcat(entries, args.separator, tostring) .. args.footer
end
--[[
Creates a list where every entry is a row in an HTML table, grouping entries by
a property. Entries grouped that holds the same data are merged, and the labels
of the entries are merged. If a whole group is collapsed in a single entry, the
label may become a custom text. If not specified otherwise, groups of one
element are printed without the label.
Arguments (names because they are many):
- source: table to scan to retrieve the data.
- makeEntry: constructor of the class representing an entry.
- entryArgs: optional, value to be passed to the entry constructor as last
argument. Defaults to nil.
- iterator: optional, the iterator used to traverse source. Default to
pairs.
- header: wikicode to be used as table-header.
- separator: optional, the separator to be used when concatenating entries.
It's both prefixed and appended newlines, Defaults to
|- style="height: 100%"
- footer: optional, the footer for the HTML table. A newline is prepended
to it, but not the separator. Defaults to '|}'.
- fullGroupLabel: optional, the label to use when a whole group generates a
single entry. Defaults to 'Tutte le forme'
- noEmptyLabel: flag, specifies if a group with one element should use the
label of the element instead of an empty one. Defaults to false
The class representing the entries must implement the following interface:
- constructor(): Takes as parameters an element of source, its key and
entryArgs when specified. Must return nil if the entry should not be
included in the list.
- __lt(): Used to sort the entries list by means of table.sort.
- __tostring(): Returns the wikicode representing the entry.
- __eq(): tells whether two boxes hold the same data, and should therefore
be merged.
- groupID(): Returns the identifier of the group this entry belongs to.
There's no need for the ID to be sortable.
- getLabel(): Returns this entry's label(s).
- addLabel(): Adds a form name to the current label set.
- labelFormsCount(): returns the number of labels currently in the label.
- replaceLabel(): Replaces the label as a whole with the passed one.
- emptyLabel(): Empties the label.
--]]
l.makeGroupedList = function(args)
args.footer = "\n" .. (args.footer or "|}")
args.fullGroupLabel = args.fullGroupLabel or "Tutte le forme"
-- "height: 100%" is just CSS making fun of us, can't really hurt anything
args.separator = table.concat({
"\n",
args.separator or '|- class="height-100"',
"\n",
})
local makeEntry = function(sourceData, sourceKey)
return args.makeEntry(sourceData, sourceKey, args.entryArgs)
end
local entries = tab.map(args.source, makeEntry, args.iterator)
local groups = tab.groupBy(entries, function(v)
return v:groupID()
end)
entries = tab.flatMapToNum(groups, function(group)
local groupEntries = {}
for _, entry in pairs(group) do
local index = tab.search(groupEntries, entry)
if index then
groupEntries[index]:addLabel(entry:getLabel())
else
table.insert(groupEntries, entry)
end
end
-- No need for sorting here because all entries are sorted after the
-- flatten
return allForms(
groupEntries,
args.fullGroupLabel,
not args.noEmptyLabel
)
end)
table.sort(entries)
table.insert(entries, 1, args.header)
return w.mapAndConcat(entries, args.separator, tostring) .. args.footer
end
--[[
Creates a list where every entry is a row in an HTML table, grouping entries by
a property and merging together all the entries in a group only if they are all
equal, with a specific label. If not otherwise specified, groups of one
element are printed without the label.
Arguments (names because they are many):
- source: table to scan to retrieve the data.
- makeEntry: constructor of the class representing an entry.
- entryArgs: optional, value to be passed to the entry constructor as last
argument. Defaults to nil.
- iterator: optional, the iterator used to traverse source. Default to
pairs.
- header: wikicode to be used as table-header.
- separator: optional, the separator to be used when concatenating entries.
It's both prefixed and appended newlines, Defaults to
|- style="height: 100%"
- footer: optional, the footer for the HTML table. A newline is prepended
to it, but not the separator. Defaults to '|}'.
- fullGroupLabel: optional, the label to use when a whole group generates a
single entry. Defaults to 'Tutte le forme'
- noEmptyLabel: flag, specifies if a group with one element should use the
label of the element instead of an empty one. Defaults to false
The class representing the entries must implement the following interface:
- constructor(): Takes as parameters an element of source, its key and
entryArgs when specified. Must return nil if the entry should not be
included in the list.
- __lt(): Used to sort the entries list by means of table.sort.
- __tostring(): Returns the wikicode representing the entry.
- __eq(): tells whether two boxes hold the same data, and should therefore
be merged.
- groupID(): Returns the identifier of the group this entry belongs to.
There's no need for the ID to be sortable.
- replaceLabel(): Replaces the label as a whole with the passed one.
- emptyLabel(): Empties the label.
--]]
l.makeCollapsedList = function(args)
args.footer = "\n" .. (args.footer or "|}")
args.fullGroupLabel = args.fullGroupLabel or "Tutte le forme"
-- "height: 100%" is just CSS making fun of us, can't really hurt anything
args.separator = table.concat({
"\n",
args.separator or '|- style="height: 100%;"',
"\n",
})
local makeEntry = function(sourceData, sourceKey)
return args.makeEntry(sourceData, sourceKey, args.entryArgs)
end
local entries = tab.map(args.source, makeEntry, args.iterator)
local groups = tab.groupBy(entries, function(v)
return v:groupID()
end)
entries = tab.flatMapToNum(groups, function(group)
if #group == 1 then
if not args.noEmptyLabel then
group[1]:emptyLabel()
end
return group
end
local equalFirst = function(val)
return group[1] == val
end
if not tab.all(group, equalFirst) then
return group
end
-- Groups all entries printing the first value
table.sort(group)
group[1]:replaceLabel(args.fullGroupLabel)
return { group[1] }
end)
table.sort(entries)
table.insert(entries, 1, args.header)
return w.mapAndConcat(entries, args.separator, tostring) .. args.footer
end
--[[
Creates and prints a list of boxes for all
forms of a given Pokémon: boxes holding the
same data are merged, and the name of the
form is added to the label of the box itself.
If the Pokémon has no alternative forms, only
the box for the base one is printed. When all
forms share the same box, the label is replaced
with 'Tutte le forme'. If only one box exists,
and it has only one form name in the label,
the label itseld its emptied.
Parameters are named:
- name: name or ndex of the Pokémon whose
noxes are created. Must be the base form.
- makeBox: Constructor of the classes
representing a box.
- boxArgs: extra parameter which will be passed
to the box constructor after the regular ones.
- printBoxes: prints the boxes to a string.
- altData: Alternative forms data.
Defaults to AltForms-data module.
The class representing a box must implement
the following interface:
- constructor(): Takes as parameters the
name of the Pokémon, in the format
name + abbreviation, as found in data
modules, and the form extended name,
if any.
- __eq(): tells whether two boxes hold the
same data, and should therefore be merged.
- addLabel(): adds a form name to the current
label set.
- labelFormsCount(): returns the number of
form names currently in the label.
- replaceLabel(): replaces the label as a whole
with the passed one.
- emptyLabel(): deletes all the form names from
the label.
--]]
l.makeFormsLabelledBoxes = function(args)
local makeBox = function(sourceData, sourceKey)
return args.makeBox(sourceData, sourceKey, args.boxArgs)
end
local altData = args.altData or alts[args.name]
if not altData then
return args.printBoxes({ makeBox(args.name) })
end
local boxes = {}
--[[
Scorrendo gamesOrder i boxes saranno già ordinati
senza bisogno di sorting successivo.
Non si può usare table.map perché ciò porterebbe
ad avere buchi negli indici di boxes, cosa
difficilmente gestibile
--]]
for _, abbr in ipairs(altData.gamesOrder) do
local formName = altData.names[abbr]
-- Replaces empty base form name with Pokémon's name
formName = abbr == "base"
and formName == ""
and pokes[args.name]
and pokes[args.name].name
or formName
--[[
If ndex is passed, base form should stay a number (thus can't be
concatenated to the empty string) and other forms should have four
digits followed by form abbr.
--]]
local name = abbr == "base" and args.name
or (
type(args.name) == "number"
and txt.fourFigures(args.name) .. abbr
or args.name .. abbr
)
local formBox = makeBox(name, formName)
local index = tab.search(boxes, formBox)
if index then
boxes[index]:addLabel(formName)
else
table.insert(boxes, formBox)
end
end
return args.printBoxes(allForms(boxes, "Tutte le forme"))
end
--[[-----------------------------------------
Classi di utilità
--]]
-----------------------------------------
--[[
Classe base per una entry relativa ad un Pokémon
che implementa l'interfaccia per l'ordinamento
tramite numero di dex nazionale, con il nome
come criterio in caso di ndex mancante.
--]]
l.PokeSortableEntry = oop.makeClass()
l.PokeSortableEntry.new = function(name, ndex)
local this = setmetatable({ ndex = ndex, name = name }, l.PokeSortableEntry)
if not this.ndex then
this.fallbackSort = this.name
end
local baseName, abbr = form.getNameAbbr(name)
this.formsData = alts[baseName]
if this.formsData then
this.formAbbr = abbr
end
return this
end
l.PokeSortableEntry.__lt = l.sortNdex
--[[
Base class of an object having a label.
Implements methods addLabel(), hasLabel()
and replaceLabel() required by makeFormsLabelledBoxes.
--]]
l.Labelled = oop.makeClass()
--[[
Sostituisce la label con quella passata, sia
essa una sola label o una table di labels.
--]]
l.Labelled.replaceLabel = function(this, label)
-- Table vuota anche in caso label non sia dato
this.labels = type(label) == "table" and label or { label }
end
l.Labelled.new = function(label)
local this = setmetatable({}, l.Labelled)
this:replaceLabel(label)
return this
end
-- Returns the labels
l.Labelled.getLabel = function(this)
return this.labels
end
-- Aggiunge una label o una table di labels
l.Labelled.addLabel = function(this, label)
if type(label) == "table" then
this.labels = tab.merge(this.labels, label)
else
table.insert(this.labels, label)
end
end
-- Deletes all the form names from the label
l.Labelled.emptyLabel = function(this)
this:replaceLabel({})
end
-- Returns the number of labels
l.Labelled.labelFormsCount = function(this)
return #this.labels
end
-- Ritorna true se la label è settata
l.Labelled.hasLabel = function(this)
return this:labelFormsCount() > 0
end
--[[
Base class of a Pokémon entry for a labelled list. Inherits from Labelled and
add the sort, like PokeSortableEntry, and the grouping by ndex. The sort is by
ndex first, using the name if it's missing. With the same ndex, entries are
sorted by form order (according to gamesOrder). The form abbr in case of
multiple labels is the least.
Note that the labels are the abbr, NOT the formNames. The duty to convert abbr
to extended name is left to the printing function.
--]]
l.PokeLabelledEntry = oop.makeClass(l.Labelled)
l.PokeLabelledEntry.new = function(name, ndex)
local baseName, abbr = form.getNameAbbr(name)
local this =
setmetatable(l.PokeLabelledEntry.super.new(abbr), l.PokeLabelledEntry)
this.ndex = ndex
this.name = name
if not this.ndex then
this.fallbackSort = this.name
end
this.formsData = alts[baseName]
if this.formsData then
this.formAbbr = abbr
end
return this
end
l.PokeLabelledEntry.__lt = l.sortNdex
l.PokeLabelledEntry.groupID = function(this)
return this.ndex or this.name:lower()
end
--[[
Should be reimplemented to take into account the change in this.formAbbr
]]
l.PokeLabelledEntry.replaceLabel = function(this, label)
l.PokeLabelledEntry.super.replaceLabel(this, label)
if type(label) == "table" then
-- Takes care also of the empty label case
this.formAbbr = label[1]
for _, v in ipairs(label) do
if form.abbrLT(this.formsData, v, this.formAbbr) then
this.formAbbr = v
end
end
else
-- This can happen also because of label change to 'Tutte le forme',
-- thus this check
if this.formsData.names[label] then
this.formAbbr = label
else
this.formAbbr = "base"
end
end
end
l.PokeLabelledEntry.addLabel = function(this, label)
if type(label) ~= "table" then
label = { label }
end
for _, v in ipairs(label) do
table.insert(this.labels, v)
if form.abbrLT(this.formsData, v, this.formAbbr) then
this.formAbbr = v
end
end
end
return l