Jump to content

Module:Annotated link

Permanently protected module
From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Fred Gandt (talk | contribs) at 19:31, 6 February 2023 (+ optionally visible maintenance categorisation of pages displaying wikidata descriptions as a fallback). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.

local getShortDescription = require( 'Module:GetShortDescription' ).main
local getArgs = require( 'Module:Arguments' ).getArgs
local mLang = require( 'Module:Lang' )

local function pipedLink( name, display ) return '[[:' .. name ..  '|' .. display .. ']]' end

local function isEmpty( value ) return value == nil or value == '' end

local function notEmpty( value ) return not isEmpty( value ) end

-- Unescape functionality grabbed from https://stackoverflow.com/a/14899740/1832568
local function unescape( str )
	str = string.gsub( str, '&#(%d+);', string.char )
	str = string.gsub( str, '&#x(%d+);', function( d ) return string.char( tonumber( d, 16 ) ) end )
	return str
end

local function hashDelimitedList( list_string ) return mw.text.gsplit( unescape( list_string ), '%s*#%s*' ) end

local function alarmingMessage( message )
	return '<span style="color:#d33">[[Module:Annotated link]] ' .. message .. '.</span>'
		.. '[[Category:Pages displaying alarming messages about Module:AnnotatedLink]]'
end

local function optionallyVisibleCategory( class, category )
	return '<span style="display:none" class="' .. class .. '">' .. category .. '</span>[[Category:' .. category .. ']]'
end

local function handleFirstLetterCase( short_description, case )
	return mw.ustring.gsub( short_description, '^([^%d])', function( first_char )
		if case == 'upper' then return mw.ustring.upper( first_char ) end
		return mw.ustring.lower( first_char ) end
	)
end

local function langify( args )
	local lang = args.lang
	local text = args.text
	if isEmpty( lang ) or lang == 'en' then return text end
	return mLang._lang {
		lang,
		text,
		italic = args.italic,
		nocat = args.nocat,
		size = args.size,
		cat = args.cat,
		rtl = args.rtl
	}
end

local function annotatedLink( args )
	local name = args.name
	if isEmpty( name ) then return alarmingMessage( 'requires a page name (including namespace)' ) end
	
	-- In order to handle an attempt to annotate a template link,
	-- already formatted with the likes of {{tl|<template name>}};
	-- unescape name to make sense of braces in lua patern matching.
	name = unescape( name )
	
	if name:match( '^{%b{}}$' ) then
		-- The possibility to extract useful data exists here: e.g. {{tl*|Template}}.
		return alarmingMessage( 'requires only a page name (including namespace) without markup. ' ..
			'If an attempt is being made to annotate a link to a template, provide only the template name with namespace e.g. "Template:Example"'
		)
	end
	
	-- If a literal link was provided as name; extract the content and apply it to name and display as appropriate.
	local wikilink = mw.ustring.match( name, '^%[%[%s*:*%s*(.-)%s*%]%]$' )
	if wikilink then
		local link_name, link_display = unpack( mw.text.split( wikilink, '%s*|%s*' ) )
		if link_name then name = link_name end
		if link_display and not args.display then args.display = link_display end
	end
	
	-- Exclude wikidata fallback for any specified list of link titles, unless explicity instructed that it's okay.
	local not_wikidata = args.not_wikidata
	if isEmpty( args.wikidata ) and notEmpty( not_wikidata ) then
		for only_explicit in hashDelimitedList( not_wikidata ) do
			if name:match( '^' .. only_explicit ) then args.only = 'explicit' break end
		end
	end
	
	-- Get the short description from Module:GetShortDescription.
	local short_description = getShortDescription( {
		objectify_alarm = true,
		report_redlinks = true,
		lang_italic = args.desc_lang_italic,
		lang_nocat = args.desc_lang_nocat,
		lang_size = args.desc_lang_size,
		lang_cat = args.desc_lang_cat,
		lang_rtl = args.desc_lang_rtl,
		lang_no = args.desc_lang_no,
		fallback = args.fallback,
		prefer = args.prefer,
		only = args.only,
		name = name
	} )
	
	local redlink
	local wikidata
	if type( short_description ) == 'table' then
		if short_description.alarm then return short_description.alarm end
		redlink = short_description.redlink
		wikidata = short_description.wikidata
		if wikidata then short_description = wikidata end
	end
	
	local result
	
	local is_template = name:match( '^Template:(.+)$' )
	local template_link = args.template_link
	if is_template and template_link ~= 'no' then
		result = '{{' .. pipedLink( name, is_template ) .. '}}'
		if template_link == 'code' then
			result = '<code>' .. result .. '</code>'
		end
	else
		local display = args.display
		if isEmpty( display ) then display = name end
		
		result = langify( {
			lang = args.link_lang,
			text = pipedLink( name, display ),
			italic = args.link_lang_italic,
			nocat = args.link_lang_nocat,
			size = args.link_lang_size,
			cat = args.link_lang_cat,
			rtl = args.link_lang_rtl
		} )
		
		if notEmpty( args.quote ) then result = '"' .. result .. '"' end
		
		local abbr = args.abbr
		if notEmpty( abbr ) then
			result = result .. ' (<abbr'
			local abbr_title = args.abbr_title
			if notEmpty( abbr_title ) then result = result .. ' title="' .. abbr_title .. '"' end
			result = result .. '>' .. abbr .. '</abbr>)'
		end
	end
	
	if isEmpty( result ) then return alarmingMessage( 'could not create a link for "' .. name .. '"' ) end
	
	local aka = args.aka
	if notEmpty( aka ) then
		result = result .. ', also known as ' .. langify( {
			lang = args.aka_lang,
			text = aka,
			italic = args.aka_lang_italic,
			nocat = args.aka_lang_nocat,
			size = args.aka_lang_size,
			cat = args.aka_lang_cat,
			rtl = args.aka_lang_rtl
		} )
	end
	
	local wedge = args.wedge
	if notEmpty( wedge ) then
		result = result .. ', ' .. langify( {
			lang = args.wedge_lang,
			text = wedge,
			italic = args.wedge_lang_italic,
			nocat = args.wedge_lang_nocat,
			size = args.wedge_lang_size,
			cat = args.wedge_lang_cat,
			rtl = args.wedge_lang_rtl
		} )
	end

	if isEmpty( short_description ) then return result end
	
	-- If Module:GetShortDescription indicates that name is for a nonexistent page; redlink will be true and there will be no short_description.
	if redlink then
		
		-- red_cat_no may be provided as a '#' delimited list of titles to not categorise;
		-- the titles may be incomplete prefixes e.g. 'List of'.
		local red_cat_no = args.red_cat_no
		local current_title = mw.title.getCurrentTitle().prefixedText
		-- If instructed to not categorise pages matching the current title:
		if current_title and notEmpty( red_cat_no ) then
			for no_cat in hashDelimitedList( red_cat_no ) do
				if current_title:match( '^' .. no_cat ) then args.red_cat = 'no' break end
			end
		end
		
		-- If the invocation has not stated a value for red_cat; return the annotation we have thusfar with an appended maintenance category.
		if isEmpty( args.red_cat ) then
			return result .. optionallyVisibleCategory( 'category-annotated-redlinks', 'Pages displaying redlinks processed by Module:AnnotatedLink' )
		end
		return result
		
	end
	
-- Short descriptions on en Wikipedia should be formatted with an uppercase first letter, but
-- the typical application of this module will require the first character to be lowercase, but
-- some descriptions may start with proper names and should start with an uppercase letter even if used in an annotaion.
-- By default; this module will not affect the first letter case of descriptions retrieved by Module:GetShortDescription, but
-- the first letter case may be transformed explicitly if required.
	local desc_first_letter_case = args.desc_first_letter_case
	if desc_first_letter_case == 'upper' or desc_first_letter_case == 'lower' then
		short_description = handleFirstLetterCase( short_description, desc_first_letter_case )
	end
	
	if wikidata then
		short_description = short_description .. optionallyVisibleCategory(
			'category-wikidata-fallback-annotation', 'Pages displaying wikidata descriptions as a fallback via Module:Annotated link'
		)
	end
	
	if isEmpty( args.space_cat ) and not short_description:match( ' ' ) then
		short_description = short_description .. '[[Category:Pages displaying short descriptions with no spaces via Module:AnnotatedLink]]'
	end
	
	local dash = args.dash
	if isEmpty( dash ) then dash = '&nbsp;–' end
	
	return result .. dash .. ' ' .. short_description
end

local p = {}

function p.main( frame )
	local args = getArgs( frame )
	if isEmpty( args ) then return alarmingMessage( 'could not getArgs' ) end -- This really would be alarming
	return annotatedLink( args )
end

return p