Модуль:Hatnote: различия между версиями
imported>QBA-bot м (Защитил страницу Модуль:Hatnote: критический шаблон или модуль (каскадная защита) ([Редактирование=только автоподтверждённые] (бессрочно) [Переименование=только автоподтверждённые] (бессрочно))) |
Admin (обсуждение | вклад) м (Замена текста — «Википедия:» на «ЭАНМ:») |
||
(не показаны 43 промежуточные версии 9 участников) | |||
Строка 1: | Строка 1: | ||
local get_args = require('Module:Arguments').getArgs | local get_args = require('Module:Arguments').getArgs | ||
local mError | local mError | ||
local yesno = require('Module:Yesno') | local yesno = function (v) return require('Module:Yesno')(v, true) end | ||
local p, tr = {}, {} | local p, tr = {}, {} | ||
Строка 9: | Строка 9: | ||
no_links = 'ЭАНМ:Страницы с модулем Hatnote без ссылок', | no_links = 'ЭАНМ:Страницы с модулем Hatnote без ссылок', | ||
red_link = 'ЭАНМ:Страницы с модулем Hatnote с красной ссылкой', | red_link = 'ЭАНМ:Страницы с модулем Hatnote с красной ссылкой', | ||
bad_format = 'ЭАНМ:Страницы с модулем Hatnote с некорректно заполненными параметрами', | |||
unparsable_link = 'ЭАНМ:Страницы с модулем Hatnote с нечитаемой ссылкой', | |||
formatted = 'ЭАНМ:Страницы с модулем Hatnote с готовым форматированием', | |||
} | } | ||
local function index(t1, t2) | |||
return setmetatable(t1, {__index = t2}) | |||
end | |||
local function concat(e1, e2) | local function concat(e1, e2) | ||
Строка 16: | Строка 22: | ||
end | end | ||
function tr. | function tr.define_categories(tracked_cases) | ||
local categories = | local categories = setmetatable({}, { | ||
__tostring = function (self) return table.concat(self) end, | |||
function categories:add( | __concat = concat | ||
}) | |||
function categories:add(element, nocat) | |||
if not nocat then | if not nocat then | ||
table.insert(self, string.format('[[Категория:%s]]', | local cat_name | ||
if tracked_cases and tracked_cases[element] then | |||
cat_name = tracked_cases[element] | |||
else | |||
cat_name = element | |||
end | |||
table.insert(self, string.format('[[Категория:%s]]', cat_name)) | |||
end | end | ||
end | end | ||
return | return categories | ||
end | end | ||
Строка 38: | Строка 50: | ||
current_frame:getTitle() | current_frame:getTitle() | ||
if not preview_only or current_frame:preprocess('{{REVISIONID}}') == '' then | if not preview_only or current_frame:preprocess('{{REVISIONID}}') == '' then | ||
return mError.error{string.format('Ошибка в [[%s]]: %s', res_frame_title, msg)} .. categories | mError = require('Module:Error') | ||
return mError.error{ | |||
tag = 'div', | |||
string.format('Ошибка в [[%s]]: %s.' | |||
.. (preview_only and '<br><small>Это сообщение показывается только во время предпросмотра.</small>' or ''), res_frame_title, msg) | |||
} .. categories | |||
else | else | ||
return categories | return categories | ||
Строка 44: | Строка 61: | ||
end | end | ||
function p. | function p.parse_link(frame) | ||
local args = get_args(frame) | |||
local link = args[1]:gsub('\n', '') | |||
local label | |||
link = mw.text.trim(link:match('^%[%[([^%]]+)%]%]$') or link) | |||
if link:sub(1, 1) == '/' then | |||
label = link | |||
link = current_title.prefixedText .. link | |||
end | |||
link = link:match(':?(.+)') | |||
if link:match('|') then | |||
link, label = link:match('^([^%|]+)%|(.+)$') | |||
end | |||
if not mw.title.new(link) then | |||
return nil, nil | |||
end | |||
return link, label | |||
end | |||
function p.format_link(frame) | |||
-- {{ссылка на раздел}} | -- {{ссылка на раздел}} | ||
local args = get_args(frame) | local args = get_args(frame) | ||
local link, section, label = args[1], args[2], | local link, section, label = args[1], args[2], args[3] | ||
if not link then | if not link then | ||
link = current_title. | link = current_title.prefixedText | ||
if section then | if section then | ||
link = '#' .. section | link = '#' .. section | ||
label = '§ ' .. section | label = label or '§ ' .. section | ||
end | end | ||
else | else | ||
local parsed_link, parsed_label = p.parse_link{link} | |||
if | if parsed_link then | ||
link | link = parsed_link | ||
else | |||
return link | |||
end | end | ||
if section and not link:match('#') then | if section and not link:match('#') then | ||
link = link .. '#' .. section | link = link .. '#' .. section | ||
if parsed_label then | |||
parsed_label = parsed_label .. '#' .. section | |||
end | |||
end | end | ||
label = (label or link):gsub('^([^#]-)#(.+)$', '%1 § %2') | label = (label or parsed_label or link):gsub('^([^#]-)#(.+)$', '%1 § %2') | ||
end | end | ||
return string.format('[[%s|%s]]', link, label | if label and label ~= link then | ||
return string.format('[[:%s|%s]]', link, label) | |||
else | |||
return string.format('[[:%s]]', link) | |||
end | |||
end | end | ||
function p.remove_precision(frame) | function p.remove_precision(frame) | ||
-- {{без уточнения}} | |||
local args = get_args(frame) | |||
local title = args[1] | |||
return title:match('^(.+)%s+%b()$') or title | |||
end | end | ||
function p.is_disambig(frame) | function p.is_disambig(frame) | ||
local args = get_args(frame) | local args = get_args(frame) | ||
local title = | local title = args[1] | ||
local page = mw.title.new(title) | local page = mw.title.new(title) | ||
if not page.exists or mw.title.equals(page, current_title) then | if not page or not page.exists or mw.title.equals(page, current_title) then | ||
return false | return false | ||
end | end | ||
Строка 95: | Строка 143: | ||
end | end | ||
end | end | ||
return false | |||
end | end | ||
Строка 102: | Строка 150: | ||
local list_sep = args.list_sep or args['разделитель списка'] or ', ' | local list_sep = args.list_sep or args['разделитель списка'] or ', ' | ||
local last_list_sep = yesno(args.natural_join) ~= false and ' и ' or list_sep | local last_list_sep = yesno(args.natural_join) ~= false and ' и ' or list_sep | ||
local links_ns = args.links_ns or args['ПИ ссылок'] | |||
local bold_links = yesno(args.bold_links or args['ссылки болдом']) | local bold_links = yesno(args.bold_links or args['ссылки болдом']) | ||
local | local res_list = {} | ||
local tracked = { | local tracked = { | ||
red_link = false, | red_link = false, | ||
bad_format = false, | |||
formatted = false, | |||
unparsable_link = false | |||
} | } | ||
local i = 1 | |||
while args[i] do | while args[i] do | ||
local link = args[i] | local link = args[i] | ||
local label = args['l' .. i] | local label = args['l' .. i] | ||
local element = '' | |||
if link:match('<span') then -- TODO: переписать | |||
tracked.formatted = true | |||
element = link -- for {{не переведено}} | |||
else | |||
local bad_format = (link:match('|') or link:match('[%[%]]')) ~= nil | |||
local parsed_link, parsed_label = p.parse_link{link} | |||
if parsed_link then | |||
tracked.bad_format = tracked.bad_format or bad_format | |||
if links_ns then | |||
parsed_label = parsed_label or parsed_link | |||
parsed_link = mw.site.namespaces[links_ns].name .. ':' .. parsed_link | |||
end | |||
local title = mw.title.new(parsed_link) | |||
tracked.red_link = tracked.red_link or not (title.isExternal or title.exists) | |||
element = p.format_link{parsed_link, nil, label or parsed_label} | |||
else | |||
tracked.unparsable_link = true | |||
element = link | |||
end | end | ||
end | end | ||
if bold_links then | |||
element = string.format('<b>%s</b>', element) | element = string.format('<b>%s</b>', element) | ||
end | end | ||
table.insert(res_list, element) | |||
i = i + 1 | |||
end | end | ||
return setmetatable( | return setmetatable(res_list, { | ||
__index = tracked, | __index = tracked, | ||
__tostring = function (self) return self | __tostring = function (self) return mw.text.listToText(self, list_sep, last_list_sep) end, | ||
__concat = concat | __concat = concat, | ||
__pairs = function (self) return pairs(tracked) end | |||
}) | }) | ||
end | end | ||
Строка 152: | Строка 208: | ||
function p.hatnote(frame) | function p.hatnote(frame) | ||
local args = get_args(frame) | local args = get_args(frame) | ||
local text = | local text = args[1] | ||
local id = args.id | |||
local extraclasses = args.extraclasses | local extraclasses = args.extraclasses | ||
local hide_disambig = yesno(args.hide_disambig) | local hide_disambig = yesno(args.hide_disambig) | ||
local res = mw.html.create('div') | local res = mw.html.create('div') | ||
:attr('id', id) | |||
:addClass('hatnote') | :addClass('hatnote') | ||
:addClass('navigation-not-searchable') | |||
:addClass(extraclasses) | :addClass(extraclasses) | ||
:wikitext(text) | :wikitext(text) | ||
Строка 168: | Строка 227: | ||
end | end | ||
function p.main(frame, | function p.main(frame, _tracking_categories) | ||
local args = get_args(frame, {trim = false}) | local args = get_args(frame, {trim = false}) | ||
Строка 174: | Строка 233: | ||
local prefix_plural = args.prefix_plural or args['префикс мн. ч.'] | local prefix_plural = args.prefix_plural or args['префикс мн. ч.'] | ||
local sep = args.sep or args['разделитель'] or ' ' | local sep = args.sep or args['разделитель'] or ' ' | ||
local dot = yesno(args.dot or args['точка']) and '.' or '' | local dot = yesno(args.dot or args['точка']) and '.' or '' | ||
local nocat = yesno(args.nocat) | local nocat = yesno(args.nocat) | ||
local preview_error = yesno(args.preview_error) | local preview_error = yesno(args.preview_error) | ||
local empty_list_message = args.empty_list_message or 'Не указано ни одной страницы' | |||
categories = tr. | categories = tr.define_categories(index(_tracking_categories or {}, tracking_categories)) | ||
if not prefix | if not prefix then | ||
categories:add('no_prefix', nocat) | categories:add('no_prefix', nocat) | ||
return tr.error('Не указан префикс', categories) | return tr.error('Не указан префикс', categories) | ||
Строка 187: | Строка 246: | ||
if not args[1] then | if not args[1] then | ||
categories:add('no_links', nocat) | categories:add('no_links', nocat) | ||
return tr.error( | return tr.error(empty_list_message, categories, preview_error) | ||
end | end | ||
Строка 195: | Строка 254: | ||
local list = p.list(args) | local list = p.list(args) | ||
for k, v in pairs(list) do | |||
if type(v) == 'boolean' and v then | |||
categories:add(k, nocat) | |||
end | |||
end | end | ||
return p.hatnote{prefix .. sep .. list .. dot, | return p.hatnote(index({prefix .. sep .. list .. dot}, args)) .. categories | ||
end | end | ||
return | return index(p, tr) |
Текущая версия от 05:00, 11 января 2024
Этот модуль относится к критическим. У него очень много включений или он используется с подстановкой. Из-за опасности вандализма или ошибочного редактирования он был защищён. |
Прежде чем вносить какие-либо изменения в этот модуль, просьба оттестировать их в /песочнице и проверить результат на странице с /контрольными примерами. Вносить тестированные изменения в этот модуль следует ровно одной правкой. |
Модуль предназначен для реализации hatnote-шаблонов (например, {{основная статья}}, {{см. также}}). Также он содержит несколько функций для работы со ссылками и отслеживания ошибок. См. тесты на странице Шаблон:Основная статья/тесты.
Основные функции
Основные функции содержатся в таблице p
, все функции можно вызывать как из других модулей, так и через {{#invoke}}. При вызове из модуля функции принимают первым параметром таблицу с аргументами. Все описанные ниже параметры являются аргументами этой таблицы. Некоторые параметры принимают булевоподобные значения: их перечисление можно найти здесь.
parse_link
Принимает на вход ссылку, возвращает два значения: целевую страницу и видимую часть ссылки. Если ссылку не удалось разобрать, возвращает nil
. Наличие квадратных скобок не обязательно.
format_link
Формирует вики-ссылку. Превращает #
в ссылках на разделы в знак параграфа, отбитый пробелами, делая их более читабельными (см. {{ссылка на раздел}}).
Параметры:
- первый параметр: название целевой страницы или вики-ссылка.
- второй параметр: название раздела. Параметр будет проигнорирован, если раздел уже задан в первом параметре через
#
. - третий параметр: видимый текст ссылки.
remove_precision
Убирает уточнение из первого параметра (см. {{без уточнения}}).
is_disambig
Функция, определяющая, является ли первый параметр страницей разрешения неоднозначности. Список шаблонов неоднозначностей берется со страницы MediaWiki:Disambiguationspage.
list
Формирует список из вики-ссылок.
Параметры:
- нумерованные параметры — ссылки на страницы.
|l1 =
,|l2 =
и т. д. — текст ссылок.|list_sep =
или|разделитель списка =
: разделитель списка, по умолчанию — «, ».|natural_join =
: при неложном значении вместо последнего разделителя выводится союз «и».|links_ns =
или|ПИ ссылок =
: ко всем ссылкам добавляется префикс указанного пространства имен.|bold_links =
или|ссылки болдом =
: при непустом и неложном значении каждая ссылка выделяется полужирным.
Возвращает таблицу, для неё предусмотрен метод tostring()
и конкатенация. Именованные параметры этой таблицы — булевы переменные, истинные при обнаружении проблем. Отслеживаемые проблемы: red_link
, bad_format
, formatted
, unparsable_link
. Их описание см. ниже.
hatnote
Формирует код <div class="hatnote">...</div>
.
Параметры:
- первый параметр — текст заметки.
|id =
: id заметки.|extraclasses =
: дополнительные классы заметки.|hide_disambig =
: при непустом и неложном значении к тексту добавляется классdabhide
, скрывающий подсветку для пользователей гаджета подсветки неоднозначностей.
main
Генерирует текст-заметку. Позволяет ссылаться на неограниченное число страниц. Синтаксис:
- вызов из вики-текста:
{{#invoke:Hatnote|main|префикс= префикс |префикс мн. ч.= префикс для нескольких ссылок |разделитель списка= разделитель списка |ПИ ссылок= пространство имен |extraclasses = дополнительные классы |точка= true / false |hide_disambig= true / false}}
- вызов из другого модуля:
-- подключение модуля local hatnote = require('Модуль:Hatnote').main -- пример использования без изменения категорий по умолчанию local res1 = hatnote{ 'Какая-то статья', l1 = 'Какой-нибудь текст ссылки', prefix = 'Основная статья:', prefix_plural = 'Основные статьи:', dot = true, nocat = args.nocat -- следует всегда передавать nocat, если он предусмотрен } -- и с изменением local res2 = hatnote({ 'Еще какая-то статья', prefix = 'См. также:', list_sep = '; ', natural_join = false, nocat = args.nocat }, { red_link = 'ЭАНМ:Статьи с шаблоном «См. также» с красными ссылками' })
Параметры:
|prefix =
или|префикс =
: обязательный параметр, текст перед списком ссылок.|prefix_plural =
или|префикс мн. ч. =
: если ссылок несколько, для префикса вместо предыдущего параметра будет использован этот.|sep =
или|разделитель =
: разделитель между префиксом и списком ссылок.|dot =
или|точка =
: при непустом и неложном значении выводится точка в конце.|nocat =
: при непустом и неложном значении отключается категоризация.|empty_list_message =
: сообщение об ошибке, выводящееся при пустом списке ссылок. По умолчанию — «Не указано ни одной страницы».|preview_error =
: при непустом и неложном значении ошибка при пустом списке ссылок выводится только во время предпросмотра.
Функция также принимает все параметры функций list
и hatnote
и передает их без изменения в эти функции.
Вторым параметром (при вызове через другой модуль) функция принимает таблицу с переназначенными категориями. Их идентификаторы см. ниже:
Служебные функции
Находятся в таблице tr
, предназначены для отслеживания проблем и вывода сообщений об ошибках. Все функции можно вызывать только из других модулей.
define_categories
tr.define_categories(tracking_categories)
Создает объект, представляющий собой таблицу с категориями и функцией для их добавления. Первым параметром передается ассоциированный массив с идентификаторами отслеживающих категорий.
add
categories:add(type, nocat)
или
categories:add(category_name, nocat)
Добавляет в таблицу категории. Первый параметр — либо идентификатор, объявленный в define_categories()
, либо название категории. Второй параметр — отключение категоризации.
error
tr.error(msg, categories, preview_only)
Выводит сообщение об ошибке. Если ошибка генерируется шаблоном, то отображается название этого шаблона; если прямым вызовом через {{#invoke}}, то название вызываемого модуля.
Параметры:
msg
: текст сообщения.categories
: либо объект, возвращаемыйdefine_categories()
, либо строка с категориями.preview_only
: при непустом и неложном значении ошибка выводится только во время предпросмотра.
Отслеживаемые случаи
no_prefix
— не задан префикс. Также выводится сообщение об ошибке.no_links
— не задано ни одной ссылки. Также выводится сообщение об ошибке (либо в тексте статьи, либо только во время предварительного просмотра, подробнее см. #main-Параметры).- Категория: ЭАНМ:Страницы с модулем Hatnote без ссылок
red_link
— параметр содержит ссылку на несуществующую статью.bad_format
— параметр содержит разделитель{{!}}
вместо параметра|lx =
или лишние квадратные скобки.formatted
— параметр содержит<span
(в таком случае он не парсится, а выдаётся в готовом виде). Нужно для поддержки шаблонов {{не переведено}} в качестве параметров.unparsable_link
— параметр содержит нестандартное оформление, которое не может быть разобрано модулем (например,{{см. также|[https://example.com внешние ссылки]}}
или{{см. также|посторонний текст помимо [[ссылка|ссылки]]}}
).
Использование в других модулях
Некоторые функции используются в следующих модулях (проверить):
local get_args = require('Module:Arguments').getArgs local mError local yesno = function (v) return require('Module:Yesno')(v, true) end local p, tr = {}, {} local current_title = mw.title.getCurrentTitle() local tracking_categories = { no_prefix = 'ЭАНМ:Страницы с модулем Hatnote без указания префикса', no_links = 'ЭАНМ:Страницы с модулем Hatnote без ссылок', red_link = 'ЭАНМ:Страницы с модулем Hatnote с красной ссылкой', bad_format = 'ЭАНМ:Страницы с модулем Hatnote с некорректно заполненными параметрами', unparsable_link = 'ЭАНМ:Страницы с модулем Hatnote с нечитаемой ссылкой', formatted = 'ЭАНМ:Страницы с модулем Hatnote с готовым форматированием', } local function index(t1, t2) return setmetatable(t1, {__index = t2}) end local function concat(e1, e2) return tostring(e1) .. tostring(e2) end function tr.define_categories(tracked_cases) local categories = setmetatable({}, { __tostring = function (self) return table.concat(self) end, __concat = concat }) function categories:add(element, nocat) if not nocat then local cat_name if tracked_cases and tracked_cases[element] then cat_name = tracked_cases[element] else cat_name = element end table.insert(self, string.format('[[Категория:%s]]', cat_name)) end end return categories end function tr.error(msg, categories, preview_only) local current_frame = mw.getCurrentFrame() local parent_frame = current_frame:getParent() local res_frame_title = parent_frame and parent_frame:getTitle() ~= current_title.prefixedText and parent_frame:getTitle() or current_frame:getTitle() if not preview_only or current_frame:preprocess('{{REVISIONID}}') == '' then mError = require('Module:Error') return mError.error{ tag = 'div', string.format('Ошибка в [[%s]]: %s.' .. (preview_only and '<br><small>Это сообщение показывается только во время предпросмотра.</small>' or ''), res_frame_title, msg) } .. categories else return categories end end function p.parse_link(frame) local args = get_args(frame) local link = args[1]:gsub('\n', '') local label link = mw.text.trim(link:match('^%[%[([^%]]+)%]%]$') or link) if link:sub(1, 1) == '/' then label = link link = current_title.prefixedText .. link end link = link:match(':?(.+)') if link:match('|') then link, label = link:match('^([^%|]+)%|(.+)$') end if not mw.title.new(link) then return nil, nil end return link, label end function p.format_link(frame) -- {{ссылка на раздел}} local args = get_args(frame) local link, section, label = args[1], args[2], args[3] if not link then link = current_title.prefixedText if section then link = '#' .. section label = label or '§ ' .. section end else local parsed_link, parsed_label = p.parse_link{link} if parsed_link then link = parsed_link else return link end if section and not link:match('#') then link = link .. '#' .. section if parsed_label then parsed_label = parsed_label .. '#' .. section end end label = (label or parsed_label or link):gsub('^([^#]-)#(.+)$', '%1 § %2') end if label and label ~= link then return string.format('[[:%s|%s]]', link, label) else return string.format('[[:%s]]', link) end end function p.remove_precision(frame) -- {{без уточнения}} local args = get_args(frame) local title = args[1] return title:match('^(.+)%s+%b()$') or title end function p.is_disambig(frame) local args = get_args(frame) local title = args[1] local page = mw.title.new(title) if not page or not page.exists or mw.title.equals(page, current_title) then return false end local page_content = page:getContent() local mw_list_content = mw.title.new('MediaWiki:Disambiguationspage'):getContent() local lang = mw.language.getContentLanguage() for template in mw.ustring.gmatch(mw_list_content, '%*%s?%[%[Шаблон:([^%]]+)') do if page_content:match('{{' .. template) or page_content:match('{{' .. lang:lcfirst(template)) then return true end end return false end function p.list(frame) local args = get_args(frame, {trim = false}) local list_sep = args.list_sep or args['разделитель списка'] or ', ' local last_list_sep = yesno(args.natural_join) ~= false and ' и ' or list_sep local links_ns = args.links_ns or args['ПИ ссылок'] local bold_links = yesno(args.bold_links or args['ссылки болдом']) local res_list = {} local tracked = { red_link = false, bad_format = false, formatted = false, unparsable_link = false } local i = 1 while args[i] do local link = args[i] local label = args['l' .. i] local element = '' if link:match('<span') then -- TODO: переписать tracked.formatted = true element = link -- for {{не переведено}} else local bad_format = (link:match('|') or link:match('[%[%]]')) ~= nil local parsed_link, parsed_label = p.parse_link{link} if parsed_link then tracked.bad_format = tracked.bad_format or bad_format if links_ns then parsed_label = parsed_label or parsed_link parsed_link = mw.site.namespaces[links_ns].name .. ':' .. parsed_link end local title = mw.title.new(parsed_link) tracked.red_link = tracked.red_link or not (title.isExternal or title.exists) element = p.format_link{parsed_link, nil, label or parsed_label} else tracked.unparsable_link = true element = link end end if bold_links then element = string.format('<b>%s</b>', element) end table.insert(res_list, element) i = i + 1 end return setmetatable(res_list, { __index = tracked, __tostring = function (self) return mw.text.listToText(self, list_sep, last_list_sep) end, __concat = concat, __pairs = function (self) return pairs(tracked) end }) end function p.hatnote(frame) local args = get_args(frame) local text = args[1] local id = args.id local extraclasses = args.extraclasses local hide_disambig = yesno(args.hide_disambig) local res = mw.html.create('div') :attr('id', id) :addClass('hatnote') :addClass('navigation-not-searchable') :addClass(extraclasses) :wikitext(text) if hide_disambig then res:addClass('dabhide') end return res end function p.main(frame, _tracking_categories) local args = get_args(frame, {trim = false}) local prefix = args.prefix or args['префикс'] local prefix_plural = args.prefix_plural or args['префикс мн. ч.'] local sep = args.sep or args['разделитель'] or ' ' local dot = yesno(args.dot or args['точка']) and '.' or '' local nocat = yesno(args.nocat) local preview_error = yesno(args.preview_error) local empty_list_message = args.empty_list_message or 'Не указано ни одной страницы' categories = tr.define_categories(index(_tracking_categories or {}, tracking_categories)) if not prefix then categories:add('no_prefix', nocat) return tr.error('Не указан префикс', categories) end if not args[1] then categories:add('no_links', nocat) return tr.error(empty_list_message, categories, preview_error) end if args[2] and prefix_plural then prefix = prefix_plural end local list = p.list(args) for k, v in pairs(list) do if type(v) == 'boolean' and v then categories:add(k, nocat) end end return p.hatnote(index({prefix .. sep .. list .. dot}, args)) .. categories end return index(p, tr)