Module:title/newTitle

From Linguifex
Jump to navigation Jump to search

Documentation for this module may be created at Module:title/newTitle/doc

local load_module = "Module:load"
local scribunto_module = "Module:Scribunto"
local title_get_current_namespace_module = "Module:title/getCurrentNamespace"
local title_get_current_title_module = "Module:title/getCurrentTitle"
local title_get_main_page_title_module = "Module:title/getMainPageTitle"

local byte = string.byte
local find = string.find
local match = string.match
local new_title = mw.title.new
local reverse = string.reverse
local sub = string.sub
local type = type

local function get_current_title(...)
	get_current_title = require(title_get_current_title_module)
	return get_current_title(...)
end

local function get_main_page_title(...)
	get_main_page_title = require(title_get_main_page_title_module)
	return get_main_page_title(...)
end

local function php_ltrim(...)
	php_ltrim = require(scribunto_module).php_ltrim
	return php_ltrim(...)
end

local function php_rtrim(...)
	php_rtrim = require(scribunto_module).php_rtrim
	return php_rtrim(...)
end

local current_pagename
local function get_current_pagename()
	-- Call mw.title.getCurrentTitle() directly rather than the modified version
	-- at [[Module:title/getCurrentTitle]], as it doesn't do anything extra when
	-- no fragment argument is given, so there's no point in loading it.
	current_pagename, get_current_pagename = mw.title.getCurrentTitle().prefixedText, nil
	return current_pagename
end

local namespace_has_subpages
local function get_namespace_has_subpages()
	namespace_has_subpages, get_namespace_has_subpages = require(load_module).load_data(title_get_current_namespace_module).hasSubpages, nil
	return namespace_has_subpages
end

local function split_text(text)
	-- Split off the fragment.
	local hash, target, fragment = find(text, "#", nil, true)
	if hash then
		target, fragment = sub(text, 1, hash - 1), sub(text, hash)
	else
		target, fragment = text, ""
	end
	return php_rtrim(target), fragment
end

--[==[
A modified version of {mw.title.new}:
* It is no longer possible to generate title objects for the empty string by inputting a title starting with {"#"}. Such empty string titles do not represent a valid page, and are broken in various ways (e.g. attempting to access certain keys results in an error); see [[phab:T240678]].
* There are two additional boolean flags:
** If {allowOnlyFragment} is set, string inputs starting with {#} are handled analogously to links (e.g. the input {"#foo"} returns a title object for the current page with the addition of the fragment {"foo"}, analogous to the link [[#foo]]). As a special case, the input {"#"} returns the title for the main page (see TitleValue.php).
** If {allowRelative} is set, inputs representing relative titles will work (e.g. {"/foo"} and {"../"}, analogous to the relative links [[/foo]] and [[../]]).]==]
return function(text_or_id, defaultNamespace, allowOnlyFragment, allowRelative)
	-- Process relative titles in the same way as normalizeSubpageLink() in
	-- Linker.php.
	if (
		allowRelative and
		type(text_or_id) == "string" and
		-- Distinguish nil and false for the purposes of calling the getter.
		(namespace_has_subpages == nil and get_namespace_has_subpages() or namespace_has_subpages)
	) then
		-- For both kinds of relative title, `text_or_id` has to be split into
		-- `target` and `fragment` (if any). `target` has to be trimmed, but
		-- as an optimisation, only do a left-trim on `text_or_id`, which is
		-- sufficient for the initial checks for potential relative titles, as
		-- this avoids unnecessary splitting/trimming in the vast majority of
		-- cases where the title won't be relative. In the cases where it is,
		-- `target` will then be right-trimmed once it has been split out of
		-- `text_or_id`.
		text_or_id = php_ltrim(text_or_id)
		local init = byte(text_or_id)
		-- If the target starts with "/", it's treated as a subpage of the
		-- current page. Final slashes are trimmed, but this can't affect the
		-- intervening slash (e.g. "[[///]]" refers to "[[{{PAGENAME}}/]]").
		if init == 0x2F then -- /
			local target, fragment = split_text(text_or_id)
			text_or_id, defaultNamespace = (current_pagename or get_current_pagename()) .. (match(target, "^/.*[^/]") or "/") .. fragment, nil
		-- If the title starts with "../", trim it and any further "../" that
		-- follow, and go up that many subpage levels. Then, treat any
		-- additional text as a subpage of that page. Final slashes are trimmed.
		elseif init == 0x2E and sub(text_or_id, 2, 3) == "./" then -- ../
			local n, target, fragment = 4, split_text(text_or_id)
			while sub(target, n, n + 2) == "../" do
				n = n + 3
			end
			-- Retain an initial "/".
			target = sub(target, n - 1)
			-- Trim the relevant number of subpages from the pagename.
			local pagename, i = reverse(current_pagename or get_current_pagename()), 0
			for _ = 1, (n - 1) / 3 do
				i = find(pagename, "/", i + 1, true)
				-- Fail if there aren't enough slashes.
				if not i then
					return nil
				end
			end
			-- Add the subpage text; since the intervening "/" is retained in
			-- `target`, it can be trimmed along with any other final slashes
			-- (e.g. [[..///]] refers to "{{BASEPAGENAME}}".)
			text_or_id, defaultNamespace = reverse(sub(pagename, i + 1)) .. (match(target, "^.*[^/]") or "") .. fragment, nil
		end
	end
	local title = new_title(text_or_id, defaultNamespace)
	if not title then
		return nil
	elseif title.prefixedText ~= "" then
		return title
	-- If `prefixedText` is the empty string, the input only has a fragment.
	elseif not allowOnlyFragment then
		return nil
	end
	-- Retrieve the fragment: if it's the empty string, return a title object
	-- for the main page (which is where the link [[#]] resolves); otherwise,
	-- return a current title object using [[Module:title/getCurrentTitle]],
	-- which sets the fragment on the returned title to the string passed in.
	-- (Note that inputs like "##foo" aren't interpreted as the main page with
	-- the fragment "foo"; instead, it's a fragment for the section "#foo" on
	-- the current page, since "#" is itself a valid fragment character.)
	local fragment = title.fragment
	return fragment == "" and get_main_page_title() or get_current_title(fragment)
end