Module:template link

From Wiktionary, the free dictionary
Jump to navigation Jump to search

This module implements {{temp}} and {{tempn}}.


local export = {}

local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local string = string
local table = table

local concat = table.concat
local encode_uri = mw.uri.encode
local format = string.format
local format_link -- defined as export.format_link below
local is_substing = mw.isSubsting
local lower = string.lower
local make_title = mw.title.makeTitle -- unconditionally adds the specified namespace prefix
local new_title = mw.title.new -- specified namespace prefix is only added if the input doesn't contain one
local pairs = pairs
local parse_template_name = require("Module:template parser").parseTemplateName
local php_trim = m_str_utils.php_trim
local process_params = require("Module:parameters").process
local shallowcopy = m_table.shallowcopy
local sorted_pairs = m_table.sortedPairs
local split = m_str_utils.split
local insert = table.insert
local type = type

local data = mw.loadData("Module:template link/data")
local magic_words = data.magic_words
local link_parameter_0 = data.link_parameter_0
local link_parameter_1 = data.link_parameter_1
local parser_extension_tags = mw.loadData("Module:data/parser extension tags")

do
	-- Generate a link. If the target title doesn't have a fragment, use "#top"
	-- (which is an implicit anchor at the top of every page), as this ensures
	-- self-links still display as links, since bold display is distracting and
	-- unintuitive for template links.
	local function link_page(title, display)
		local fragment = title.fragment
		fragment = fragment == "" and "top" or fragment
		return format(
			"[[:%s#%s|%s]]",
			encode_uri(title.prefixedText, "WIKI"),
			encode_uri(fragment, "WIKI"),
			display
		)
	end
	
	-- pf_arg0 or arg1 may need to be linked if a given parser function treats
	-- them as a pagename. If a key exists in `namespace`, the value is the
	-- namespace for the page: if not 0, then the namespace prefix will always
	-- be added to the input (e.g. {{#invoke}} can only target the Module:
	-- namespace, so inputting "Template:foo" gives "Module:Template:foo", and
	-- "Module:foo" gives "Module:Module:foo"). However, this isn't possible
	-- with mainspace (namespace 0), so prefixes are respected. make_title
	-- handles all of this automatically.
	local function finalize_arg(pagename, namespace)
		if not namespace then
			return pagename
		end
		local title = make_title(namespace, pagename)
		if not title then
			return pagename
		end
		return link_page(title, pagename)
	end
	
	local function render_title(name, args)
		-- parse_template_name returns a table of transclusion modifiers plus
		-- the normalized template/magic word name, which will be used as link
		-- targets. The second return value pf_arg0 is argument 0, which is
		-- returned if the target is a parser function (e.g. "foo" in
		-- "{{#IF:foo|bar|baz}}"). Note: the second parameter checks if there
		-- are any arguments, since parser variables cannot take arguments
		-- (e.g. {{CURRENTYEAR}} is a parser variable, but {{CURRENTYEAR|foo}}
		-- transcludes "Template:CURRENTYEAR"). In such cases, the returned
		-- table explicitly includes the "Template:" prefix in the template
		-- name. The third parameter instructs it to retain any fragment in the
		-- template name in the returned table, if present.
		local chunks, pf_arg0 = parse_template_name(
			name,
			args and pairs(args)(args) ~= nil,
			true
		)
		if chunks == nil then
			return name
		end
		local chunks_len = #chunks
		-- Additionally, generate the corresponding table `rawchunks`, which
		-- is a list of colon-separated chunks in the raw input. This is used
		-- to retrieve the display forms for each chunk.
		local rawchunks = split(name, ":")
		for i = 1, chunks_len - 1 do
			chunks[i] = format(
				"[[%s|%s]]",
				encode_uri(magic_words[chunks[i]], "WIKI"),
				rawchunks[i]
			)
		end
		local chunk = chunks[chunks_len]
		local link = magic_words[chunk]
		-- If the name is not listed in magic_words, it must be a template,
		-- so return a link to it with link_page, concatenating the remaining
		-- chunks in `rawchunks` to form the display text.
		-- Use new_title with the default namespace 10 (Template:) to generate
		-- a target title, which is the same setting used for retrieving
		-- templates (including those in other namespaces, as prefixes override
		-- the default).
		if not link then
			chunks[chunks_len] = link_page(
				new_title(chunk, 10),
				concat(rawchunks, ":", chunks_len) -- :
			)
			return concat(chunks, ":") -- :
		end
		local arg1 = args and args[1] or nil
		-- Otherwise, it's a magic word. Some magic words have different links,
		-- depending on whether argument 1 is specified (e.g. "baz" in
		-- {{foo:bar|baz}}).
		if type(link) == "table" then
			link = arg1 and link[1] or link[0]
		end
		chunks[chunks_len] = format(
			"[[%s|%s]]",
			encode_uri(link, "WIKI"),
			rawchunks[chunks_len]
		)
		-- If we don't have pf_arg0, it must be a parser variable, so return.
		if not pf_arg0 then
			return concat(chunks, ":") -- :
		-- #TAG: has special handling, because documentation links for parser
		-- extension tags come from [[Module:data/parser extension tags]].
		elseif chunk == "#TAG:" then
			local tag = parser_extension_tags[lower(php_trim(pf_arg0))]
			if tag then
				pf_arg0 = format(
					"[[%s|%s]]",
					encode_uri(tag, "WIKI"),
					pf_arg0
				)
			end
		-- Otherwise, finalize pf_arg0 and add it to `chunks`.
		else
			pf_arg0 = finalize_arg(pf_arg0, link_parameter_0[chunk])
		end
		chunks[chunks_len + 1] = pf_arg0
		-- Finalize arg1 (if applicable) then return.
		if arg1 then
			args[1] = finalize_arg(arg1, link_parameter_1[chunk])
		end
		return concat(chunks, ":") -- :
	end

	function export.format_link(title, args, nested)
		local output = {render_title(title, args)}
		
		-- Iterate over numbered arguments, adding them as implicit parameters
		-- (i.e. with no key) for as long as they correspond with a counter
		-- from 1. Then, add any remaining parameters with explicit keys
		-- (including any non-sequential numbered parameters).
		if args then
			local iter = sorted_pairs(args)
			local i, k, v = 1, iter()
			while k == i do
				insert(output, v)
				k, v = iter()
				i = i + 1
			end
			if k ~= nil then
				repeat
					insert(output, k .. "=" .. v) -- =
					k, v = iter()
				until k == nil
			end
		end
		
		-- Add opening/closing braces and delimiting pipes.
		output = "{{" .. concat(output, "|") .. "}}" -- {{ | }}
		
		-- Return, enclosing in <code> tags if the `nested` flag hasn't been
		-- set.
		return nested and output or "<code>" .. output .. "</code>"
	end
	format_link = export.format_link
end

function export.show(frame)
	if is_substing() then
		return require("Module:unsubst").unsubst_template("format_link")
	end
	
	local iargs = process_params(frame.args, {
		["annotate"] = {},
		["nested"] = {type = "boolean"}
	})
	
	-- iargs.annotate allows a template to specify the title, so the input
	-- arguments will match the output. Otherwise, we treat parameter 1 as the
	-- template name and offset any implicit arguments.
	local title = iargs.annotate
	if title then
		return format_link(title, shallowcopy(frame:getParent().args), iargs.nested)
	end
	-- Process parameters with the return_unknown flag set. `title` contains
	-- the title at key 1; everything else goes in `args`.
	local args
	title, args = process_params(frame:getParent().args, {
		[1] = {required = true, allow_empty = true, allow_whitespace = true}
	}, true)
	title = title[1]
	-- Shift all implicit arguments down by 1. Non-sequential numbered
	-- parameters don't get shifted; however, this offset means that if the
	-- input contains (e.g.) {{tl|l|en|3=alt}}, representing {{l|en|3=alt}},
	-- the parameter at 3= is instead treated as sequential by this module,
	-- because it's indistinguishable from {{tl|l|en|alt}}, which represents
	-- {{l|en|alt}}. On the other hand, {{tl|l|en|4=tr}} is handled correctly,
	-- because there's still a gap before 4=.
	-- Unfortunately, there's no way to know the original input, so
	-- there's no clear way to fix this; the only difference is that explicit
	-- parameters have whitespace trimmed from their values while implicit ones
	-- don't, but we can't assume that every input with no whitespace was given
	-- with explicit numbering.
	-- This also causes bigger problems for any parser functions which treat
	-- their inputs as arrays, or in some other nonstandard way (e.g.
	-- {{#IF:foo|bar=baz|qux}} treats "bar=baz" as parameter 1). Without
	-- knowing the original input, these can't be reconstructed accurately.
	-- The way around this is to use <nowiki> tags in the input, since this
	-- module won't unstrip them by design.
	local i = 2
	repeat
		local arg = args[i]
		args[i - 1] = arg
		i = i + 1
	until arg == nil
	
	return format_link(title, args, iargs.nested)
end

return export