Jump to content

Module:Hatnote/sandbox

From Wikipedia, the free encyclopedia
This is an old revision of this page, as edited by Nihiltres (talk | contribs) at 14:58, 7 April 2016 (Some tweaks to the forSee stuff to make it more usable in practice). The present address (URL) is a permanent link to this revision, which may differ significantly from the current revision.
--------------------------------------------------------------------------------
--                              Module:Hatnote                                --
--                                                                            --
-- 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 checkType = libraryUtil.checkType
local mArguments -- lazily initialise [[Module:Arguments]]
local yesno -- lazily initialise [[Module:Yesno]]

local p = {}

--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------

local function getArgs(frame)
	-- Fetches the arguments from the parent frame. Whitespace is trimmed and
	-- blanks are removed.
	mArguments = require('Module:Arguments')
	return mArguments.getArgs(frame, {parentOnly = true})
end

local function removeInitialColon(s)
	-- Removes the initial colon from a string, if present.
	return s:match('^:?(.*)')
end

function p.findNamespaceId(link, removeColon)
	-- Finds the namespace id (namespace number) of a link or a pagename. This
	-- function will not work if the link is enclosed in double brackets. Colons
	-- are trimmed from the start of the link by default. To skip colon
	-- trimming, set the removeColon parameter to false.
	checkType('findNamespaceId', 1, link, 'string')
	checkType('findNamespaceId', 2, removeColon, 'boolean', true)
	if removeColon ~= false then
		link = removeInitialColon(link)
	end
	local namespace = link:match('^(.-):')
	if namespace then
		local nsTable = mw.site.namespaces[namespace]
		if nsTable then
			return nsTable.id
		end
	end
	return 0
end

function p.formatPages(...)
	-- Formats a list of pages using formatLink and returns it as an array. Nil
	-- values are not allowed.
	local pages = {...}
	local ret = {}
	for i, page in ipairs(pages) do
		ret[i] = p._formatLink(page)
	end
	return ret
end

function p.formatPageTables(...)
	-- Takes a list of page/display tables and returns it as a list of
	-- formatted links. Nil values are not allowed.
	local pages = {...}
	local links = {}
	for i, t in ipairs(pages) do
		checkType('formatPageTables', i, t, 'table')
		local link = t[1]
		local display = t[2]
		links[i] = p._formatLink(link, display)
	end
	return links
end

function p.makeWikitextError(msg, helpLink, addTrackingCategory, title)
	-- Formats an error message to be returned to wikitext. If
	-- addTrackingCategory is not false after being returned from
	-- [[Module:Yesno]], and if we are not on a talk page, a tracking category
	-- is added.
	checkType('makeWikitextError', 1, msg, 'string')
	checkType('makeWikitextError', 2, helpLink, 'string', true)
	yesno = require('Module:Yesno')
	title = title or mw.title.getCurrentTitle()
	-- Make the help link text.
	local helpText
	if helpLink then
		helpText = ' ([[' .. helpLink .. '|help]])'
	else
		helpText = ''
	end
	-- Make the category text.
	local category
	if not title.isTalkPage and yesno(addTrackingCategory) ~= false then
		category = 'Hatnote templates with errors'
		category = string.format(
			'[[%s:%s]]',
			mw.site.namespaces[14].name,
			category
		)
	else
		category = ''
	end
	return string.format(
		'<strong class="error">Error: %s%s.</strong>%s',
		msg,
		helpText,
		category
	)
end

--------------------------------------------------------------------------------
-- For see
--
-- Makes a "For X, see [[Y]]." list from raw parameters. Intended for the
-- {{about}} and {{redirect}} templates and their variants. Also incidentally
-- introduces andList & orList helpers, which will probably see more use later.
--------------------------------------------------------------------------------

function p.andList (andTable)
	-- Stringifies a list with "and"
	local andString = (#andTable > 2 and mw.text.listToText(andTable, nil, ', and ')) or
		mw.text.listToText(andTable)
	return andString
end

function p.orList (orTable)
	-- Stringifies a list with "or"
	local orString = (#andTable > 2 and mw.text.listToText(andTable, nil, ', or ')) or
		mw.text.listToText(andTable, nil, ' or ')
	return orString
end

function p.forSee (frame, from, options)
	-- Calls _forSee but pulls from the frame.
	local args = getArgs(frame)
	return p._forSee(args, from, options)
end

function p._forSee (args, from, options)
	-- Produces a "For X, see [[Y]]" string from arguments. Expects index gaps
	-- but not blank or whitespace values; those should be filtered. Ignores
	-- arguments less than "from", and named arguments.

	--type-checks and defaults
	checkType("_forSee", 1, args, 'table')
	checkType("_forSee", 2, from, 'number', true)
	from = from or 1
	checkType("_forSee", 3, options, 'table', true)
	options = options or {}
	local defaultOptions = {
		title = mw.title.getCurrentTitle().text,
		disambiguator = ' (disambiguation)',
		otherText = 'other uses'
	}
	for k, v in pairs(defaultOptions) do
		if options[k] == nil then options[k] = v end
	end

	--maxArg's gotten manually because getArgs() and table.maxn aren't friends
	local maxArg = 0
	for k, v in pairs(args) do
		if type(k) == 'number' and k > maxArg then maxArg = k end
	end

	--Structure the data out from the parameter list
	--forTable is the wrapper table, with forRow rows
	local forTable = {}
	local i = from
	local terminated = false
	--repeat to generate each forsee row
	repeat
		--new empty row
		local forRow = {}
		--if there's a blank use, assume the list's ended, use the default,
		--and break at the end of this loop-through.
		if args[i] then
			forRow.use = args[i]
		else 
			forRow.use = options.otherText
			terminated = true
		end
		--new empty list of pages
		forRow.see = {}
		--if there's not at least one page listed, assume the list's ended, use
		--the default, and break at end of this loop-through.
		if args[i + 1] then
			table.insert(forRow.see, args[i + 1])
		else
			table.insert(forRow.see, (options.title .. options.disambiguator))
			terminated = true
		end
		--if the option after next is "and", do an inner loop where we collect
		--items following "and"'s until the "and"'s stop. If there's a blank
		--where we'd expect an item, ignore it: "1|and||and|3" → {1, 3}
		while args[i + 2] == 'and' do
			if args[i + 3] then 
				table.insert(forRow.see, args[i + 3])
			end
			--increment up to the next "and"
			i = i + 2
		end
		--increment to the next use
		i = i + 2
		--add the row to the table
		table.insert(forTable, forRow)
	until terminated or i > maxArg

	--stringify the table, which is easy because it's structured now.	
	local strList = {}
	for k, v in pairs(forTable) do
		local useStr = v.use
		local seeStr = p.andList(p.formatPages(unpack(v.see)))
		table.insert(strList, string.format('For %s, see %s.', useStr, seeStr))
	end
	return mw.text.listToText(strList, ' ', ' ')
end

--------------------------------------------------------------------------------
-- Format link
--
-- Makes a wikilink from the given link and display values. Links are escaped
-- with colons if necessary, and links to sections are detected and displayed
-- with " § " as a separator rather than the standard MediaWiki "#". Used in
-- the {{format hatnote link}} template.
--------------------------------------------------------------------------------

function p.formatLink(frame)
	local args = getArgs(frame)
	local link = args[1]
	local display = args[2]
	if not link then
		return p.makeWikitextError(
			'no link specified',
			'Template:Format hatnote link#Errors',
			args.category
		)
	end
	return p._formatLink(link, display)
end

function p._formatLink(link, display)
	checkType('_formatLink', 1, link, 'string')
	checkType('_formatLink', 2, display, 'string', true)

	-- Remove the initial colon for links where it was specified manually.
	link = removeInitialColon(link)

	-- Find whether a faux display value has been added with the {{!}} magic
	-- word.
	if not display then
		local prePipe, postPipe = link:match('^(.-)|(.*)$')
		link = prePipe or link
		display = postPipe
	end

	-- Find the display value.
	if not display then
		local page, section = link:match('^(.-)#(.*)$')
		if page then
			display = page .. ' § ' .. section
		end
	end

	-- Assemble the link.
	if display then
		return string.format('[[:%s|%s]]', link, display)
	else
		return string.format('[[:%s]]', link)
	end
end

--------------------------------------------------------------------------------
-- Hatnote
--
-- Produces standard hatnote text. Implements the {{hatnote}} template.
--------------------------------------------------------------------------------

function p.hatnote(frame)
	local args = getArgs(frame)
	local s = args[1]
	local options = {}
	if not s then
		return p.makeWikitextError(
			'no text specified',
			'Template:Hatnote#Errors',
			args.category
		)
	end
	options.extraclasses = args.extraclasses
	options.selfref = args.selfref
	return p._hatnote(s, options)
end

function p._hatnote(s, options)
	checkType('_hatnote', 1, s, 'string')
	checkType('_hatnote', 2, options, 'table', true)
	options = options or {}
	local classes = {'hatnote'}
	local extraclasses = options.extraclasses
	local selfref = options.selfref
	if type(extraclasses) == 'string' then
		classes[#classes + 1] = extraclasses
	end
	if selfref then
		classes[#classes + 1] = 'selfref'
	end
	return string.format(
		'<div role="note" class="%s">%s</div>',
		table.concat(classes, ' '),
		s
	)
end

return p