Minecraft Wiki
Advertisement
[créer | historique | purger]Documentation
Ce module n'a pas de documentation. Si vous savez comment l'utiliser, merci de la créer.
local input = {}
local program = {}
local filter = {}
local parser = {}
local builder = {}

local dataModule = 'Module:BlockstateList'

-- Comparateur pour les blocs. Compare le bloc 'element1' à 'element2' si celui-ci ne possède pas de '|', ou au deux parties séparées par '|' si celui-ci en possède un
--[[local comparatorBlock = function(element1, element2) 
	element2 = mw.ustring.lower(element2)
	local splitted = mw.text.split(mw.ustring.lower(element1), '|', true)
	return mw.text.trim(splitted[1]) == element2 or (splitted[2] and mw.text.trim(splitted[2]) == element2) or (splitted[3] and mw.text.trim(splitted[3]))
end

local comparatorBlock = function(element1, element2) 
	element2 = mw.ustring.lower(element2)
	local splitted = mw.text.split(mw.ustring.lower(element1), '|', true)
	local equalsFirst = mw.text.trim(splitted[1]) == mw.text.trim(mw.ustring.lower(element2))
	local equalsSecond = splitted[2] and mw.text.trim(splitted[2]) == mw.ustring.lower(mw.text.trim(element2))
	local equalsThird = splitted[3] and mw.text.trim(splitted[3]) == mw.ustring.lower(mw.text.trim(element2))
	mw.addWarning(element2)
	return equalsFirt or equalsSecond or equalsThird
end--]]

local comparatorBlock = function(element1, element2) 
	element1 = mw.ustring.lower(element1)
	local splitted = mw.text.split(mw.ustring.lower(element2), '|', true)
	local equalsFirst = mw.text.trim(splitted[1]) == mw.text.trim(mw.ustring.lower(element1))
	local equalsSecond = splitted[2] and mw.text.trim(splitted[2]) == mw.ustring.lower(mw.text.trim(element1))
	local equalsThird = splitted[3] and mw.text.trim(splitted[3]) == mw.ustring.lower(mw.text.trim(element1))
	mw.addWarning(element2)
	return equalsFirt or equalsSecond or equalsThird
end

-- Comparateur pour les blockstates. Vérifie la simple égalité en ignorant la casse et en ayant trim les entrées
local comparatorBlockstate = function (element1, element2)
	return mw.text.trim(mw.ustring.lower(element1)) == mw.text.trim(mw.ustring.lower(element2))
end

function input.main(frame)

    -- Si le module est appelé par un template, on récupère les paramètres passés au template
	if mw.getCurrentFrame():getParent() then 
		frame = mw.getCurrentFrame():getParent()
	end
	
	success, msg = program.run(frame.args)
	if not success then
		mw.addWarning('[Erreur] ' .. msg)
	else
		--mw.addWarning('<nowiki>' .. msg .. '</nowiki>')
		return frame:preprocess(msg)
	end
end

--[[
		Lance le filtrage puis la construction du tableau sur les données
	@param args un dictionnaire d'arguments passé au programme
	@return1 vrai si l'exécution du programme s'est faite sans erreur, faux sinon
	@return2 le tableau au format wikicode
]]
function program.run(args)
	-- Récupération des données
	local data = require(dataModule)
	
	local success, result = program.parseArguments(args)
	
	if success then
		success, data = filter.data(data, result.blockstates, result.blocks)
		if success then
			return true, program.buildTable(data, result.anchor, result.order, result.width)
		else
			return false, data
		end
	else
		return false, result
	end
end


--[[
	@param data les données des blockstates
	@param anchor vrai si une ancre doit être affichée sur le nom du blockstate, faux sinon
	@param order l'ordre d'affichage des colonnes
	@return le tableau complet sous le format wikicode
]]
function program.buildTable(data, anchor, order, width)

	return builder.header(order, width) .. builder.lines(data, anchor, order) .. '\n|}\n{{-}}'

end


--[[
	@param args un dictionnaire d'arguments passé au programme
	@return1 vrai si chaque élément a réussi à être parsé, faux sinon
	@return2 si return1 vrai, retourne un dictionnaire avec les différentes valeurs parsées, sinon, retourne le message d'erreur
]]
function program.parseArguments(args) 

	local error = false
	local msgError = ''
	
	-- Parsing du paramètre 'block'
	local blocks = parser.multiParam(args.block)
	
	-- Parsing du paramètre 'blockstate'
	local blockstates = parser.multiParam(args.blockstate)
	
	-- Parsing du paramètre 'anchor'
	local anchor = false
	error, msgError = parser.boolean(args.anchor or 'false')
	if not error then
		anchor = msgError
	end
	
	-- Parsing du paramètre width
	local width = nil
	if not error and args.width then
		error, msgError = parser.int(args.width)
		if not error then
			width = msgError
		end
	end
	
	-- Parsing du paramètre 'order'	
	if not error and args.order then
		error, msgError = parser.order(args.order)
	end

	if error then
		return false, msgError
	else
		return true, { blocks = blocks, blockstates = blockstates, anchor = anchor, order = args.order or '1234', width = width }
	end
		
end

----- FONCTIONS DE CONSTRUCTION DE LA TABLE -----


--[[
	@param order l'ordre d'affichage des colonnes
	@return le header sous le format wikicode en prenant compte de 'order'
]]
function builder.header(order, width)
	
	local widthText = ''
	
	-- Gestion du paramètre width
	if width then
		widthText = ' style="width:' .. width .. '%"'
	end
	
	local header = '{|align="left" class="wikitable sortable"' .. widthText .. '\n'

	for c in order:gmatch("[1234]") do
		header = header .. builder.getColumnDeclaration(tonumber(c))
	end
	
	return header

end

--[[
	@param data les données des blockstates
	@param anchor vrai si une ancre doit être affichée sur le nom du blockstate, faux sinon
	@param order l'ordre d'affichage des colonnes
	@return les lignes du blockstate au format wikicode
]]
function builder.lines(data, anchor, order)

	local lines = ''
	local keyTable = {}
	
	-- Trie en fonction des clés
	for state, _ in pairs(data) do
		table.insert(keyTable, state)
	end
	
	table.sort(keyTable)

	for _, state in ipairs(keyTable) do
		lines = lines .. '|-\n' .. builder.line(state, data[state], anchor, order)
	end
	return lines
	
end

--[[
	@param state le nom du blockstate
	@param value la valeur du blockstate dans les données
	@param anchor vrai si une ancre doit être affichée sur le nom du blockstate, faux sinon
	@param order l'ordre d'affichage des colonnes
	@return la ligne du blockstate au format wikicode
]]
function builder.line(state, value, anchor, order)

	local line = ''

	for column in order:gmatch("[1234]") do
		
		if column == '1' then
			line = line .. '| rowspan="' .. #(value.entries) .. '" | ' .. builder.nameLine(state, anchor)
		elseif column == '2' then
			line = line .. '| rowspan="' .. #(value.entries) .. '" | ' .. value.description
		elseif column == '3' then
			line = line .. '| ' .. builder.valueLine(value.entries[1].values)
		elseif column == '4' then
			line = line .. '| ' .. builder.blockLine(value.entries[1].blocks)
		end
		line = line .. '\n'
	
	end
	
	if order:find('[34]') then
		table.remove(value.entries, 1)
	end
	
	if #(value.entries) > 0 then
		line = line .. builder.entryLine(value.entries, mw.ustring.gsub(order, '[^34]', ''))
	end
	
	return line
end


--[[
	@param column un index de colonne parmi {1, 2, 3, 4}
	@return l'en-tête de colonne du numéro de colonne spécifié en paramètre
]]
function builder.getColumnDeclaration(column)
	return ({
		'! colspan="1" style="text-align: center;" | Nom de l\'états\n',
		'! colspan="1" style="text-align: center;" | Description\n',
		'! colspan="1" style="text-align: center;" | Valeur de l\'état\n',
		'! colspan="1" style="text-align: center;" | Blocs\n'
	})[column]
end


--[[
	@param string le nom du blockstate
	@param anchor vrai si les noms des blockstates doivent être une ancre, faux sinon
	@return la case 'nom' formatée
]]
function builder.nameLine(name, anchor)
	local line = ''
	-- Prise en compte de l'ancre
	if anchor then
		line = line .. '{{anchor|' .. name .. '}} '
	end
	line = line .. '{{code|' .. name .. '}}'
	return line
end

--[[
	@param data une liste d'entrées à afficher dans les cellules 'valeur' et 'bloc'
	@param order l'ordre d'affichage des colonnes
	@return la partie de 'bloc' et 'valeur' de la ligne si celle-ci possède plusieurs entrées, au format wikicode
]]
function builder.entryLine(data, order)
	local lines = ''
	
	-- Parcours de toutes les entrées
	for _, entry in ipairs(data) do
		lines = lines .. '\n|-\n'
		for column in order:gmatch("[34]") do
			
			if column == '3' then
				lines = lines .. '| ' .. builder.valueLine(entry.values)
			elseif column == '4' then
				lines = lines .. '| ' .. builder.blockLine(entry.blocks)
			end
			lines = lines .. '\n'
		end
	end
	return lines
end

--[[
	@param data une liste de valeurs à afficher dans la cellule
	@return la cellule du tableau sous le format wikicode
]]	
function builder.valueLine(data)
	local cell = ''
	for i, value in ipairs(data) do
		-- Gestion des valeurs de la forme 'MIN - MAX'
		if(type(value) == 'table') then
			cell = cell .. '{{code|' .. value.min .. '}} - {{code|' .. value.max .. '}}'
		else
			cell = cell .. '{{code|' .. value .. '}}'
			-- Séparation des différentes valeurs par un retour à la ligne (sans en mettre après le dernier élément)
			if i < #data then
				cell = cell .. ' <br/> '
			end
		end
	end
	return cell
end
	
--[[
	@param data une liste de blocs à afficher dans la cellule
	@return la cellule du tableau sous le format wikicode
]]	
function builder.blockLine(data)
	local cell = ''
	for i, block in ipairs(data) do
		-- Gestion des blocs si pas cachées
		if block:find('|', 0, true) then
			cell = cell .. '{{LienBloc|id=' .. block .. '}}'
		else
			cell = cell .. '{{LienBloc|' .. block:gsub("^%l", string.upper) .. '}}'
		end
		-- Séparation des différentes valeurs par un retour à la ligne (sans en mettre après le dernier élément)
		if i < #data then
			cell = cell .. ' <br/> '
		end
	end
	return cell
end



----- FONCTIONS DE FILTRE -----


--[[ 
	@param data un tableau de donnée
	@param blockstates une séquence de blockstates à récupérer dans 'data'
	@param blocks une séquence de blocs à récupérer dans 'data' filtré
	@return 'data', filtré en fonction des deux contraintes 'blockstates' et blocks'
		le retour contiendra donc tous les blockstates de 'blockstates' demandés si ils répondent à la contrainte 'blocks'
]]
function filter.data(data, blockstates, blocks)

	local stateConstraint = #blockstates ~= 0

	-- Parcours de tous les blockstates
	for k, v in pairs(data) do
		-- Suppression du blockstate courant si celui-ci n'est pas spécifié dans la containte 'blockstates' (non nulle)
		if stateConstraint and not table.contains(blockstates, k, comparatorBlockstate) then
			data[k] = nil
		-- Suppression du blockstate courant si aucun bloc lui appartenant ne répond à la containte 'blocks' (non nulle)
		elseif #blocks ~= 0 and not filter.blocks(v, blocks) then
			data[k] = nil
		end
		if stateConstraint and table.contains(blockstates, k, comparatorBlockstate) then
			table.remove(blockstates, table.indexOf(blockstates, k, comparatorBlockstate))
		end
	end
	-- Vérification de la validation des contraintes pour les retours
	if stateConstraint and #blockstates ~= 0 then
		return false, table.concat(blockstates, ", ") .. ' n\'existe(nt) pas. Vous pouvez compléter la liste en cliquant [[' .. dataModule .. '|ici]].'
	elseif #blocks ~= 0 and table.getMapSize(data) == 0 then
		return false, 'Aucun état de bloc ne possède l\'un des blocs demandés. Vous pouvez compléter la liste en cliquant [[' .. dataModule .. '|ici]].'
	else 
		return true, data
	end
	
end

--[[
		Filtre state pour ne garder que les entrées qui correspondent à la contrainte si cell-ci existe
	@param state les attributs d'un blockstate
	@param blocks les contraintes de blocs
	@return vrai si le blockstate possède au moins un bloc de la contrainte dans le cas où celle-ci existe, faux sinon
]]
function filter.blocks(state, blocks) 
	local keep = #blocks == 0
	local toRemove = {}
	-- Parcours de toutes les entrées de state
	for k, v in ipairs(state.entries) do
		local foundBlock = false
		-- Parcours de tous les blocs de l'entrée courante à la recherche d'un qui pourrait répondre à la contrainte
		for _, block in ipairs(v.blocks) do
			foundBlock = foundBlock or table.contains(blocks, block, comparatorBlock)
			keep = keep or foundBlock
		end
		-- Suppression de l'entrée si elle ne correspond pas à la contrainte
		if not foundBlock and #blocks ~= 0 then
			table.insert(toRemove, k)
		end
	end
	
    local shift = 0
	for _, v in ipairs(toRemove) do
		table.remove(state.entries, v - shift)
        shift = shift + 1
	end
	
	return keep
end


----- FONCTIONS DE PARSING -----



--[[
	@param v
	@return1 faux si 'v' a bien réussi à être converti en boolean, c'est-à-dire si 'v' est un élément de l'ensemble {'true', 'false', 'vrai', 'faux'}
	@return2 la valeur parsée si return1 vrai, un message d'erreur sinon
]]
function parser.boolean(v)
	if (type(v) == 'string') and (v == 'true' or v == 'false' or v == 'vrai' or v == 'faux') then
		return false, v == 'true' or v == 'vrai'
	else
		return true, "'" .. tostring(v) .. "' n'est pas un booléen valide."
	end
end

--[[
	@param v
	@return1 faux si 'v' a bien réussi à être converti en entier compris entre 0 et 100 inclu
	@return2 la valeur parsée si return1 vrai, un message d'erreur sinon
]]
function parser.int(v)
	local matches = tonumber((type(v) == 'string') and string.match(v, '^%d+$'))
	if matches and 0 <= matches and matches <= 100 then
		return false, matches
	else
		return true, "'" .. tostring(v) .. "' n'est pas un pourcentage valide (0-100)."
	end
end

--[[
	@param param une valeur d'un paramètre sous forme de string pouvant énumérer plusieurs valeurs ou non
	@return1 une séquence possédant chaque valeur de 'param'
]]
function parser.multiParam(param)
	return ((param == '' or param == nil) and '') or mw.text.split(mw.text.trim(param or ''), ' *, *')
end


--[[
	@param order un string
	@return1 vrai si le string est un sous-ensemble de {1, 2, 3, 4}, faux sinon
	@return2 le message d'erreur si return1 est faux, chaîne vide sinon
]]
function parser.order(order)
	
	local record = {true, true, true, true}
	
	if order and #order <= 4 and #order > 0 then
		for c in order:gmatch("[^%c]") do
			if c:match("[1234]") then
				if record[tonumber(c)] then
					record[tonumber(c)] = false
				else
					-- Renvoie d'une erreur dans le cas où l'une des colonnes est demandée à être cachée plusieurs fois
					return true, "'" .. c .. "' dans '" .. order .. "' ne peut pas être présent plusieurs fois."
				end
			else
				-- Renvoie d'une erreur dans le cas où l'un des caractères n'est pas un numéro de colonne valide
				return true, "'" .. c .. "' dans '" .. order .. "' n'est pas un numéro de colonne valide."
			end	
		end
	elseif order and (#order > 4 or #order == 0) then
		-- Renvoie d'une erreur dans le cas où il y a plus ou moins de caractères que demandé
		return true, "'" .. order .. "' doit posséder de 1 à 4 caractères uniques parmi [1234]."
	end
	return false, ''
	
end


--------------------------------


--[[
	@param array une séquence d'élément
	@param element un élément dont la présence doit être vérifiée dans 'array'
	@param comparator une fonction comparatrice
	@return vrai si un élément de 'array' est égal à 'element' selon 'comparator', faux sinon
]]
function table.contains(array, element, comparator) 
	for _, v in ipairs(array) do
		if comparator(v, element) then
			return true
		end
	end
	return false
end

--[[
	@param array une séquence d'élément
	@param element un élément dont la présence doit être vérifiée dans 'array'
	@param comparator une fonction comparatrice
	@return vrai si 'element' est dans 'array' selon l'égalité définie par 'comparator'
]]
function table.indexOf(array, element, comparator)
	for k, v in ipairs(array) do
		if comparator(v, element) then
			return k
		end
	end
end

function table.getMapSize(map)
	local counter = 0
	for _, _ in pairs(map) do
		counter = counter + 1
	end
	return counter
end


return input
Advertisement