Jump to content

Module:Annotated link: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
exclude single word descriptions where that word is part of the link name e.g. Knaster–Tarski theorem – Theorem
m applied coding conventions now I have to look at it less often
Line 1: Line 1:


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


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


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


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


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


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


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


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


local mLang = require( 'Module:Lang' )
local mLang = require('Module:Lang')
local function langify( args )
local function langify(args)
local lang = args.lang
local lang = args.lang
local text = args.text
local text = args.text
if isEmpty( lang ) or lang == 'en' then return text end
if isEmpty(lang) or lang == 'en' then
return text
end
return mLang._lang {
return mLang._lang {
lang,
lang,
Line 47: Line 52:
end
end


local function formatResult( result, dash, description )
local function formatResult(result, dash, description)
if notEmpty( description ) then return result .. dash .. ' ' .. description end
if notEmpty(description) then
return result..dash..' '..description
end
return result
return result
end
end


local function annotatedLink( args )
local function annotatedLink(args)
local name = args.name
local name = args.name
if isEmpty( name ) then return alarmingMessage( 'requires a page name (including namespace)' ) end
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>}};
-- 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.
-- unescape name to make sense of braces in lua patern matching.
name = unescape( name )
name = unescape(name)
if name:match( '^{%b{}}$' ) then
if name:match('^{%b{}}$') then
-- The possibility to extract useful data exists here: e.g. {{tl*|Template}}.
-- The possibility to extract useful data exists here: e.g. {{tl*|Template}}.
return alarmingMessage( 'requires only a page name (including namespace) without markup. ' ..
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"' )
'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
end
-- If a literal link was provided as name; extract the content and apply it to name and display as appropriate.
-- 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*%]%]$' )
local wikilink = mw.ustring.match(name, '^%[%[%s*:*%s*(.-)%s*%]%]$')
if wikilink then
if wikilink then
local link_name, link_display = unpack( mw.text.split( wikilink, '%s*|%s*' ) )
local link_name, link_display = unpack(mw.text.split(wikilink, '%s*|%s*'))
if link_name then name = link_name end
if link_name then
name = link_name
end
if link_display and isEmpty( args.display ) then args.display = link_display end
if link_display and isEmpty(args.display) then
args.display = link_display
end
end
end
Line 77: Line 94:
local result
local result
local is_template = name:match( '^Template:(.+)$' )
local is_template = name:match('^Template:(.+)$')
local template_link = args.template_link
local template_link = args.template_link
if is_template and template_link ~= 'no' then
if is_template and template_link ~= 'no' then
result = '{{' .. pipedLink( name, is_template ) .. '}}'
result = '{{'..pipedLink(name, is_template)..'}}'
if template_link == 'code' then
if template_link == 'code' then
result = '<code>' .. result .. '</code>'
result = '<code>'..result..'</code>'
end
end
else
else
local display = args.display
local display = args.display
if isEmpty( display ) then display = name end
if isEmpty(display) then
display = name
end
result = langify({
result = langify( {
lang = args.link_lang,
lang = args.link_lang,
text = pipedLink( name, display ),
text = pipedLink(name, display),
italic = args.link_lang_italic,
italic = args.link_lang_italic,
nocat = args.link_lang_nocat,
nocat = args.link_lang_nocat,
Line 96: Line 114:
cat = args.link_lang_cat,
cat = args.link_lang_cat,
rtl = args.link_lang_rtl
rtl = args.link_lang_rtl
} )
})
if notEmpty( args.quote ) then result = '"' .. result .. '"' end
if notEmpty(args.quote) then
result = '"'..result..'"'
end
local abbr = args.abbr
local abbr = args.abbr
if notEmpty( abbr ) then
if notEmpty(abbr) then
result = result .. ' (<abbr'
result = result..' (<abbr'
local abbr_title = args.abbr_title
local abbr_title = args.abbr_title
if notEmpty( abbr_title ) then result = result .. ' title="' .. abbr_title .. '"' end
if notEmpty(abbr_title) then
result = result..' title="'..abbr_title..'"'
end
result = result .. '>' .. abbr .. '</abbr>)'
result = result..'>'..abbr..'</abbr>)'
end
end
end
end
if isEmpty( result ) then return alarmingMessage( 'could not create a link for "' .. name .. '"' ) end
if isEmpty(result) then
return alarmingMessage('could not create a link for "'..name..'"')
end
local aka = args.aka
local aka = args.aka
if notEmpty( aka ) then
if notEmpty(aka) then
result = result .. ', also known as ' .. langify( {
result = result..', also known as '..langify({
lang = args.aka_lang,
lang = args.aka_lang,
text = aka,
text = aka,
Line 121: Line 145:
cat = args.aka_lang_cat,
cat = args.aka_lang_cat,
rtl = args.aka_lang_rtl
rtl = args.aka_lang_rtl
} )
})
end
end
local wedge = args.wedge
local wedge = args.wedge
if notEmpty( wedge ) then
if notEmpty(wedge) then
result = result .. ', ' .. langify( {
result = result..', '..langify({
lang = args.wedge_lang,
lang = args.wedge_lang,
text = wedge,
text = wedge,
Line 134: Line 158:
cat = args.wedge_lang_cat,
cat = args.wedge_lang_cat,
rtl = args.wedge_lang_rtl
rtl = args.wedge_lang_rtl
} )
})
end
end
-- Exclude wikidata fallback for any specified list of link titles, unless explicity instructed that it's okay.
-- 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
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
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
for only_explicit in hashDelimitedList(not_wikidata_for_links_starting_with) do
if name:match( '^' .. only_explicit ) then args.only = 'explicit' break end
if name:match('^'..only_explicit) then
args.only = 'explicit'
break
end
end
end
end
end
-- Get the short description from Module:GetShortDescription.
-- Get the short description from Module:GetShortDescription.
local short_description = require( 'Module:GetShortDescription' ).main( {
local short_description = require('Module:GetShortDescription').main({
none_is_valid = args.none_is_valid,
none_is_valid = args.none_is_valid,
none_is_nil = args.none_is_nil,
none_is_nil = args.none_is_nil,
lang_italic = args.desc_lang_italic,
lang_italic = args.desc_lang_italic,
lang_nocat = args.desc_lang_nocat,
lang_nocat = args.desc_lang_nocat,
Line 159: Line 186:
only = args.only,
only = args.only,
name = name
name = name
} )
})
local dash = args.dash
local dash = args.dash
if isEmpty( dash ) then dash = '&nbsp;–' end
if isEmpty(dash) then
dash = '&nbsp;–'
end


local fallback = args.fallback
local fallback = args.fallback


if isEmpty( short_description ) or short_description.redlink then return formatResult( result, dash, fallback ) end
if isEmpty(short_description) or short_description.redlink then
return formatResult(result, dash, fallback)
end
if short_description.alarm then return short_description.alarm end
if short_description.alarm then
return short_description.alarm
end
local maintenance = ''
local maintenance = ''
if short_description.redirected then
if short_description.redirected then
maintenance = optionallyVisibleCategory( 'category-annotation-with-redirected-description', 'Pages displaying short descriptions of redirect targets' )
maintenance = optionallyVisibleCategory(
'category-annotation-with-redirected-description',
'Pages displaying short descriptions of redirect targets')
end
end
Line 181: Line 216:
if short_description.fellback then
if short_description.fellback then
fellback = true
fellback = true
maintenance = maintenance .. optionallyVisibleCategory( 'category-wikidata-fallback-annotation', 'Pages displaying wikidata descriptions as a fallback' )
maintenance = maintenance..optionallyVisibleCategory(
'category-wikidata-fallback-annotation',
'Pages displaying wikidata descriptions as a fallback')
end
end
short_description = short_description.wikidata
short_description = short_description.wikidata
-- Filter against likely rubbish wikidata descriptions.
-- Filter against likely rubbish wikidata descriptions.
local not_wikidata_descriptions_including = args.not_wikidata_descriptions_including
local not_wikidata_descriptions_including = args.not_wikidata_descriptions_including
if notEmpty( not_wikidata_descriptions_including ) then
if notEmpty(not_wikidata_descriptions_including) then
-- Case insentive matching.
-- Case insentive matching.
local lower_case_short_description = short_description:lower()
local lower_case_short_description = short_description:lower()
for exclusion in hashDelimitedList( not_wikidata_descriptions_including:lower() ) do
for exclusion in hashDelimitedList(not_wikidata_descriptions_including:lower()) do
if lower_case_short_description:match( exclusion ) then short_description = '' break end
if lower_case_short_description:match(exclusion) then
short_description = ''
break
end
end
end
end
end
if isEmpty( short_description ) then return formatResult( result, dash, fallback ) end
if isEmpty(short_description) then
return formatResult(result, dash, fallback)
end
else
else short_description = short_description.explicit end
short_description = short_description.explicit
end
local lower_case_name = name:lower()
local lower_case_name = name:lower()
if notEmpty( short_description ) and not short_description:match( ' ' ) then
if notEmpty(short_description) and not short_description:match(' ') then
-- Filter against likely rubbish single word descriptions.
-- Filter against likely rubbish single word descriptions.
local lower_case_short_description = short_description:lower()
local lower_case_short_description = short_description:lower()
local not_single_word = args.not_single_word
local not_single_word = args.not_single_word
if notEmpty( not_single_word ) then
if notEmpty(not_single_word) then
-- Case insentive matching.
-- Case insentive matching.
for single_word in hashDelimitedList( not_single_word:lower() ) do
for single_word in hashDelimitedList(not_single_word:lower()) do
if single_word == lower_case_short_description then short_description = '' break end
if single_word == lower_case_short_description then
short_description = ''
break
end
end
end
end
end
if isEmpty( short_description ) or lower_case_name:match( lower_case_short_description ) then return formatResult( result, dash, fallback ) end
if isEmpty(short_description) or lower_case_name:match(lower_case_short_description) then
return formatResult(result, dash, fallback)
end
if isEmpty( args.space_cat ) then
if isEmpty(args.space_cat) then
maintenance = maintenance .. optionallyVisibleCategory( 'category-spaceless-annotation', 'Pages displaying short descriptions with no spaces' )
maintenance = maintenance..optionallyVisibleCategory(
'category-spaceless-annotation',
'Pages displaying short descriptions with no spaces')
end
end
end
end
if lower_case_name == short_description:lower() then
if lower_case_name == short_description:lower() then
if fellback then return formatResult( result, dash, fallback ) end
if fellback then
return formatResult(result, dash, fallback)
end
maintenance = maintenance .. optionallyVisibleCategory( 'category-annotation-matches-name', 'Pages displaying short descriptions matching their page name' )
maintenance = maintenance..optionallyVisibleCategory(
'category-annotation-matches-name',
'Pages displaying short descriptions matching their page name')
end
end
Line 226: Line 281:
local desc_first_letter_case = args.desc_first_letter_case
local desc_first_letter_case = args.desc_first_letter_case
if desc_first_letter_case == 'upper' or desc_first_letter_case == 'lower' then
if desc_first_letter_case == 'upper' or desc_first_letter_case == 'lower' then
short_description = handleFirstLetterCase( short_description, desc_first_letter_case )
short_description = handleFirstLetterCase(short_description, desc_first_letter_case)
end
end
return formatResult( result, dash, ( short_description or fallback ) .. maintenance )
return formatResult(result, dash, (short_description or fallback)..maintenance)
end
end


local p = {}
local p = {}


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



Revision as of 20:13, 15 February 2023

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)
	if notEmpty(description) then
		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)
	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)
		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)
		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)
		end
		maintenance = maintenance..optionallyVisibleCategory(
			'category-annotation-matches-name',
			'Pages displaying short descriptions matching their page name')
	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
	
	return formatResult(result, dash, (short_description or fallback)..maintenance)
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