Модуль:NumberOf (Bk;rl,&NumberOf)

Перейти к навигации Перейти к поиску
Документация

Модуль NumberOf используется для отображения и обновления шаблонов {{NUMBEROF}} и {{TODAYNUMBEROF}}, использующихся для отображения статистики по другим разделам Википедии. С помощью данного модуля также отображается список Википедий. Страницы с данными для этого модуля, c:Data:Wikipedia statistics/hourly.tab и c:Data:Wikipedia statistics/daily.tab, обновляются ботом на Викискладе.

Функции и параметры

Доступные функции:

  • Editions — функция (без параметров) для вывода таблиц с языковыми разделами Википедии, отсортированными по количеству статей (используется на странице Википедия:Список Википедий). Использует Data:Wikipedia statistics/daily.tab в качестве списка данных (обновляется ботом ежедневно) и страницу Модуль:NumberOf/lang.json для отображения названий языков.
  • Now — функция для вывода наиболее актуальной статистики (используется в шаблоне {{NUMBEROF}}). Использует Data:Wikipedia statistics/hourly.tab (обновляется ботом раз в два часа) в качестве списка данных.
  • Today — функция для вывода статистики по состоянию на начало текущего UTC-дня (используется в шаблоне {{TODAYNUMBEROF}}). Использует Data:Wikipedia statistics/daily.tab в качестве списка данных.

Параметры, используемые функциями Now и Today:

  • wiki — код раздела Википедии (обязательный параметр; список кодов доступен в списке разделов Википедии);
  • param — необходимые данные (обязательный параметр):
    • articles — количество статей в разделе,
    • admins — количество администраторов,
    • activeusers — количество активных участников,
    • date — дата обновления данных в модуле,
    • depth — глубина раздела,
    • edits — количество правок в разделе,
    • files — количество файлов,
    • pages — количество страниц,
    • pos — место раздела по количеству статей (см. список Википедий),
    • users — количество участников.
  • fmt — форматирование числа (принимает любое непустое значение).

Использование

Формат использования:

  • {{#invoke:NumberOf|Editions|single=1}}
  • {{#invoke:NumberOf|Now|wiki=en|param=articles}} — 6916445 (неформатированное значение)
  • {{#invoke:NumberOf|Now|wiki=en|param=articles|fmt=N}} — 6 916 445 (форматированное значение)

Дату последнего обновления страницы с данными можно узнать с помощью конструкции {{#invoke:NumberOf|Now|wiki=en|param=date}} (26 ноября 2024, 22:47 (UTC); параметр wiki может содержать любое непустое значение). На страницах с данными дата обновления шаблона представлена в UNIX-времени.

Представленные примеры с функцией Now работают и с функцией Today.

-- Модуль для шаблонов серии NUMBEROF и страницы [[Википедия:Список Википедий]]
local p = {}

-- Важнейшие переменные
local mwlang = mw.getContentLanguage()
local langs = {}
local cache = {}

-- Разделы Википедии, закрытые от редактирования
local readOnly = {
	ak = true,
	aa = true,
	cho = true,
	ho = true,
	hz = true,
	ii = true,
	kj = true,
	kr = true,
	na = true,
	lrc = true,
	mh = true,
	mus = true,
	ng = true
}

-- Проверка пустоты параметра
local function isEmpty(s)
	return s == nil or s == ''
end

-- Округление до сотых
local function round(n)
	return math.floor(n * 100) / 100
end

-- Форматирование даты
local function formatDate(val)
	return mwlang:formatDate('j xg Y, G:i', val) .. ' (UTC)'
end

-- Длина таблицы
local function tableLength(t)
	local count = 0
	for _ in pairs(t) do count = count + 1 end
	return count
end

-- Вычисление вики по переданному номеру
local function calculatePosition(pos, info)
	pos = tonumber(pos)
	for key in pairs(info) do
		if key ~= 'total' and info[key]['pos'] == pos then
			return key
		end
	end
	
	return ''
end

-- Подгрузка и кэширование табличных данных с Викисклада
local function loadTabData( name )
	if cache[ name ] then
		return cache[ name ]
	end
	
	local tab = mw.ext.data.get( name )
	
	local fields = {}
	for index, field in ipairs( tab[ 'schema' ][ 'fields' ] ) do
		fields[ index ] = field[ 'name' ]
	end
	
	cache[ name ] = {}
	for _, row in ipairs( tab[ 'data' ] ) do
		local langData = {}
		for index, value in ipairs( row ) do
			langData[ fields[ index ] ] = value
		end
		cache[ name ][ langData[ 'lang' ] ] = langData
	end

	return cache[ name ]
end

-- Рендеринг необходимого параметра из страницы с данными
local function getParam(f, info)
	-- Парсинг параметров шаблона
	local wiki = f.wiki
	local param = f.param
	local fmt = f.fmt
	
	-- Если нет обязательных параметров, выводится ноль
	local result
	if isEmpty(wiki) or isEmpty(param) then
		result = 0
	else
		-- Убираем NUMBEROF из легаси-кода параметров
		param = param:lower():gsub('numberof','')
		
		if param == 'date' then
			result = formatDate(info['total']['date'])
			return result
		end
		
		if param == 'pos' and tonumber(wiki) ~= nil then
			-- 
			result = calculatePosition(wiki, info)
			return result
		end
		
		-- Расчёты для общего числа разделов
		if wiki == 'total' then
			if param == 'all' then
				result = tableLength(info) - 1
			end
			if param == 'active' then
				result = tableLength(info) - 1 - tableLength(readOnly)
			end
		end
		
		local obj = info[wiki]
		if obj ~= nil then
			if param ~= nil and info[wiki][param] ~= nil then
				result = info[wiki][param]
				
				-- Форматируем значение, если задан параметр
				if not isEmpty(fmt) and type(result) == 'number' then
					if param == 'depth' then
						result = math.floor(result * 100) / 100
					end
					result = mwlang:formatNum(result)
				end
			end
		else
			result = 0
		end
	end
	return tostring(result)
end

-- Вывод ссылки на языковой раздел
local function renderLink(val, text)
	local text = (isEmpty(text) and val or text)
	
	local result = ''
	if val ~= mwlang:getCode() then
		result = result .. '[[:' .. val ..  ':|'
		
		if readOnly[val] == true then
			result = result .. string.format('<s title="Данный раздел закрыт и доступен только в режиме для чтения">%s</s>', text)
		else
			result = result .. text
		end
		
		result = result .. ']]'
	else
		result = result .. text
	end
	return result
end

-- Вывод названия языка
local function renderLang(val, frame)
	local text = langs[val] and langs[val][1] or mwlang:ucfirst(mw.language.fetchLanguageName(val))
	local link = langs[val] and langs[val][2] or nil
	local result = text
	if readOnly[val] == true then
		result = '<s title="Данный раздел закрыт и доступен только в режиме для чтения">' .. text .. '</s> (закрыт)'
	end
	
	if not isEmpty(link) then
		result = string.format('[[:%s|%s]]', link, result)
	end

	if val == mwlang:getCode() then
		result = result .. ' [[File:Mw-unwatch-icon.svg|16px|text-top|alt=(текущий раздел)|link=]]'
	end
	
	return result
end

-- Функция для вывода ячейки
local function renderNum(val, key, stats)
	local text = mwlang:formatNum(val)
	if isEmpty(stats) or val == 0 then
		return text
	end
	
	return string.format(
		'[[:%s:%s|%s]]',
		key,
		stats,
		text
	)
end

-- Функция для вывода ряда таблицы
local function createRow(key, val, frame)
	local result = mw.html.create('tr')
		:css('text-align', 'right')
	if key == mwlang:getCode() then
		result
			:css('background', '#d5fdf4')
			:css('font-weight', 'bold')
	end
	
	result:tag('td')
		:wikitext(val['pos'])
	
	result:tag('td')
		:wikitext(renderLink(key))
	
	result:tag('td')
		:wikitext(renderLang(key, frame))
		:css('text-align', 'left')
	
	result:tag('td')
		:wikitext(renderNum(val['articles'], key, 'Special:Statistics'))
	
	result:tag('td')
		:wikitext(renderNum(val['pages']))
	
	result:tag('td')
		:wikitext(renderNum(val['edits']))
	
	result:tag('td')
		:wikitext(renderNum(round(val['depth'])))
	
	result:tag('td')
		:wikitext(renderNum(val['users'], key, 'Special:ListUsers'))
	
	result:tag('td')
		:wikitext(renderNum(val['activeusers'], key, 'Special:ActiveUsers'))
	
	result:tag('td')
		:wikitext(renderNum(val['admins'], key, 'Special:ListAdmins'))
	
	result:tag('td')
		:wikitext(renderNum(val['files'], key, 'Special:ListFiles'))
	
	return result
end

-- Функция для вывода шапки таблицы
local function createHeader()
	local result = mw.html.create('table')
		:addClass('wikitable sortable')
		:attr('style', 'font-feature-settings:"tnum" 1; margin:0.25em 0; width:100%;')
		:css('width', '100%')
		:css('margin', '0.25em 0')
	
	result:tag('tr')
	
	local cells = {
		'№',
		'Код',
		'Язык',
		'Статей',
		'Страниц',
		'Правок',
		'<abbr title="Глубина">Глуб.</abbr>',
		'Участников',
		'<abbr title="Активных участников">(акт.)</abbr>',
		'<abbr title="Администраторов">Адм.</abbr>',
		'Файлов',
	}
	
	for i, val in ipairs(cells) do
		result
			:tag('th')
			:attr('scope', 'col')
			:css('text-align', (val == 'Язык' and 'left' or 'right'))
			:css('width', (val == 'Язык' and '25%' or nil))
			:wikitext(val)
	end
	
	return result
end

-- Функция для вывода подвала таблицы
local function createFooter(frame, hide)
	-- Параметр hide отвечает за оптическое выравнивание таблиц
	local val = frame['total']
	local cellStyle = 'text-align:right;'
	local cellStyleHide = 'color:transparent; padding-top:0; padding-bottom:0; white-space:nowrap;'
	if hide then
		cellStyle = 'padding-top:0; padding-bottom:0;' .. cellStyle
	end
	
	-- aa — закрытый раздел с самой большой глубиной
	local num = tableLength(frame) - 1
	local depth = (hide and round(frame['aa']['depth']) or round(val['depth']))
	local result = mw.html.create('tr')
		:addClass('sortbottom' .. (hide and ' nomobile' or ''))
		:attr('style', (hide and ' line-height:0; visibility:hidden; white-space:nowrap;' or ''))
	
	if hide then
		result:attr('aria-hidden', 'true')
	end
	
	result:tag('th')
		:wikitext(string.format('<span aria-hidden="true">%s</span>', num))
		:attr('style', cellStyleHide)
	
	-- zh-classical — самое длинное название раздела
	result:tag('th')
		:wikitext('<span aria-hidden="true">zh-classical</span>')
		:attr('style', cellStyleHide)
	
	result:tag('th')
		:attr('scope', 'col')
		:wikitext('Всего')
		:attr('style', cellStyle)
		:css('text-align', 'left')
	
	result:tag('th')
		:attr('scope', 'col')
		:wikitext(renderNum(val['articles']))
		:attr('style', cellStyle)
	
	result:tag('th')
		:attr('scope', 'col')
		:wikitext(renderNum(val['pages']))
		:attr('style', cellStyle)
	
	result:tag('th')
		:attr('scope', 'col')
		:wikitext(renderNum(val['edits']))
		:attr('style', cellStyle)
	
	result:tag('th')
		:attr('scope', 'col')
		:wikitext(renderNum(depth))
		:attr('style', cellStyle)
	
	result:tag('th')
		:attr('scope', 'col')
		:wikitext(renderNum(val['users']))
		:attr('style', cellStyle)
	
	result:tag('th')
		:attr('scope', 'col')
		:wikitext(renderNum(val['activeusers']))
		:attr('style', cellStyle)
	
	result:tag('th')
		:attr('scope', 'col')
		:wikitext(renderNum(val['admins']))
		:attr('style', cellStyle)
	
	result:tag('th')
		:attr('scope', 'col')
		:wikitext(renderNum(val['files']))
		:attr('style', cellStyle)
	
	return result
end

-- Функция для вывода в {{NUMBEROF}}
function p.Now(frame)
	local data = loadTabData( "Wikipedia_statistics/hourly.tab" )
	
	return getParam(frame.args, data)
end

-- Функция для вывода в {{TODAYNUMBEROF}}
function p.Today(frame)
	local data = loadTabData( "Wikipedia_statistics/daily.tab" )
	
	return getParam(frame.args, data)
end

-- Функция для вывода в [[Википедия:Список Википедий]]
function p.Editions(frame)
	langs = mw.loadJsonData( "Модуль:NumberOf/lang.json" )
	local data = loadTabData( "Wikipedia_statistics/daily.tab" )
	local single = frame.args.single or false
	local length = tableLength(data)
	local result = ''
	
	-- Таблицы для сбора разделов по величине
	local sorted = {
		[1000000] = {},
		[100000] = {},
		[10000] = {},
		[1000] = {},
		[0] = {}
	}
	local sortedKeys = { 0, 1000, 10000, 100000, 1000000 }
	if single then
		sortedKeys = { 0 }
	end
	
	-- Заполняем пустыми элементами каждую таблицу
	for i = #sortedKeys, 1, -1 do
		local n = sortedKeys[i]
		for j = 1, length, 1 do
			table.insert(sorted[n], false)
		end
	end
	
	-- Сортировка разделов по позиции и величине
	for key, val in pairs(data) do
		local curr = data[key]
		
		if single and key ~= 'total' then
			sorted[ 0 ][ tonumber( curr[ 'pos' ] ) ] = key
		elseif key ~= 'total' then
			if curr['articles'] <= 1000 then
				sorted[ 0 ][ tonumber( curr[ 'pos' ] ) ] = key
			else
				for i = #sortedKeys, 2, -1 do
					local n = sortedKeys[ i ]
					if curr['articles'] / n > 1 then
						sorted[ n ][ tonumber( curr[ 'pos' ] ) ] = key
						break
					end
				end
			end
		end
	end
	
	-- Вывод таблицы
	for i = #sortedKeys, 1, -1 do
		if not single and i ~= #sortedKeys then
			result = result .. '\n'
		end
		
		local n = sortedKeys[i]
		if not single then
			if n == 0 then
				result = result .. '=== Менее 1000 статей ==='
			else
				result = result .. string.format('=== Более %s статей ===', mwlang:formatNum(n))
			end
		end
		
		-- Автоматический скролл для недостаточно широких мониторов
		local section = mw.html.create('div')
			:attr('style', 'overflow-x:auto; overflow-y:hidden;')
		
		local sectionTable = createHeader()

		-- Вывод рядов таблицы
		for _, val in ipairs(sorted[ n ]) do
			if val ~= false then
				local curr = data[ val ]
				sectionTable:node(createRow(val, curr, frame))
			end
		end
		
		-- Вывод подвала таблицы
		sectionTable:node(createFooter(data, (n ~= 0)))
		section:node(sectionTable)
		
		result = result .. '\n' .. tostring(section)
	end
	
	return result
end

return p