Строка 1: |
Строка 1: |
− | --------------------------------------------------------------------------------
| + | local get_args = require('Module:Arguments').getArgs |
− | -- Module:Hatnote --
| + | local mError |
− | -- --
| + | local yesno = function (v) return require('Module:Yesno')(v, true) end |
− | -- This module produces hatnote links and links to related articles. It --
| |
− | -- implements the {{hatnote}} and {{format link}} meta-templates and includes --
| |
− | -- helper functions for other Lua hatnote modules. --
| |
− | --------------------------------------------------------------------------------
| |
| | | |
− | local libraryUtil = require('libraryUtil') | + | local p, tr = {}, {} |
− | local checkType = libraryUtil.checkType
| + | local current_title = mw.title.getCurrentTitle() |
− | local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg
| + | local tracking_categories = { |
− | local mArguments -- lazily initialise [[Module:Arguments]]
| + | no_prefix = 'Википедия:Страницы с модулем Hatnote без указания префикса', |
− | local yesno -- lazily initialise [[Module:Yesno]]
| + | no_links = 'Википедия:Страницы с модулем Hatnote без ссылок', |
− | local formatLink -- lazily initialise [[Module:Format link]] ._formatLink
| + | red_link = 'Википедия:Страницы с модулем Hatnote с красной ссылкой', |
| + | bad_format = 'Википедия:Страницы с модулем Hatnote с некорректно заполненными параметрами', |
| + | unparsable_link = 'Википедия:Страницы с модулем Hatnote с нечитаемой ссылкой', |
| + | formatted = 'Википедия:Страницы с модулем Hatnote с готовым форматированием', |
| + | } |
| | | |
− | local p = {} | + | local function index(t1, t2) |
− | | + | return setmetatable(t1, {__index = t2}) |
− | --------------------------------------------------------------------------------
| + | end |
− | -- Helper functions
| |
− | --------------------------------------------------------------------------------
| |
| | | |
− | local function getArgs(frame) | + | local function concat(e1, e2) |
− | -- Fetches the arguments from the parent frame. Whitespace is trimmed and | + | return tostring(e1) .. tostring(e2) |
− | -- blanks are removed.
| |
− | mArguments = require('Module:Arguments')
| |
− | return mArguments.getArgs(frame, {parentOnly = true})
| |
| end | | end |
| | | |
− | local function removeInitialColon(s)
| + | function tr.define_categories(tracked_cases) |
− | -- Removes the initial colon from a string, if present. | + | local categories = setmetatable({}, { |
− | return s:match('^:?(.*)')
| + | __tostring = function (self) return table.concat(self) end, |
− | end
| + | __concat = concat |
| + | }) |
| | | |
− | function p.defaultClasses(inline) | + | function categories:add(element, nocat) |
− | -- Provides the default hatnote classes as a space-separated string; useful
| + | if not nocat then |
− | -- for hatnote-manipulation modules like [[Module:Hatnote group]].
| + | local cat_name |
− | return
| + | if tracked_cases and tracked_cases[element] then |
− | (inline == 1 and 'hatnote-inline' or 'hatnote') .. ' ' ..
| + | cat_name = tracked_cases[element] |
− | 'navigation-not-searchable' | + | else |
| + | cat_name = element |
| + | end |
| + | table.insert(self, string.format('[[Категория:%s]]', cat_name)) |
| + | end |
| + | end |
| + | |
| + | return categories |
| end | | end |
| | | |
− | function p.disambiguate(page, disambiguator) | + | function tr.error(msg, categories, preview_only) |
− | -- Formats a page title with a disambiguation parenthetical, | + | local current_frame = mw.getCurrentFrame() |
− | -- i.e. "Example" → "Example (disambiguation)". | + | local parent_frame = current_frame:getParent() |
− | checkType('disambiguate', 1, page, 'string') | + | local res_frame_title = parent_frame and parent_frame:getTitle() ~= current_title.prefixedText and |
− | checkType('disambiguate', 2, disambiguator, 'string', true)
| + | parent_frame:getTitle() or |
− | disambiguator = disambiguator or 'disambiguation'
| + | current_frame:getTitle() |
− | return mw.ustring.format('%s (%s)', page, disambiguator)
| + | 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 | | end |
| | | |
− | function p.findNamespaceId(link, removeColon) | + | function p.parse_link(frame) |
− | -- Finds the namespace id (namespace number) of a link or a pagename. This | + | local args = get_args(frame) |
− | -- function will not work if the link is enclosed in double brackets. Colons | + | local link = args[1]:gsub('\n', '') |
− | -- are trimmed from the start of the link by default. To skip colon | + | local label |
− | -- trimming, set the removeColon parameter to false. | + | |
− | checkType('findNamespaceId', 1, link, 'string') | + | link = mw.text.trim(link:match('^%[%[([^%]]+)%]%]$') or link) |
− | checkType('findNamespaceId', 2, removeColon, 'boolean', true) | + | if link:sub(1, 1) == '/' then |
− | if removeColon ~= false then
| + | label = link |
− | link = removeInitialColon(link) | + | link = current_title.prefixedText .. link |
| end | | end |
− | local namespace = link:match('^(.-):') | + | link = link:match(':?(.+)') |
− | if namespace then | + | if link:match('|') then |
− | local nsTable = mw.site.namespaces[namespace] | + | link, label = link:match('^([^%|]+)%|(.+)$') |
− | if nsTable then
| + | end |
− | return nsTable.id
| + | |
− | end
| + | if not mw.title.new(link) then |
| + | return nil, nil |
| end | | end |
− | return 0 | + | |
| + | return link, label |
| end | | end |
| | | |
− | function p.makeWikitextError(msg, helpLink, addTrackingCategory, title) | + | function p.format_link(frame) |
− | -- Formats an error message to be returned to wikitext. If | + | -- {{ссылка на раздел}} |
− | -- addTrackingCategory is not false after being returned from | + | local args = get_args(frame) |
− | -- [[Module:Yesno]], and if we are not on a talk page, a tracking category | + | local link, section, label = args[1], args[2], args[3] |
− | -- is added.
| + | |
− | checkType('makeWikitextError', 1, msg, 'string')
| + | if not link then |
− | checkType('makeWikitextError', 2, helpLink, 'string', true)
| + | link = current_title.prefixedText |
− | yesno = require('Module:Yesno') | + | if section then |
− | title = title or mw.title.getCurrentTitle() | + | link = '#' .. section |
− | -- Make the help link text.
| + | label = label or '§ ' .. section |
− | local helpText
| + | end |
− | if helpLink then
| |
− | helpText = ' ([[' .. helpLink .. '|help]])'
| |
| else | | else |
− | helpText = '' | + | 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 | | end |
− | -- Make the category text. | + | |
− | local category
| + | if label and label ~= link then |
− | if not title.isTalkPage -- Don't categorise talk pages | + | return string.format('[[:%s|%s]]', link, label) |
− | and title.namespace ~= 2 -- Don't categorise userspace
| |
− | and yesno(addTrackingCategory) ~= false -- Allow opting out
| |
− | then
| |
− | category = 'Hatnote templates with errors' | |
− | category = mw.ustring.format(
| |
− | '[[%s:%s]]',
| |
− | mw.site.namespaces[14].name,
| |
− | category
| |
− | )
| |
| else | | else |
− | category = '' | + | return string.format('[[:%s]]', link) |
| end | | end |
− | return mw.ustring.format(
| |
− | '<strong class="error">Error: %s%s.</strong>%s',
| |
− | msg,
| |
− | helpText,
| |
− | category
| |
− | )
| |
| end | | end |
| | | |
− | local curNs = mw.title.getCurrentTitle().namespace | + | function p.remove_precision(frame) |
− | p.missingTargetCat =
| + | -- {{без уточнения}} |
− | --Default missing target category, exported for use in related modules | + | local args = get_args(frame) |
− | ((curNs == 0) or (curNs == 14)) and | + | local title = args[1] |
− | 'Articles with hatnote templates targeting a nonexistent page' or nil
| + | |
| + | return title:match('^(.+)%s+%b()$') or title |
| + | end |
| | | |
− | function p.quote(title) | + | function p.is_disambig(frame) |
− | --Wraps titles in quotation marks. If the title starts/ends with a quotation | + | local args = get_args(frame) |
− | --mark, kerns that side as with {{-'}} | + | local title = args[1] |
− | local quotationMarks = { | + | local page = mw.title.new(title) |
− | ["'"]=true, ['"']=true, ['“']=true, ["‘"]=true, ['”']=true, ["’"]=true
| + | |
− | }
| + | if not page or not page.exists or mw.title.equals(page, current_title) then |
− | local quoteLeft, quoteRight = -- Test if start/end are quotation marks | + | return false |
− | quotationMarks[string.sub(title, 1, 1)],
| + | end |
− | quotationMarks[string.sub(title, -1, -1)]
| + | |
− | if quoteLeft or quoteRight then
| + | local page_content = page:getContent() |
− | title = mw.html.create("span"):wikitext(title)
| + | 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 | | end |
− | if quoteLeft then title:css("padding-left", "0.15em") end
| + | return false |
− | if quoteRight then title:css("padding-right", "0.15em") end
| |
− | return '"' .. tostring(title) .. '"' | |
| end | | end |
| | | |
− | --------------------------------------------------------------------------------
| + | function p.list(frame) |
− | -- Hatnote
| + | local args = get_args(frame, {trim = false}) |
− | --
| + | local list_sep = args.list_sep or args['разделитель списка'] or ', ' |
− | -- Produces standard hatnote text. Implements the {{hatnote}} template.
| + | 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['ссылки болдом']) |
| | | |
− | function p.hatnote(frame)
| + | local res_list = {} |
− | local args = getArgs(frame) | + | local tracked = { |
− | local s = args[1]
| + | red_link = false, |
− | if not s then
| + | bad_format = false, |
− | return p.makeWikitextError( | + | formatted = false, |
− | 'no text specified', | + | unparsable_link = false |
− | 'Template:Hatnote#Errors', | + | } |
− | args.category | + | |
− | ) | + | 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 | | end |
− | return p._hatnote(s, { | + | |
− | extraclasses = args.extraclasses, | + | return setmetatable(res_list, { |
− | selfref = args.selfref | + | __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 | | end |
| | | |
− | function p._hatnote(s, options) | + | function p.hatnote(frame) |
− | checkType('_hatnote', 1, s, 'string') | + | local args = get_args(frame) |
− | checkType('_hatnote', 2, options, 'table', true) | + | local text = args[1] |
− | options = options or {} | + | local id = args.id |
− | local inline = options.inline | + | local extraclasses = args.extraclasses |
− | local hatnote = mw.html.create(inline == 1 and 'span' or 'div') | + | local hide_disambig = yesno(args.hide_disambig) |
− | local extraclasses | + | |
− | if type(options.extraclasses) == 'string' then | + | local res = mw.html.create('div') |
− | extraclasses = options.extraclasses
| + | :attr('id', id) |
| + | :addClass('hatnote') |
| + | :addClass('navigation-not-searchable') |
| + | :addClass(extraclasses) |
| + | :wikitext(text) |
| + | |
| + | if hide_disambig then |
| + | res:addClass('dabhide') |
| end | | end |
| + | |
| + | return res |
| + | end |
| | | |
− | hatnote | + | function p.main(frame, _tracking_categories) |
− | :attr('role', 'note')
| + | local args = get_args(frame, {trim = false}) |
− | :addClass(p.defaultClasses(inline))
| + | |
− | :addClass(extraclasses)
| + | local prefix = args.prefix or args['префикс'] |
− | :addClass(options.selfref and 'selfref' or nil)
| + | local prefix_plural = args.prefix_plural or args['префикс мн. ч.'] |
− | :wikitext(s)
| + | 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)) |
| | | |
− | return mw.getCurrentFrame():extensionTag{ | + | if not prefix then |
− | name = 'templatestyles', args = { src = 'Module:Hatnote/styles.css' } | + | categories:add('no_prefix', nocat) |
− | } .. tostring(hatnote) | + | 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 | | end |
| | | |
− | return p | + | return index(p, tr) |