Mudanças entre as edições de "Módulo:Gb"
Ir para navegação
Ir para pesquisar
| Linha 1: | Linha 1: | ||
local p = {} | local p = {} | ||
-- === Utilidades === | |||
local function trim(s) | |||
if type(s) ~= "string" then | |||
return s | |||
end | |||
return mw.text.trim(s) | |||
end | |||
-- | local function split_list(s) | ||
-- Tenta primeiro por ';' (recomendado quando usar vírgula decimal). | |||
if s:find(";") then | |||
local t = {} | |||
for part in mw.text.gsplit(s, ";", true) do | |||
table.insert(t, part) | |||
end | |||
return t | |||
end | |||
-- Caso não haja ';', separa por ',' (útil se números usam ponto decimal). | |||
local t = {} | |||
for part in mw.text.gsplit(s, ",", true) do | |||
table.insert(t, part) | |||
end | |||
return t | |||
end | |||
local function parse_number(token) | |||
if type(token) ~= "string" then | |||
return nil | |||
end | |||
token = trim(token) | |||
if token == "" then | |||
return nil | |||
end | |||
-- | -- Remove espaços | ||
token = token:gsub("%s+", "") | |||
: | |||
-- Heurística pt-BR: | |||
-- 1) Se tiver vírgula e ponto, assume '.' como milhar e ',' como decimal | |||
-- Ex.: "1.234,56" -> "1234.56" | |||
if token:find(",") and token:find("%.") then | |||
token = token:gsub("%.", "") -- tira milhares | |||
token = token:gsub(",", ".") -- vírgula -> ponto decimal | |||
-- 2) Se só tiver vírgula, assume vírgula decimal | |||
elseif token:find(",") then | |||
token = token:gsub(",", ".") | |||
-- 3) Se só tiver ponto, deixa como está (ponto decimal) | |||
end | |||
return tostring( | local n = tonumber(token) | ||
return n | |||
end | |||
local function collect_numbers(frame) | |||
local args = frame:getParent() and frame:getParent().args or frame.args | |||
local nums = {} | |||
-- 1) Lista em 'valores=' | |||
local lista = args.valores or args.lista | |||
if lista and trim(lista) ~= "" then | |||
for _, part in ipairs(split_list(lista)) do | |||
local n = parse_number(part) | |||
if n then | |||
table.insert(nums, n) | |||
end | |||
end | |||
end | |||
-- 2) Parâmetros posicionais 1,2,3,... | |||
local i = 1 | |||
while true do | |||
local v = args[i] | |||
if v == nil then | |||
break | |||
end | |||
v = trim(v) | |||
if v and v ~= "" then | |||
-- Pode ser lista dentro do posicional | |||
local parts = split_list(v) | |||
for _, part in ipairs(parts) do | |||
local n = parse_number(part) | |||
if n then | |||
table.insert(nums, n) | |||
end | |||
end | |||
end | |||
i = i + 1 | |||
end | |||
return nums | |||
end | |||
local function round(n, casas) | |||
if not casas or casas == "" then | |||
return n | |||
end | |||
casas = tonumber(casas) or 0 | |||
local mult = 10 ^ casas | |||
if n >= 0 then | |||
return math.floor(n * mult + 0.5) / mult | |||
else | |||
return math.ceil(n * mult - 0.5) / mult | |||
end | |||
end | |||
local function format_number(n, casas, formato) | |||
n = round(n, casas) | |||
if formato == "pt" then | |||
-- Formata com vírgula decimal e ponto de milhar | |||
local s = string.format("%." .. tostring(tonumber(casas) or 0) .. "f", n) | |||
-- s usa ponto decimal; converter para pt-BR | |||
local inteiro, frac = s:match("^(-?%d+)%.(%d+)$") | |||
if not inteiro then | |||
inteiro = s | |||
frac = nil | |||
end | |||
-- insere pontos nos milhares | |||
local k | |||
local sign = "" | |||
if inteiro:sub(1, 1) == "-" then | |||
sign = "-" | |||
inteiro = inteiro:sub(2) | |||
end | |||
while true do | |||
inteiro, k = inteiro:gsub("^(-?%d+)(%d%d%d)", "%1.%2") | |||
if k == 0 then | |||
break | |||
end | |||
end | |||
if frac then | |||
return sign .. inteiro .. "," .. frac | |||
else | |||
return sign .. inteiro | |||
end | |||
else | |||
-- "en" ou padrão: ponto decimal, sem milhar | |||
local casasN = tonumber(casas) or 0 | |||
return string.format("%." .. tostring(casasN) .. "f", n) | |||
end | |||
end | |||
local function compute(nums, op) | |||
if #nums == 0 then | |||
return nil | |||
end | |||
if op == "soma" or op == "sum" then | |||
local s = 0 | |||
for _, v in ipairs(nums) do | |||
s = s + v | |||
end | |||
return s | |||
elseif op == "media" or op == "média" or op == "avg" then | |||
local s = 0 | |||
for _, v in ipairs(nums) do | |||
s = s + v | |||
end | |||
return s / #nums | |||
elseif op == "min" or op == "mín" then | |||
local m = nums[1] | |||
for i = 2, #nums do | |||
if nums[i] < m then | |||
m = nums[i] | |||
end | |||
end | |||
return m | |||
elseif op == "max" then | |||
local m = nums[1] | |||
for i = 2, #nums do | |||
if nums[i] > m then | |||
m = nums[i] | |||
end | |||
end | |||
return m | |||
elseif op == "contar" or op == "count" then | |||
return #nums | |||
else | |||
-- padrão: soma | |||
local s = 0 | |||
for _, v in ipairs(nums) do | |||
s = s + v | |||
end | |||
return s | |||
end | |||
end | |||
-- === Interface pública === | |||
function p.calc(frame) | |||
local args = frame:getParent() and frame:getParent().args or frame.args | |||
local op = (args.op or args.operacao or args.operacao or "soma"):lower() | |||
local casas = args.casas or args.decimais or 2 | |||
local formato = (args.formato or args.locale or "pt"):lower() -- "pt" ou "en" | |||
local nums = collect_numbers(frame) | |||
local result = compute(nums, op) | |||
if result == nil then | |||
return "—" -- sem dados | |||
end | |||
return format_number(result, casas, formato) | |||
end | end | ||
return p | return p | ||
Edição das 19h35min de 26 de setembro de 2025
A documentação para este módulo pode ser criada em Módulo:Gb/doc
local p = {}
-- === Utilidades ===
local function trim(s)
if type(s) ~= "string" then
return s
end
return mw.text.trim(s)
end
local function split_list(s)
-- Tenta primeiro por ';' (recomendado quando usar vírgula decimal).
if s:find(";") then
local t = {}
for part in mw.text.gsplit(s, ";", true) do
table.insert(t, part)
end
return t
end
-- Caso não haja ';', separa por ',' (útil se números usam ponto decimal).
local t = {}
for part in mw.text.gsplit(s, ",", true) do
table.insert(t, part)
end
return t
end
local function parse_number(token)
if type(token) ~= "string" then
return nil
end
token = trim(token)
if token == "" then
return nil
end
-- Remove espaços
token = token:gsub("%s+", "")
-- Heurística pt-BR:
-- 1) Se tiver vírgula e ponto, assume '.' como milhar e ',' como decimal
-- Ex.: "1.234,56" -> "1234.56"
if token:find(",") and token:find("%.") then
token = token:gsub("%.", "") -- tira milhares
token = token:gsub(",", ".") -- vírgula -> ponto decimal
-- 2) Se só tiver vírgula, assume vírgula decimal
elseif token:find(",") then
token = token:gsub(",", ".")
-- 3) Se só tiver ponto, deixa como está (ponto decimal)
end
local n = tonumber(token)
return n
end
local function collect_numbers(frame)
local args = frame:getParent() and frame:getParent().args or frame.args
local nums = {}
-- 1) Lista em 'valores='
local lista = args.valores or args.lista
if lista and trim(lista) ~= "" then
for _, part in ipairs(split_list(lista)) do
local n = parse_number(part)
if n then
table.insert(nums, n)
end
end
end
-- 2) Parâmetros posicionais 1,2,3,...
local i = 1
while true do
local v = args[i]
if v == nil then
break
end
v = trim(v)
if v and v ~= "" then
-- Pode ser lista dentro do posicional
local parts = split_list(v)
for _, part in ipairs(parts) do
local n = parse_number(part)
if n then
table.insert(nums, n)
end
end
end
i = i + 1
end
return nums
end
local function round(n, casas)
if not casas or casas == "" then
return n
end
casas = tonumber(casas) or 0
local mult = 10 ^ casas
if n >= 0 then
return math.floor(n * mult + 0.5) / mult
else
return math.ceil(n * mult - 0.5) / mult
end
end
local function format_number(n, casas, formato)
n = round(n, casas)
if formato == "pt" then
-- Formata com vírgula decimal e ponto de milhar
local s = string.format("%." .. tostring(tonumber(casas) or 0) .. "f", n)
-- s usa ponto decimal; converter para pt-BR
local inteiro, frac = s:match("^(-?%d+)%.(%d+)$")
if not inteiro then
inteiro = s
frac = nil
end
-- insere pontos nos milhares
local k
local sign = ""
if inteiro:sub(1, 1) == "-" then
sign = "-"
inteiro = inteiro:sub(2)
end
while true do
inteiro, k = inteiro:gsub("^(-?%d+)(%d%d%d)", "%1.%2")
if k == 0 then
break
end
end
if frac then
return sign .. inteiro .. "," .. frac
else
return sign .. inteiro
end
else
-- "en" ou padrão: ponto decimal, sem milhar
local casasN = tonumber(casas) or 0
return string.format("%." .. tostring(casasN) .. "f", n)
end
end
local function compute(nums, op)
if #nums == 0 then
return nil
end
if op == "soma" or op == "sum" then
local s = 0
for _, v in ipairs(nums) do
s = s + v
end
return s
elseif op == "media" or op == "média" or op == "avg" then
local s = 0
for _, v in ipairs(nums) do
s = s + v
end
return s / #nums
elseif op == "min" or op == "mín" then
local m = nums[1]
for i = 2, #nums do
if nums[i] < m then
m = nums[i]
end
end
return m
elseif op == "max" then
local m = nums[1]
for i = 2, #nums do
if nums[i] > m then
m = nums[i]
end
end
return m
elseif op == "contar" or op == "count" then
return #nums
else
-- padrão: soma
local s = 0
for _, v in ipairs(nums) do
s = s + v
end
return s
end
end
-- === Interface pública ===
function p.calc(frame)
local args = frame:getParent() and frame:getParent().args or frame.args
local op = (args.op or args.operacao or args.operacao or "soma"):lower()
local casas = args.casas or args.decimais or 2
local formato = (args.formato or args.locale or "pt"):lower() -- "pt" ou "en"
local nums = collect_numbers(frame)
local result = compute(nums, op)
if result == nil then
return "—" -- sem dados
end
return format_number(result, casas, formato)
end
return p