Module:Labelled list hatnote: Difference between revisions

From NvWiki
Jump to navigation Jump to search
m 1 revision imported: Import modules used with Template:Infobox software
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
{{Permanently protected}}
--------------------------------------------------------------------------------
{{Old AfD multi |date1=2018 July 4 |result1='''No consensus''' |link1={{canonicalurl:Wikipedia:Templates for discussion/Log/2018 July 4#Module:About-distinguish}} |date2=2018 July 5 |result2='''No consensus''' |link2={{canonicalurl:Wikipedia:Templates for discussion/Log/2018 July 5#Module:Distinguish}}}}
--                              Labelled list                                --
--                                                                            --
-- This module does the core work of creating a hatnote composed of a list    --
-- prefixed by a colon-terminated label, i.e. "LABEL: [andList of pages]",    --
-- for {{see also}} and similar templates.                                    --
--------------------------------------------------------------------------------


== Proposal to add nowrap capabability ==
local mHatnote = require('Module:Hatnote')
local mHatlist = require('Module:Hatnote list')
local mArguments --initialize lazily
local yesno --initialize lazily
local p = {}


A proposal to add nowrap capability is given initially for {{tl|Further}}. There is a working template space mockup at [[Template:Further/sandbox]], and a demo of it on the Talk page. Please see discussion at [[Template:Further#Adding nowrap]]. Courtesy ping: [[User:Nihiltres]]. Thanks, [[User:Mathglot|Mathglot]] ([[User talk:Mathglot|talk]]) 06:06, 21 March 2022 (UTC)
-- Defaults global to this module
local defaults = {
label = 'See also', --Final fallback for label argument
labelForm = '%s: %s',
prefixes = {'label', 'label ', 'l'},
template = 'Module:Labelled list hatnote'
}


== Boolean problem ==
-- Localizable message strings
local msg = {
errorSuffix = '#Errors',
noInputWarning = 'no page names specified',
noOutputWarning =
"'''[[%s]] — no output: none of the target pages exist.'''"
}


{{ping|Nihiltres}} Recent edits changed selfref to a boolean. That has given a problem demonstrated at [[God the Father#Judaism]]:
-- Helper function that pre-combines display parameters into page arguments.
:Lua error in Module:Hatnote at line 146: bad argument #1 to 'addClass' (string, number or nil expected, got boolean).
-- Also compresses sparse arrays, as a desirable side-effect.
That is because line 146 of [[Module:Hatnote]] is
function p.preprocessDisplays (args, prefixes)
:<code>:addClass(options.selfref and 'selfref')</code>
-- Prefixes specify which parameters, in order, to check for display options
so if selfref is false, that is what is passed to addClass. Presumably it was nil before and addClass ignored that? [[User:Johnuniq|Johnuniq]] ([[User talk:Johnuniq|talk]]) 23:40, 5 June 2022 (UTC)
-- They each have numbers auto-appended, e.g. 'label1', 'label 1', & 'l1'
prefixes = prefixes or defaults.prefixes
local indices = {}
local sparsePages = {}
for k, v in pairs(args) do
if type(k) == 'number' then
indices[#indices + 1] = k
local display
for i = 1, #prefixes do
display = args[prefixes[i] .. k]
if display then break end
end
sparsePages[k] = display and
string.format('%s|%s', string.gsub(v, '|.*$', ''), display) or v
end
end
table.sort(indices)
local pages = {}
for k, v in ipairs(indices) do pages[#pages + 1] = sparsePages[v] end
return pages
end


:Yes, that was a fragile conditional in [[Module:Hatnote]]; I fixed it immediately in [[Special:Diff/1091749839|this edit]]. <span style="white-space:nowrap;">{&#123;[[User:Nihiltres|<span style="color:#233D7A;">Nihiltres</span>]]&#8202;&#124;[[User talk:Nihiltres|talk]]&#8202;&#124;[[Special:Contributions/Nihiltres|edits]]}&#125;</span> 04:19, 6 June 2022 (UTC)
--Helper function to get a page target from a processed page string
--e.g. "Page|Label" → "Page" or "Target" "Target"
local function getTarget(pagename)
local pipe = string.find(pagename, '|')
return string.sub(pagename, 0, pipe and pipe - 1 or nil)
end


== Template-protected edit request on November 8, 2023 ==
-- Produces a labelled pages-list hatnote.
-- The main frame (template definition) takes 1 or 2 arguments, for a singular
-- and (optionally) plural label respectively:
-- * {{#invoke:Labelled list hatnote|labelledList|Singular label|Plural label}}
-- The resulting template takes pagename & label parameters normally.
function p.labelledList (frame)
mArguments = require('Module:Arguments')
yesno = require('Module:Yesno')
local labels = {frame.args[1] or defaults.label}
labels[2] = frame.args[2] or labels[1]
labels[3] = frame.args[3] --no defaulting
labels[4] = frame.args[4] --no defaulting
local template = frame:getParent():getTitle()
local args = mArguments.getArgs(frame, {parentOnly = true})
local pages = p.preprocessDisplays(args)
local options = {
category = yesno(args.category),
extraclasses = frame.args.extraclasses,
ifexists = yesno(frame.args.ifexists),
namespace = frame.args.namespace or args.namespace,
selfref = yesno(frame.args.selfref or args.selfref),
template = template
}
return p._labelledList(pages, labels, options)
end
local function exists(title)
local success, result = pcall(function() return title.exists end)
if success then
return result
else
return true
end
end


{{edit template-protected|Module:Labelled list hatnote|answered=yes}}
function p._labelledList (pages, labels, options)
local removednonexist = false
if options.ifexists then
for k = #pages, 1, -1 do --iterate backwards to allow smooth removals
local v = pages[k]
if mw.ustring.sub(mw.text.trim(v), 1, 1) ~= "#" then
local title = mw.title.new(getTarget(v), namespace)
if (v == '') or (title == nil) or not exists(title) then
table.remove(pages, k)
removednonexist = true
end
end
end
end
labels = labels or {}
label = (#pages == 1 and labels[1] or labels[2]) or defaults.label
for k, v in pairs(pages) do
if mHatnote.findNamespaceId(v) ~= 0 then
label =
(
#pages == 1 and
(labels[3] or labels[1] or defaults.label) or
(labels[4] or labels[2] or defaults.label)
) or defaults.label
end
end
if #pages == 0 then
if removednonexist then
mw.addWarning(
string.format(
msg.noOutputWarning, options.template or defaults.template
)
)
return ''
else
return mHatnote.makeWikitextError(
msg.noInputWarning,
(options.template or defaults.template) .. msg.errorSuffix,
options.category
)
end
end
local text = string.format(
options.labelForm or defaults.labelForm,
label,
mHatlist.andList(pages, true)
)
local hnOptions = {
extraclasses = options.extraclasses,
selfref = options.selfref
}
return mHatnote._hatnote(text, hnOptions)
end


This module seems to be causing a comma to unnecessarily appear in lists consisting of two items when the first item is a section link. For example:
return p
{{tq2|{{Further|Example|Example}}}}
compared to
{{tq2|{{Further|Example#Example|Example}}}}
I would provide a more detailed fix in this request if I knew what I was going with Lua, but I think I'll defer that part to someone more experienced at it than me. Thanks! <span class="nowrap">—[[User:TechnoSquirrel69|TechnoSquirrel69]]</span> ([[User talk:TechnoSquirrel69|sigh]]) 08:01, 8 November 2023 (UTC)
 
:I think this adjustment is on line number 68 of [[Module:Hatnote list]] (one of this module's dependencies). The comment on line number 65 suggests this.
:<syntaxhighlight lang="lua"> -- Set the conjunction, apply Oxford comma, and force a comma if #1 has "§"
: local conjunction = s .. options.conjunction .. s
: if #list == 2 and searchDisp(list[1], "§") or #list > 2 then
: conjunction = separator .. conjunction
: end</syntaxhighlight> [[User:Gkiyoshinishimoto|Nishimoto, Gilberto Kiyoshi]] ([[User talk:Gkiyoshinishimoto|talk]]) 12:30, 8 November 2023 (UTC)
::That looks like a deliberate programming choice. Also, the documentation for that module says that it is used in over 1 million pages, so any changes would need to be tested thoroughly. – [[User:Jonesey95|Jonesey95]] ([[User talk:Jonesey95|talk]]) 01:06, 9 November 2023 (UTC)
:::Since it seems now seems to be intentional, does anyone know of any particular reason why the comma is programmed to appear after a section link? It's not immediately obvious to me what the point of that is. <span class="nowrap">—[[User:TechnoSquirrel69|TechnoSquirrel69]]</span> ([[User talk:TechnoSquirrel69|sigh]]) 01:12, 9 November 2023 (UTC)
::::accessibility, maybe... [[User:Gkiyoshinishimoto|Nishimoto, Gilberto Kiyoshi]] ([[User talk:Gkiyoshinishimoto|talk]]) 01:37, 9 November 2023 (UTC)
:::I just tried to indicate, to the editor of the previous message, the point where I think the issue he raised lies. Although grammar rules vary between languages, this comma helps me discern where 2 (page and section) are 1 (link). So I'm not opposing it. I'm training in editing, to adapt enwiki modules to ptwiki, and I'm really enjoying the 'self-taught' learning. The English language is not my native language, I depend on web translators to understand and communicate with you, so forgive me if I make some mistakes (such mistakes are not intentional). Grateful for the attention. [[User:Gkiyoshinishimoto|Nishimoto, Gilberto Kiyoshi]] ([[User talk:Gkiyoshinishimoto|talk]]) 01:31, 9 November 2023 (UTC)
: {{ping|Nihiltres}} [[User:Pppery|* Pppery *]] [[User talk:Pppery|<sub style="color:#800000">it has begun...</sub>]] 18:20, 10 November 2023 (UTC)
::[[File:Red information icon with gradient background.svg|20px|link=|alt=]] '''Not done:'''<!-- Template:ESp --> the relevant code is not in this module but in [[Module:Hatnote list]], and it is very intentional, so we should probably establish a proper consensus, on the proper talk page, before changing it, not least because Module:Hatnote&nbsp;list is transcluded to a ''very'' large number of pages. For context, the idea was originally suggested by {{u|SMcCandlish}} as "easier to read" at <span class="plainlinks">[https://en.wikipedia.org/w/index.php?oldid=718131129#Oxford_Comma? Template talk:See also §&nbsp;Oxford Comma?]</span> and then echoed by {{u|Andy M. Wang}} at [[Wikipedia talk:Hatnote/Archive 6#Standardizing_for-see_lists|Wikipedia talk:Hatnote §&nbsp;Standardizing for-see lists ''(since archived)'']], where I (who was doing the mentioned standardization) saw it, thought it was a nice idea, and implemented it. I wouldn't terribly mind changing this behaviour, but I ''am'' going to ask for a consensus first. <span style="white-space:nowrap;">{&#123;[[User:Nihiltres|<span style="color:#233D7A;">Nihiltres</span>]]&#8202;&#124;[[User talk:Nihiltres|talk]]&#8202;&#124;[[Special:Contributions/Nihiltres|edits]]}&#125;</span> 00:09, 11 November 2023 (UTC)
:::If we're going to discuss this here, then: I would be opposed dropping the comma. It helps distinguish the two referents when one or both of them is complicated by the presence of a § segment; it is similar to use of a semicolon instead of a comma between clauses when at least one of them contains its own comma(s). The "Further information: Example1 § Example2 and Example3" format the OP wants would be difficult to parse correctly for many readers, especially those with visual impairments like colorblindess marking it harder to discern which part is a link and which is not. It looks like it means "sections Example2 and Example3 of page Example1". This is less of an issue when page names start with something like "Wikipedia:", but it is a clear problem when dealing with articles and their section names. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 00:26, 11 November 2023 (UTC)
::::I will note that it's pretty standard to use two section symbols when referring to multiple sections of the same document; in other words, there's a meaningful difference between ''Example § Example and Example'' and ''Example §§ Example and Example''. I don't think this double section symbol format is used anywhere on Wikipedia, but I don't personally see an ambiguity being introduced if the comma was removed, even without the link colors. <span class="nowrap">—[[User:TechnoSquirrel69|TechnoSquirrel69]]</span> ([[User talk:TechnoSquirrel69|sigh]]) 01:33, 11 November 2023 (UTC)
::::Also, this feels like the kind of thing that should be covered somewhere in like a 500-page style guide. I have no idea where to start looking for such a research, though. <span class="nowrap">—[[User:TechnoSquirrel69|TechnoSquirrel69]]</span> ([[User talk:TechnoSquirrel69|sigh]]) 01:38, 11 November 2023 (UTC)
:::::And it's not a "standard" at all, but a style choice, and one which is contradicted by another in which §§ means "sub-section" (and §§§ means "sub-sub-section"). Both conventions are obscure and even if one could be proven to dominate among the both-obscure practices, we certainly cannot depend upon our general readership to understand it. Even use of § at all is a bit dubious. But none of this is actually responsive to my point, which has nothing to do with whether someone who uses § all the time can figure out what the meaning is. It's about visual scannability of this template's more complex output, especially for people with vision issues; these are usability and accessibility points, not a semantic one. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 14:06, 23 December 2024 (UTC)
:::Right and fair.
:::I was thinking about making an alternative version, which follows the grammar rule contextualized here, in a ptwiki testing area. I think commenting on lines 72 and 73 there, which are equivalent to lines 67 and 68 here, would be enough. The main module editor resolved the issue by removing <syntaxhighlight lang="lua">"or #list > 2"</syntaxhighlight> on the line there which is equivalent to line 67 here (the question, on ptwiki, was related to 3 or more items in a list and the conjunction with or without separator/comma).
:::But after reading, quickly and superficially, the section "[[Serial comma#Ambiguity|Ambiguity]]" of the article [[Serial comma]] (and its respective version in Portuguese), it seems to me that we have good reasons for the choices we made. Until last week, I still thought that its usage was not in line with grammar rules. I'm glad to know that the serial comma is no longer 'illegal'. I would try such a modification, just to test whether my current understanding (when it comes to code editing) is consistent.
:::Thanks again for the classes. [[User:Gkiyoshinishimoto|Nishimoto, Gilberto Kiyoshi]] ([[User talk:Gkiyoshinishimoto|talk]]) 21:47, 11 November 2023 (UTC)
::::Where did you get the idea that the serial comma was somehow forbidden? Any style guide that is relevant to our writing (versus, say, news journalism) recommends use of serial commas any time they improve clarity, and some thus also recommend them as a general practice for consistency, since it can be jarring to have material veer back and forth between the two styles for reasons that not be very apparent except to the author. <span style="white-space:nowrap;font-family:'Trebuchet MS'"> — [[User:SMcCandlish|'''SMcCandlish''']] [[User talk:SMcCandlish|☏]] [[Special:Contributions/SMcCandlish|¢]] 😼 </span> 14:06, 23 December 2024 (UTC)
 
== Related discussion at Template talk:Further ==
 
Editors here may be interested in the discussion at [[Template talk:Further#Possible bug in error handling related to module-emitted error message]]. Thanks, [[User:Mathglot|Mathglot]] ([[User talk:Mathglot|talk]]) 21:42, 27 October 2025 (UTC)

Latest revision as of 16:57, 26 December 2025

Template:Module rating Template:Used in system

This module provides a handful of functions that make it easy to implement hatnotes that take the form of a label in front of a list of pages, e.g.

Usage

labelledList

Invoking the labelledList() function is enough to implement most such templates:

{{#invoke:Labelled list hatnote|labelledList|Universal label}}

or

{{#invoke:Labelled list hatnote|labelledList|Singular label|Plural label}}

For example, providing "See also" instead of "Universal label" duplicates the functionality of {{see also}}, while providing "Main article" and "Main articles" instead of "Singular label" and "Plural label" duplicates the (article namespace) functionality of {{main}}.

If third and fourth labels are provided, they'll be used in the case where any of the target pages are outside the article namespace, so e.g. {{main}} can be implemented thus:

{{#invoke:Labelled list hatnote|labelledList|Main article|Main articles|Main page|Main pages}}

preprocessDisplays

The preprocessDisplays() function takes a raw list of arguments and combines in any display arguments. For example, {{see also|1|l1=One}} initially has the arguments table {'1', ['l1'] = 'One'}; this table would combine those into the table {'1|One'}. It overrides manual piping (e.g. {{see also|1{{!}}2|l1=One}}{'1|One'}) and compresses sparse arrays if a parameter is skipped or left empty.

Example:

local mLabelledList = require('Module:Labelled list hatnote')
local pages = mLabelledList.preprocessDisplays(args)

_labelledList

For modules that need to modify the functionality slightly while still using it, _labelledList() provides some flexibility. It takes three parameters:

  1. A pages list, preferably preprocessed and compressed by preprocessDisplays
  2. A labels table, where the first item is the singular or universal label, and the second either a plural label or a copy of the first.
  3. An options table, preferably containing:
    • a template string with the full title of the template. Defaults to the title of this module.
    • a category string (or nil) as taken by makeWikitextError from Module:Hatnote, to optionally disable error categories
    • a selfref string (or nil) as taken by _hatnote to enable the selfref option

Example:

local mLabelledList = require('Module:Labelled list hatnote')
return mLabelledList._labelledList(pages, labels, options)

Errors

Template:See also This module causes templates based on it to produce an error message if no page names are provided as template parameters. Normally, these should lead back to "Errors" sections in the documentation of those templates. However, if those templates use a module with _labelledList() and don't provide a template item in their options table, that error defaults to leading back here. The error can be solved by providing at least one valid page-name parameter to the template in question; the problem in the template can be fixed by providing some value to the template item of the _labelledList() options table.


--------------------------------------------------------------------------------
--                               Labelled list                                --
--                                                                            --
-- This module does the core work of creating a hatnote composed of a list    --
-- prefixed by a colon-terminated label, i.e. "LABEL: [andList of pages]",    --
-- for {{see also}} and similar templates.                                    --
--------------------------------------------------------------------------------

local mHatnote = require('Module:Hatnote')
local mHatlist = require('Module:Hatnote list')
local mArguments --initialize lazily
local yesno --initialize lazily
local p = {}

-- Defaults global to this module
local defaults = {
	label = 'See also', --Final fallback for label argument
	labelForm = '%s: %s',
	prefixes = {'label', 'label ', 'l'},
	template = 'Module:Labelled list hatnote'
}

-- Localizable message strings
local msg = {
	errorSuffix = '#Errors',
	noInputWarning = 'no page names specified',
	noOutputWarning =
		"'''[[%s]] — no output: none of the target pages exist.'''"
}

-- Helper function that pre-combines display parameters into page arguments.
-- Also compresses sparse arrays, as a desirable side-effect.
function p.preprocessDisplays (args, prefixes)
	-- Prefixes specify which parameters, in order, to check for display options
	-- They each have numbers auto-appended, e.g. 'label1', 'label 1', & 'l1'
	prefixes = prefixes or defaults.prefixes
	local indices = {}
	local sparsePages = {}
	for k, v in pairs(args) do
		if type(k) == 'number' then
			indices[#indices + 1] = k
			local display
			for i = 1, #prefixes do
				display = args[prefixes[i] .. k]
				if display then break end
			end
			sparsePages[k] = display and
				string.format('%s|%s', string.gsub(v, '|.*$', ''), display) or v
		end
	end
	table.sort(indices)
	local pages = {}
	for k, v in ipairs(indices) do pages[#pages + 1] = sparsePages[v] end
	return pages
end

--Helper function to get a page target from a processed page string
--e.g. "Page|Label" → "Page" or "Target" → "Target"
local function getTarget(pagename)
 	local pipe = string.find(pagename, '|')
	return string.sub(pagename, 0, pipe and pipe - 1 or nil)
end

-- Produces a labelled pages-list hatnote.
-- The main frame (template definition) takes 1 or 2 arguments, for a singular
-- and (optionally) plural label respectively:
-- * {{#invoke:Labelled list hatnote|labelledList|Singular label|Plural label}}
-- The resulting template takes pagename & label parameters normally.
function p.labelledList (frame)
	mArguments = require('Module:Arguments')
	yesno = require('Module:Yesno')
	local labels = {frame.args[1] or defaults.label}
	labels[2] = frame.args[2] or labels[1]
	labels[3] = frame.args[3] --no defaulting
	labels[4] = frame.args[4] --no defaulting
	local template = frame:getParent():getTitle()
	local args = mArguments.getArgs(frame, {parentOnly = true})
	local pages = p.preprocessDisplays(args)
	local options = {
		category = yesno(args.category),
		extraclasses = frame.args.extraclasses,
		ifexists = yesno(frame.args.ifexists),
		namespace = frame.args.namespace or args.namespace,
		selfref = yesno(frame.args.selfref or args.selfref),
		template = template
	}
	return p._labelledList(pages, labels, options)
end
local function exists(title)
	local success, result = pcall(function() return title.exists end)
	if success then
		return result
	else
		return true
	end
end

function p._labelledList (pages, labels, options)
	local removednonexist = false
	if options.ifexists then
		for k = #pages, 1, -1 do --iterate backwards to allow smooth removals
			local v = pages[k]
			if mw.ustring.sub(mw.text.trim(v), 1, 1) ~= "#" then
				local title = mw.title.new(getTarget(v), namespace)
				if (v == '') or (title == nil) or not exists(title) then
					table.remove(pages, k)
					removednonexist = true
				end
			end
		end
	end
	labels = labels or {}
	label = (#pages == 1 and labels[1] or labels[2]) or defaults.label
	for k, v in pairs(pages) do 
		if mHatnote.findNamespaceId(v) ~= 0 then
			label =
				(
					#pages == 1 and
					(labels[3] or labels[1] or defaults.label) or
					(labels[4] or labels[2] or defaults.label)
				) or defaults.label
		end
	end
	if #pages == 0 then
		if removednonexist then
			mw.addWarning(
				string.format(
					msg.noOutputWarning, options.template or defaults.template
				)
			)
			return ''
		else
			return mHatnote.makeWikitextError(
				msg.noInputWarning,
				(options.template or defaults.template) .. msg.errorSuffix,
				options.category
			)
		end
	end
	local text = string.format(
		options.labelForm or defaults.labelForm,
		label,
		mHatlist.andList(pages, true)
	)
	local hnOptions = {
		extraclasses = options.extraclasses,
		selfref = options.selfref
	}
	return mHatnote._hatnote(text, hnOptions)
end

return p