Module:Sandbox/Aidan9382/Link once

From WikiProjectMed
Jump to navigation Jump to search

require("strict")
local yesno = require("Module:Yesno")

-- Behaviour for these functions determined via [[Help:Pipe trick]]
local function wlPipeTrick(target)
	target = target:gsub("^[a-zA-Z0-9 _-]-:(.*)$", "%1") --Remove the namespace
	if target:find("%(.+%)$") then --If ending parenthesis
		target = target:gsub("^(.-) *%(.+%)$", "%1") --Remove ending parenthesis
	else 
		target = target:gsub("^(.-), .*$", "%1") --Else, remove ending comma
	end
	return target
end

local function wlReversePipeTrick(target)
	local current = mw.title.getCurrentTitle().prefixedText
	if current:find("%(.+%)$") then --If ending parenthesis
		target = target .. current:gsub("^.-( *%(.+%))$", "%1") --Append ending parenthesis
	else 
		target = target .. current:gsub("^.-(, .*)$", "%1") --Else, append ending comma
	end
	return target
end

local function getWikilinkInfo(wikilink)
	--[=[
		Returns the wikilink's target and its display text.
		Automatically recreates the effect of any [[Help:pipe tricks|]]
	--]=]
	local trim = mw.text.trim
	local trimmed = string.sub(wikilink, 3, -3)
	local firstPipe = string.find(trimmed, "|")
	if firstPipe then
		local target = string.sub(trimmed, 1, firstPipe-1)
		local displayText = string.sub(trimmed, firstPipe+1)
		if target == "" then -- [[|XYZ]]
			return trim(wlReversePipeTrick(displayText)), trim(displayText)
		elseif displayText == "" then -- [[XYZ|]]
			return trim(target), trim(wlPipeTrick(target))
		else --[[ABC|XYZ]]
			return trim(target), trim(displayText)
		end
	else
		local out = trim(trimmed)
		if out:find("^/.-/+$") and mw.title.getCurrentTitle().namespace ~= 0 then -- [[/Test/]]
			return out, out:gsub("^/(.-)/+$", "%1")
		else -- [[Test]]
			return out, nil
		end
	end
end

local function linkOnce(text, options) -- Module entry point
	--[=[
		We are going to traverse the text linearly ourselves.
		Using %b[] isn't preferable as nested brackets (E.g. the wikilink to t
		in [[File:x|Cap[[t]]ion]]) would be missed and doing a check for
		%[%[.-%]%] wouldn't work for the exact same testcase for other reasons
	--]=]
	local options = options or {follow_redirects=true}
	local newText = {}
	local scannerPosition = 1
	local existingWikilinks = {}
	local openWikilinks = {}
	while true do
		local Position, _, Character = string.find(text, "([%[%]])%1", scannerPosition)
		local container = (openWikilinks[#openWikilinks] or {Text=newText}).Text
		if not Position then --Done
			container[#container+1] = string.sub(text, scannerPosition)
			break
		end
		container[#container+1] = string.sub(text, scannerPosition, Position-1)

		scannerPosition = Position+2 --+2 to pass the [[ / ]]
		if Character == "[" then --Add a [[ to the pending wikilink queue
			openWikilinks[#openWikilinks+1] = {Position = Position, Text = {"[["}}

		else --Pair up the ]] to any available [[
			if #openWikilinks >= 1 then
				local openingPair = table.remove(openWikilinks) --Pop the latest [[
				local wlStart, wlText = openingPair.Position, table.concat(openingPair.Text, "") .. "]]"
				local wikilink = string.sub(text, wlStart, Position+1)
				local wlTarget, wlPiped = getWikilinkInfo(wikilink)

				local newContainer = (openWikilinks[#openWikilinks] or {Text=newText}).Text
				if wlTarget:find("^[Ii]mage:") or wlTarget:find("^[Ff]ile:") or wlTarget:find("^[Cc]ategory:") then --Files/Images/Categories aren't processed (they aren't really wikilinks)
					newContainer[#newContainer+1] = wlText
				else
					local realTarget = wlTarget:sub(1, 1):upper() .. wlTarget:sub(2)
					if existingWikilinks[realTarget] then
						newContainer[#newContainer+1] = wlPiped or wlTarget
					else
						local resolvedTarget = realTarget
						if options.follow_redirects then
							local titleObj = mw.title.new(wlTarget)
							if titleObj then
								local newTarget = titleObj.isRedirect and titleObj.redirectTarget.fullText or titleObj.fullText
								resolvedTarget = newTarget:sub(1, 1):upper() .. newTarget:sub(2)
							end
						end
						if existingWikilinks[resolvedTarget] then
							newContainer[#newContainer+1] = wlPiped or wlTarget
						else
							existingWikilinks[realTarget] = true
							existingWikilinks[resolvedTarget] = true
							newContainer[#newContainer+1] = wlText
						end
					end
				end

			else --Just a random ]] with no matching pair, dont process it
				newText[#newText+1] = "]]"
			end
		end
	end

	if #openWikilinks > 0 then --Random [[ with no matching pair, dont process it
		for i = #openWikilinks, 2, -1 do
			local nextLink = openWikilinks[i-1]
			nextLink.Text[#nextLink.Text+1] = table.concat(openWikilinks[i].Text, "")
		end
		newText[#newText+1] = table.concat(openWikilinks[1].Text, "")
	end
	return table.concat(newText, "")
end

local function main(frame) -- Template entry point
	local args = require('Module:Arguments').getArgs(frame)
	return linkOnce(args[1] or "", {
		follow_redirects = yesno(args.follow_redirects) or true,
	})
end

return {
	-- Main entry points
	main = main,
	linkOnce = linkOnce,
	-- Helper functions
	wlPipeTrick = wlPipeTrick,
	wlReversePipeTrick = wlReversePipeTrick,
	getWikilinkInfo = getWikilinkInfo
}