Módulo:Testes/Gkiyoshinishimoto/Hatnote list

Origem: Wikipédia, a enciclopédia livre.
--------------------------------------------------------------------------------
--                Módulo:Testes/Gkiyoshinishimoto/Hatnote list                --
--                                                                            --
-- Este módulo produz e formata listas para uso em notas de cabeçalho         --
-- ('hatnotes'). Em particular, ele implementa a lista "for-see", ou seja,    --
-- listas de declarações "Para X, consulte Y", conforme usado em              --
-- {{Teste/Gkiyoshinishimoto/About}}, {{Teste/Gkiyoshinishimoto/Redirect}} e  --
-- suas variantes. Também são introduzidas as auxiliares "andList" e "orList" --
-- para formatar listas com essas conjunções ("e"/"ou").                      --
--------------------------------------------------------------------------------

local mArguments -- inicializa lentamente
local mFormatLink = require('Módulo:Testes/Gkiyoshinishimoto/Format link')
local mHatnote = require('Módulo:Testes/Gkiyoshinishimoto/Hatnote')
local libraryUtil = require('libraryUtil')
local checkType = libraryUtil.checkType
local p = {}

--------------------------------------------------------------------------------
-- Funções auxiliares de sequenciamento ("'string'ificação") de lista
--
-- Essas funções são usadas para transformar listas e sequências ('strings'), 
-- geralmente listas de páginas dentro da parte "Y" de "Para X, ver Y" de 
-- itens para ver.
--------------------------------------------------------------------------------

-- tabela de opções padrão usada nas funções de sequenciamento 
-- ("'string'ificação") de listas
local stringifyListDefaultOptions = {
	conjunction = "e",
	separator = ",",
	altSeparator = ";",
	space = " ",
	formatted = false
}

-- Pesquisa somente texto a ser mostrado
local function searchDisp(haystack, needle)
	return string.find(
		string.sub(haystack, (string.find(haystack, '|') or 0) + 1), needle
	)
end

-- Sequencia ("'string'ifica") uma lista genericamente; provavelmente 
-- não deve ser usada diretamente
local function stringifyList(list, options)
	-- Verificações de tipo, padrões e um atalho
	checkType("stringifyList", 1, list, "table")
	if #list == 0 then return nil end
	checkType("stringifyList", 2, options, "table", true)
	options = options or {}
	for k, v in pairs(stringifyListDefaultOptions) do
		if options[k] == nil then options[k] = v end
	end
	local s = options.space
	-- Formata a lista se solicitado
	if options.formatted then
		list = mFormatLink.formatPages(
			{categorizeMissing = mHatnote.missingTargetCat}, list
		)
	end
	-- Define o separador; se algum item contiver, usa o separador alternativo
	local separator = options.separator
	for k, v in pairs(list) do
		if searchDisp(v, separator) then
			separator = options.altSeparator
			break
		end
	end
	-- Define a conjunção, aplica a vírgula de Oxford e força uma vírgula se #1 tiver "§"
	local conjunction = s .. options.conjunction .. s
	if #list == 2 and searchDisp(list[1], "§") or #list > 2 then
		conjunction = separator .. conjunction
	end
	-- Retorna a sequência ('string') formatada
	return mw.text.listToText(list, separator .. s, conjunction)
end

-- Função "'DRY'"
function p.conjList (conj, list, fmt)
	return stringifyList(list, {conjunction = conj, formatted = fmt})
end

-- Sequencia ("'string'ifica") listas com "e" ou "ou"
function p.andList (...) return p.conjList("e", ...) end
function p.orList (...) return p.conjList("ou", ...) end

--------------------------------------------------------------------------------
-- Para ver
--
-- Faz uma lista "Para X, ver [[Y]]."  a partir de parâmetros brutos. Destinada 
-- às predefinições {{Teste/Gkiyoshinishimoto/About}} e 
-- {{Teste/Gkiyoshinishimoto/Redirect}} e suas variantes.
--------------------------------------------------------------------------------

-- tabela de opções padrão usada na família de funções "forSee"
local forSeeDefaultOptions = {
	andKeyword = 'e',
	title = mw.title.getCurrentTitle().text,
	otherText = 'outros usos',
	forSeeForm = 'Para %s, ver %s.',
}

-- Recolhe a pontuação duplicada no final da sequência ('string'), ignorando 
	-- itálico e ligações ('links')
local function punctuationCollapse (text)
 	return text:match("[.?!]('?)%1(%]?)%2%.$") and text:sub(1, -2) or text
end

-- Estrutura argumentos em uma tabela para sequenciamento ("'string'ificação")
--  e opções
function p.forSeeArgsToTable (args, from, options)
	-- Verificações de tipo e padrões
	checkType("forSeeArgsToTable", 1, args, 'table')
	checkType("forSeeArgsToTable", 2, from, 'number', true)
	from = from or 1
	checkType("forSeeArgsToTable", 3, options, 'table', true)
	options = options or {}
	for k, v in pairs(forSeeDefaultOptions) do
		if options[k] == nil then options[k] = v end
	end
	-- de "maxArg" é obtido manualmente porque "getArgs()" e "table.maxn" 
	-- não são amigos
	local maxArg = 0
	for k, v in pairs(args) do
		if type(k) == 'number' and k > maxArg then maxArg = k end
	end
	-- Estrutura os dados a partir da lista de parâmetros:
	-- * "forTable" é a tabela "'wrapper'", com linhas de "forRow"
	-- As linhas são tabelas de uma sequência ('string') de "usos" e uma tabela
	-- de "páginas" de sequências ('strings') de nomes de páginas
	-- * Os espaços em branco são deixados em branco para padronizar em outro lugar, 
	-- mas podem terminar a lista
	local forTable = {}
	local i = from
	local terminated = false
	-- Se houver texto extra e nenhum argumento for fornecido, fornece o valor 
	-- nulo ('nil') para não produzir o padrão de "Para outros usos, ver
	-- algo (desambiguação)"
	if options.extratext and i > maxArg then return nil end
	-- Retorno ('loop') para geração de linhas
	repeat
		-- Nova linha vazia
		local forRow = {}
		-- No uso em branco, assume que a lista terminou e quebra no final deste 
		-- retorno ('loop')
		forRow.use = args[i]
		if not args[i] then terminated = true end
		-- Nova lista vazia de páginas
		forRow.pages = {}
		-- Insere o primeiro item de páginas se presente
		table.insert(forRow.pages, args[i + 1])
		-- Se o parâmetro após o próximo for "e", faz um retorno ('loop') interno 
		-- para coletar parâmetros
		-- até que os "e"s acabem. Os espaços em branco são ignorados: 
		-- "1|e||e|3" → {1, 3}
		while args[i + 2] == options.andKeyword do
			if args[i + 3] then
				table.insert(forRow.pages, args[i + 3])
			end
			-- Incrementa para o próximo "e"
			i = i + 2
		end
		-- Incrementa para o próximo uso
		i = i + 2
		-- Acrescenta a linha
		table.insert(forTable, forRow)
	until terminated or i > maxArg

	return forTable
end

-- Sequencia ("'string'ifica") uma tabela conforme formatada por "forSeeArgsToTable"
function p.forSeeTableToString (forSeeTable, options)
	-- Verificações de tipo e padrões
	checkType("forSeeTableToString", 1, forSeeTable, "table", true)
	checkType("forSeeTableToString", 2, options, "table", true)
	options = options or {}
	for k, v in pairs(forSeeDefaultOptions) do
		if options[k] == nil then options[k] = v end
	end
	-- Sequencia ("'string'ifica") cada item de para ver em uma lista
	local strList = {}
	if forSeeTable then
		for k, v in pairs(forSeeTable) do
			local useStr = v.use or options.otherText
			local pagesStr =
				p.andList(v.pages, true) or
				mFormatLink._formatLink{
					categorizeMissing = mHatnote.missingTargetCat,
					link = mHatnote.disambiguate(options.title)
				}
			local forSeeStr = string.format(options.forSeeForm, useStr, pagesStr)
			forSeeStr = punctuationCollapse(forSeeStr)
			table.insert(strList, forSeeStr)
		end
	end
	if options.extratext then table.insert(strList, punctuationCollapse(options.extratext..'.')) end
	-- Retorna a lista concatenada
	return table.concat(strList, ' ')
end

-- Produz uma sequência ('string') "Para X, ver [[Y]]" de argumentos. Espera lacunas 
-- no índice mas não valores em branco/espaço em branco. Ignora argumentos ('args') 
-- nomeados e argumentos ('args') menores que ("<") "from".
function p._forSee (args, from, options)
	local forSeeTable = p.forSeeArgsToTable(args, from, options)
	return p.forSeeTableToString(forSeeTable, options)
end

-- Assim como "_forSee", mas usa o quadro ('frame').
function p.forSee (frame, from, options)
	mArguments = require('Módulo:Testes/Gkiyoshinishimoto/Arguments')
	return p._forSee(mArguments.getArgs(frame), from, options)
end

return p