Модуль:NumberOf
Материал из Occultica
Версия от 11:53, 6 ноября 2022; ru>Stjn (более ровное отображение разбитой таблицы)
Для документации этого модуля может быть создана страница Модуль:NumberOf/doc
-- Модуль для шаблонов серии NUMBEROF и страницы [[Википедия:Список Википедий]] local p = {} -- Важнейшие переменные local mwlang = mw.getContentLanguage() local langs = mw.loadData('Module:NumberOf/lang') local cache = {} -- Разделы Википедии, закрытые от редактирования local readOnly = { aa = true, cho = true, ho = true, hz = true, ii = true, kj = true, kr = 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( "NumberOf/hourly.tab" ) return getParam(frame.args, data) end -- Функция для вывода в {{TODAYNUMBEROF}} function p.Today(frame) local data = loadTabData( "NumberOf/daily.tab" ) return getParam(frame.args, data) end -- Функция для вывода в [[Википедия:Список Википедий]] function p.Editions(frame) local data = loadTabData( "NumberOf/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