Difference between revisions of "Module:Autolink"
Dinoguy1000 (talk | contribs) (maybe table.concat() instead?) |
Dinoguy1000 (talk | contribs) (some tweaks: clean up globals; slightly optimize unprettify(); slightly more descriptive variable names in prettify_link(); expand ref comment a bit) |
||
(62 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
-- | -- | ||
− | -- implements {{Autolink}} | + | -- implements {{Autolink}}, {{Unlink}}, and {{Formatted link}} |
-- | -- | ||
+ | require('Module:No globals') | ||
local p = {} | local p = {} | ||
− | function | + | -- 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 | 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