Documentation for this module may be created at Module:Annotated link/doc


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:Annotated link]]'


end





local function optionallyVisibleCategory(class, category)


	return '<span style="display:none" class="'..class..'">'..category..


		'</span>[[Category:'..category..' via Module:Annotated link]]'


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 mLang = require('Module:Lang')


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 formatResult(result, dash, description, prefix_parentheses)


	if notEmpty(description) then


		if prefix_parentheses then


			local startIdx = description:find("%(")


			if startIdx then


				 local beforeParens = description:sub(1, startIdx - 2)


				 local insideParens = description:sub(startIdx, -1)


				 return result..' '..insideParens..dash..' '..beforeParens


			end


		end


		return result..dash..' '..description


	end


	return result


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 isEmpty(args.display) then


			args.display = link_display


		end


	end


	


	-- Prepare to concatenate.


	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


	


	-- Exclude wikidata fallback for any specified list of link titles,


	-- unless explicity instructed that it's okay.


	local not_wikidata_for_links_starting_with = args.not_wikidata_for_links_starting_with


	if isEmpty(args.wikidata) and notEmpty(not_wikidata_for_links_starting_with) then


		for only_explicit in hashDelimitedList(not_wikidata_for_links_starting_with) do


			if name:match('^'..only_explicit) then


				args.only = 'explicit'


				break


			end


		end


	end


	


	-- Get the short description from Module:GetShortDescription.


	local short_description = require('Module:GetShortDescription').main({


		none_is_valid = args.none_is_valid,


		none_is_nil = args.none_is_nil,


		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,


		prefer = args.prefer,


		only = args.only,


		name = name


	})


	


	local dash = args.dash


	if isEmpty(dash) then


		dash = '&nbsp;–'


	end





	local fallback = args.fallback





	if isEmpty(short_description) or short_description.redlink then


		return formatResult(result, dash, fallback, args.prefix_parentheses)


	end


	


	if short_description.alarm then


		return short_description.alarm


	end


	


	local maintenance = ''


	


	if short_description.redirected then


		maintenance = optionallyVisibleCategory(


			'category-annotation-with-redirected-description',


			'Pages displaying short descriptions of redirect targets')


	end


	


	local fellback


	


	if short_description.wikidata then


	--	if short_description.fellback then


	--		fellback = true


	--		maintenance = maintenance..optionallyVisibleCategory(


	--			'category-wikidata-fallback-annotation',


	--			'Pages displaying wikidata descriptions as a fallback')


	--	end


	--	short_description = short_description.wikidata


		-- Filter against likely rubbish wikidata descriptions.


	--	local not_wikidata_descriptions_including = args.not_wikidata_descriptions_including


	--	if notEmpty(not_wikidata_descriptions_including) then


			-- Case insentive matching.


	--		local lower_case_short_description = short_description:lower()


	--		for exclusion in hashDelimitedList(not_wikidata_descriptions_including:lower()) do


	--			if lower_case_short_description:match(exclusion) then


					short_description = ''


	--				break


	--			end


	--		end


	--	end


		if isEmpty(short_description) then


			return formatResult(result, dash, fallback, args.prefix_parentheses)


		end


	else


		short_description = short_description.explicit


	end


	


	local lower_case_name = name:lower()


	


	if notEmpty(short_description) and not short_description:match(' ') then


		-- Filter against likely rubbish single word descriptions.


		local lower_case_short_description = short_description:lower()


		local not_single_word = args.not_single_word


		if notEmpty(not_single_word) then


			-- Case insentive matching.


			for single_word in hashDelimitedList(not_single_word:lower()) do


				if single_word == lower_case_short_description then


					short_description = ''


					break


				end


			end


		end


		if isEmpty(short_description) or lower_case_name:match(lower_case_short_description) then


			return formatResult(result, dash, fallback, args.prefix_parentheses)


		end


		if isEmpty(args.space_cat) then


			maintenance = maintenance..optionallyVisibleCategory(


				'category-spaceless-annotation',


				'Pages displaying short descriptions with no spaces')


		end


	end


	


	if lower_case_name == short_description:lower() then


		if fellback then


			return formatResult(result, dash, fallback, args.prefix_parentheses)


		end


		maintenance = maintenance..optionallyVisibleCategory(


			'category-annotation-matches-name',


			'Pages displaying short descriptions matching their page name')


	end

	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


	


	return formatResult(result, dash, (short_description or fallback)..maintenance, args.prefix_parentheses)


end





local p = {}





function p.main(frame)


	local args = require('Module:Arguments' ).getArgs(frame)


	if isEmpty(args) then


		return alarmingMessage('could not getArgs') -- This really would be alarming.


	end


	return annotatedLink(args)


end





return p