Permanently protected module

Difference between revisions of "Module:Autolink"

From Yugipedia
Jump to: navigation, search
(from sandbox: fix for input which is manually linked and enclosed in a single set of double quotes)
(some tweaks: clean up globals; slightly optimize unprettify(); slightly more descriptive variable names in prettify_link(); expand ref comment a bit)
 
(10 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
--
 
--
-- implements {{Autolink}} and {{Unlink}}
+
-- implements {{Autolink}}, {{Unlink}}, and {{Formatted link}}
 
--
 
--
  
 +
require('Module:No globals')
 
local p = {}
 
local p = {}
local text = mw.text
 
  
function p.link( frame )
+
-- removes all special formatting from a string
    if frame == mw.getCurrentFrame() then
+
local function unprettify( s )
        args = frame:getParent().args
+
return s:gsub( '""', '' )
    else
+
:gsub( "'''?", '' )
        args = frame
+
:gsub( '[# ]##?', ' ' ) --lmao
    end
+
:gsub( '  ', ' ' )
 +
end
  
    -- marker used for {{nolink}} support (doesn't have to be a zero-width non-joiner, that's just what was used in the template version)
+
-- formats a link
    local zwnj = '‌'
+
local function prettify_link( target, label )
    local linkarr, links, listmarkup, el, ell, elr, link, raw, txt, formatl, formatr
+
if not target or target == '' then
 +
return ''
 +
end
 +
 +
if not label or label == '' then
 +
label = target
 +
end
 +
 +
local plain_target = unprettify( target )
 +
local plain_label = unprettify( label )
 +
local prettified_label = label:gsub( '""', '"' ):gsub( '([^&# ])#([^#])', '%1 § %2' ):gsub( '##', '#' )
 +
 +
local link = ''
 +
if plain_target == prettified_label then
 +
-- no formatting
 +
link = ('[[%s]]'):format( plain_target )
 +
elseif plain_target == plain_label then
 +
local start, _end = prettified_label:find( plain_target, 1, true )
 +
if start then
 +
-- only formatting is on the link boundaries (`''Foo Bar''`)
 +
link = ('%s[[%s]]%s'):format( prettified_label:sub( 1, start - 1 ), plain_target, prettified_label:sub( _end + 1 ) )
 +
else
 +
-- formatting within the link (`''Foo Bar'' (Quux)`)
 +
link = ('[[%s|%s]]'):format( plain_target, prettified_label )
 +
end
 +
else
 +
if plain_label == prettified_label then
 +
-- no formatting
 +
link = ('[[%s|%s]]'):format( plain_target, prettified_label )
 +
else
 +
local start, _end = prettified_label:find( plain_label, 1, true )
 +
if start then
 +
-- only formatting is on the text boundaries (`[[Foo Bar Baz|''Foo Bar'']]`)
 +
link = ('%s[[%s|%s]]%s'):format( prettified_label:sub( 1, start - 1 ), plain_target, prettified_label:sub( start, _end ), prettified_label:sub( _end + 1 ) )
 +
else
 +
-- formatting within the text (`[[Foo Bar Baz|''Foo'' Bar]]`)
 +
link = ('[[%s|%s]]'):format( plain_target, prettified_label )
 +
end
 +
end
 +
end
 +
return link
 +
end
  
    -- set default to stop errors
+
-- there's several things this doesn't do that it probably could/should:
    links = args[1] and text.trim( args[1] ) or ''
+
-- * supporting mixed lists (currently it assumes that whatever list type is used by the first item is the same as all other items)
 
+
-- ** similarly, supporting sublists
    -- check for nolink token at the front of the input, which prevents per-link/item processing
+
-- * allowing multiple items to be linked/formatted via multiple parameters, instead of just via a list in the first parameter
    if links:find( zwnj ) ~= 1 then
+
-- * supporting multiline link syntax (probably most likely from SMW annotations with multiline values, though file embeds can have multiline descriptions too)
        linkarr = text.split( links, '\n' )
+
-- * allowing text to be removed from the link for display (e.g. excluding the dabtag in `Dark Magician (manga)`)
 
+
function p.link( frame )
        args[2] = #linkarr == 1 and args[2]
+
local args = frame == mw.getCurrentFrame() and frame:getParent().args or frame
 
 
        listmarkup = #linkarr == 1 and ''
 
 
 
        for i = 1, #linkarr do
 
            el = text.trim( linkarr[i] )
 
 
 
            -- catch empty string at the start of lists
 
            if not el:find( '^[*#;:]?$' ) then
 
                if listmarkup ~= '' then
 
                    listmarkup = ( el:match( '^([*#;:])' ) or '*' ) .. ' '
 
                    el = el:gsub( '^[*#;:]%s*', '' )
 
                end
 
 
 
                if el:find( zwnj ) or el:find( '%[%[.-|.-%]%]' ) then
 
                    linkarr[i] = table.concat( { listmarkup, el }, '' )
 
                else
 
                    raw = el:find( '%[%[' )
 
                    ell = el:match( '^"%[%[' ) and '"' or ''
 
                    elr = el:match( '%]%]"$' ) and '"' or ''
 
                    el = el:match( '%[%[(.-)%]%]' ) or el
 
                    link = el
 
                    txt = args[2] or el
 
                    formatl = ''
 
                    formatr = ''
 
 
 
                    if raw ~= 1 then
 
                        link = link:gsub( '""', '' ):gsub( "'''?", '' )
 
 
 
                        -- check for formatting that can be moved out of the link entirely
 
                        if txt:find( '^""' ) and txt:find( '""$' ) then
 
                            formatl = '"'
 
                            formatr = '"'
 
                            txt = txt:gsub( '""', '' )
 
                        else
 
                            txt = txt:gsub( '""', '"' )
 
                        end
 
                        if txt:find( "^'''" ) and txt:find( "'''$" ) then
 
                            formatl = formatl .. "'''"
 
                            formatr = "'''" .. formatr
 
                            txt = txt:gsub( "'''$", '' ):gsub( "^'''", '' )
 
                        end
 
                        if txt:find( "^''" ) and txt:find( "''$" ) then
 
                            formatl = formatl .. "''"
 
                            formatr = "''" .. formatr
 
                            txt = txt:gsub( "''$", '' ):gsub( "^''", '' )
 
                        end
 
                    else
 
                        txt = txt:gsub( "'", ''' )
 
                    end
 
                    if txt:find( '[^&]#' ) then
 
                        txt = txt:gsub( '([^&])#', '%1 § ')
 
                    end
 
 
 
                    if link == txt then
 
                        linkarr[i] = table.concat( { listmarkup, ell, formatl, '[[', link, ']]', formatr, elr }, '' )
 
                    else
 
                        linkarr[i] = table.concat( { listmarkup, ell, formatl, '[[', link, '|', txt, ']]', formatr, elr }, '' )
 
                    end
 
                end
 
            end
 
        end
 
  
        links = table.concat( linkarr, '\n' )
+
-- the gsub() is a dumb hack for the encoded apostrophe test case(s)
    end
+
local items = args[1] and args[1]:gsub( '&', '&' ) or ''
 +
 +
-- remove any empty lines/list items from the input
 +
local lines = {}
 +
for item in mw.text.gsplit( items, '\n' ) do
 +
item = mw.text.trim( item:match( '^[*#;:]?(.*)$' ) )
 +
if item ~= '' then
 +
table.insert( lines, item )
 +
end
 +
end
  
    links = text.trim( links:gsub( zwnj, '' )
+
-- if there's more than one line, grab the list character (with a fallback if there is none)
        :gsub( '%[%[[Cc]ategory:', '[[:Category:' )
+
local listtype = ''
        :gsub( '%[%[[Ff]ile:', '[[:File:' )
+
if #lines > 1 then
        :gsub( '%[%[[Ii]mage:', '[[:File:' ) )
+
listtype = (items:match( '^%s*([*#;:])' ) or '*') .. ' '
    return links
+
end
 +
 +
-- format, link, and return
 +
local prettified_lines = {}
 +
local result = ''
 +
for _, line in ipairs( lines ) do
 +
if line:find( '[[', 1, true ) then
 +
local prettified_line = {}
 +
local post = line
 +
while post:find( '[[', 1, true ) do
 +
local pre, annotation, target, label
 +
if post:match( '^[^%[]*%[%[[^|%]]+::' ) then
 +
-- don't format SMW annotations
 +
pre, annotation = post:match( '^([^%[]*)(%[%[[^%]]+%]%])' )
 +
else
 +
pre, target, label = post:match( '^([^%[]*)%[%[ *([^|%]]+) *|? *([^%]]-) *%]%]' )
 +
end
 +
table.insert( prettified_line, ('%s%s'):format( pre and pre:gsub( '""', '"' ) or '', target and prettify_link( target, label ) or annotation ) )
 +
post = post:match( '%]%](.*)$' )
 +
end
 +
result = ('%s%s'):format( table.concat( prettified_line ), post:gsub( '""', '"' ) )
 +
elseif line:find( '\127', 1, true ) then
 +
-- input is unlinked but has a ref tag
 +
-- \127 (DEL) is the start/end character of strip markers
 +
-- technically this will match any stripped content, not just ref tags
 +
local link, ref = line:match( '^([^\127]+)(\127.*)$' )
 +
result = ('%s%s'):format( prettify_link( link ), ref )
 +
else
 +
result = prettify_link( line )
 +
end
 +
 +
table.insert( prettified_lines, ('%s%s'):format( listtype, result ) )
 +
end
 +
 +
prettified_lines = table.concat( prettified_lines, '\n' )
 +
:gsub( '%[%[[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]:', '[[:Category:' )
 +
:gsub( '%[%[[Ff][Ii][Ll][Ee]:', '[[:File:' )
 +
:gsub( '%[%[[Ii][Mm][Aa][Gg][Ee]:', '[[:File:' )
 +
 +
return prettified_lines
 
end
 
end
  
 +
-- returns the target of the first link in text
 +
-- to return the entire text without any links instead, see {{Delink}}
 
function p.unlink( frame )
 
function p.unlink( frame )
    local args = frame:getParent().args
+
local txt = frame == mw.getCurrentFrame() and frame:getParent().args[1] or frame[1]
    return args[1] and ( args[1]:match( '%[%[:?(.-)[|%]]' ) or text.trim( args[1] ) )
+
return txt and ( txt:match( '%[%[:?(.-)[|%]]' ) or mw.text.trim( txt ) )
 
end
 
end
  
 
return p
 
return p

Latest revision as of 19:07, 23 December 2023

--
-- implements {{Autolink}}, {{Unlink}}, and {{Formatted link}}
--

require('Module:No globals')
local p = {}

-- removes all special formatting from a string
local function unprettify( s )
	return s:gsub( '""', '' )
		:gsub( "'''?", '' )
		:gsub( '[# ]##?', ' ' ) --lmao
		:gsub( '  ', ' ' )
end

-- formats a link
local function prettify_link( target, label )
	if not target or target == '' then
		return ''
	end
	
	if not label or label == '' then
		label = target
	end
	
	local plain_target = unprettify( target )
	local plain_label = unprettify( label )
	local prettified_label = label:gsub( '""', '"' ):gsub( '([^&# ])#([^#])', '%1 § %2' ):gsub( '##', '#' )
	
	local link = ''
	if plain_target == prettified_label then
		-- no formatting
		link = ('[[%s]]'):format( plain_target )
	elseif plain_target == plain_label then
		local start, _end = prettified_label:find( plain_target, 1, true )
		if start then
			-- only formatting is on the link boundaries (`''Foo Bar''`)
			link = ('%s[[%s]]%s'):format( prettified_label:sub( 1, start - 1 ), plain_target, prettified_label:sub( _end + 1 ) )
		else
			-- formatting within the link (`''Foo Bar'' (Quux)`)
			link = ('[[%s|%s]]'):format( plain_target, prettified_label )
		end
	else
		if plain_label == prettified_label then
			-- no formatting
			link = ('[[%s|%s]]'):format( plain_target, prettified_label )
		else
			local start, _end = prettified_label:find( plain_label, 1, true )
			if start then
				-- only formatting is on the text boundaries (`[[Foo Bar Baz|''Foo Bar'']]`)
				link = ('%s[[%s|%s]]%s'):format( prettified_label:sub( 1, start - 1 ), plain_target, prettified_label:sub( start, _end ), prettified_label:sub( _end + 1 ) )
			else
				-- formatting within the text (`[[Foo Bar Baz|''Foo'' Bar]]`)
				link = ('[[%s|%s]]'):format( plain_target, prettified_label )
			end
		end
	end
	return link
end

-- there's several things this doesn't do that it probably could/should:
-- * supporting mixed lists (currently it assumes that whatever list type is used by the first item is the same as all other items)
-- ** similarly, supporting sublists
-- * allowing multiple items to be linked/formatted via multiple parameters, instead of just via a list in the first parameter
-- * supporting multiline link syntax (probably most likely from SMW annotations with multiline values, though file embeds can have multiline descriptions too)
-- * allowing text to be removed from the link for display (e.g. excluding the dabtag in `Dark Magician (manga)`)
function p.link( frame )
	local args = frame == mw.getCurrentFrame() and frame:getParent().args or frame

	-- the gsub() is a dumb hack for the encoded apostrophe test case(s)
	local items = args[1] and args[1]:gsub( '&', '&' ) or ''
	
	-- remove any empty lines/list items from the input
	local lines = {}
	for item in mw.text.gsplit( items, '\n' ) do
		item = mw.text.trim( item:match( '^[*#;:]?(.*)$' ) )
		if item ~= '' then
			table.insert( lines, item )
		end
	end

	-- if there's more than one line, grab the list character (with a fallback if there is none)
	local listtype = ''
	if #lines > 1 then
		listtype = (items:match( '^%s*([*#;:])' ) or '*') .. ' '
	end
	
	-- format, link, and return
	local prettified_lines = {}
	local result = ''
	for _, line in ipairs( lines ) do
		if line:find( '[[', 1, true ) then
			local prettified_line = {}
			local post = line
			while post:find( '[[', 1, true ) do
				local pre, annotation, target, label
				if post:match( '^[^%[]*%[%[[^|%]]+::' ) then
					-- don't format SMW annotations
					pre, annotation = post:match( '^([^%[]*)(%[%[[^%]]+%]%])' )
				else
					pre, target, label = post:match( '^([^%[]*)%[%[ *([^|%]]+) *|? *([^%]]-) *%]%]' )
				end
				table.insert( prettified_line, ('%s%s'):format( pre and pre:gsub( '""', '"' ) or '', target and prettify_link( target, label ) or annotation ) )
				post = post:match( '%]%](.*)$' )
			end
			result = ('%s%s'):format( table.concat( prettified_line ), post:gsub( '""', '"' ) )
		elseif line:find( '\127', 1, true ) then
			-- input is unlinked but has a ref tag
			-- \127 (DEL) is the start/end character of strip markers
			-- technically this will match any stripped content, not just ref tags
			local link, ref = line:match( '^([^\127]+)(\127.*)$' )
			result = ('%s%s'):format( prettify_link( link ), ref )
		else
			result = prettify_link( line )
		end
		
		table.insert( prettified_lines, ('%s%s'):format( listtype, result ) )
	end
	
	prettified_lines = table.concat( prettified_lines, '\n' )
		:gsub( '%[%[[Cc][Aa][Tt][Ee][Gg][Oo][Rr][Yy]:', '[[:Category:' )
		:gsub( '%[%[[Ff][Ii][Ll][Ee]:', '[[:File:' )
		:gsub( '%[%[[Ii][Mm][Aa][Gg][Ee]:', '[[:File:' )
		
	return prettified_lines
end

-- returns the target of the first link in text
-- to return the entire text without any links instead, see {{Delink}}
function p.unlink( frame )
	local txt = frame == mw.getCurrentFrame() and frame:getParent().args[1] or frame[1]
	return txt and ( txt:match( '%[%[:?(.-)[|%]]' ) or mw.text.trim( txt ) )
end

return p