Модуль:Hatnote

Материал из Народные Сказки

Модуль предназначен для реализации 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: при непустом и неложном значении ошибка выводится только во время предпросмотра.

Отслеживаемые случаи[править код]

Использование в других модулях[править код]

Некоторые функции используются в следующих модулях (проверить):


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 '§&nbsp;' .. 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 §&nbsp;%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)