Module:Excerpt

From escforumwiki
Revision as of 16:17, 10 July 2023 by en>Sophivorus (Update to latest: refine error handling for invalid titles, make some methods local, add Aidan9382 as author, remove old reference to fragment param)
Jump to navigation Jump to search

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

-- Module:Excerpt implements the Excerpt template
-- Documentation and master version: https://en.wikipedia.org/wiki/Module:Excerpt
-- Authors: User:Sophivorus, User:Certes, User:Aidan9382 & others
-- License: CC-BY-SA-3.0

local Transcluder = require( 'Module:Transcluder' )

local yesno = require( 'Module:Yesno' )

local ok, config = pcall( require, 'Module:Excerpt/config' )
if not ok then config = {} end

local p = {}

-- Helper function to get arguments
local args
local function getArg( key, default )
	local value = args[ key ]
	if value and mw.text.trim( value ) ~= '' then
		return value
	end
	return default
end

-- Helper function to handle errors
local function getError( message, value )
	if type( message ) == 'string' then
		message = Transcluder.getError( message, value )
	end
	if config.categories and config.categories.errors and mw.title.getCurrentTitle().isContentPage then
		message:node( '[[Category:' .. config.categories.errors .. ']]' )
	end
	return message
end

-- Helper function to get localized messages
local function getMessage( key )
	local ok, TNT = pcall( require, 'Module:TNT' )
	if not ok then return key end
	return TNT.format( 'I18n/Module:Excerpt.tab', key )
end

-- Main entry point for templates
function p.main( frame )
	args = Transcluder.parseArgs( frame )

	-- Make sure the requested page exists
	local page = getArg( 1 )
	if not page or page == '{{{1}}}' then return getError( 'no-page' ) end
	local title = mw.title.new(page)
	if not title then return getError( 'invalid-title', page ) end
	if title.isRedirect then title = title.redirectTarget end
	if not title.exists then return getError( 'page-not-found', page ) end
	page = title.prefixedText

	-- Set variables from the template parameters
	local section = getArg( 2, mw.ustring.match( getArg( 1 ), '[^#]+#(.+)' ) )
	local hat = yesno( getArg( 'hat', true ) )
	local edit = yesno( getArg( 'edit', true ) )
	local this = getArg( 'this' )
	local only = getArg( 'only' )
	local files = getArg( 'files', getArg( 'file', ( only == 'file' and 1 ) ) )
	local lists = getArg( 'lists', getArg( 'list', ( only == 'list' and 1 ) ) )
	local tables = getArg( 'tables', getArg( 'table', ( only == 'table' and 1 ) ) )
	local templates = getArg( 'templates', getArg( 'template', ( only == 'template' and 1 ) ) )
	local paragraphs = getArg( 'paragraphs', getArg( 'paragraph', ( only == 'paragraph' and 1 ) ) )
	local references = getArg( 'references' )
	local subsections = not yesno( getArg( 'subsections' ) )
	local noLinks = not yesno( getArg( 'links', true ) )
	local noBold = not yesno( getArg( 'bold' ) )
	local onlyFreeFiles = yesno( getArg( 'onlyfreefiles', true ) )
	local briefDates = yesno( getArg( 'briefdates', false ) )
	local inline = yesno( getArg( 'inline' ) )
	local quote = yesno( getArg( 'quote' ) )
	local more = yesno( getArg( 'more' ) )
	local class = getArg( 'class' )
	local displaytitle = getArg( 'displaytitle' ) or page

	-- Build the hatnote
	if hat and not inline then
		if this then
			hat = this
		elseif quote then
			hat = getMessage( 'this' )
		elseif only then
			hat = getMessage( only )
		else
			hat = getMessage( 'section' )
		end
		hat = hat .. ' ' .. getMessage( 'excerpt' ) .. ' '
		if section then
			hat = hat .. '[[:' .. page .. '#' .. mw.uri.anchorEncode( section ) .. '|' .. displaytitle
				.. ' § ' .. mw.ustring.gsub( section, '%[%[([^]|]+)|?[^]]*%]%]', '%1' ) .. ']].' -- remove nested links
		else
			hat = hat .. '[[:' .. page .. '|' .. displaytitle .. ']].'
		end
		if edit then
			hat = hat .. '<span class="mw-editsection-like plainlinks"><span class="mw-editsection-bracket">[</span>['
			hat = hat .. title:fullUrl( 'action=edit' ) .. ' ' .. mw.message.new( 'editsection' ):plain()
			hat = hat .. ']<span class="mw-editsection-bracket">]</span></span>'
		end
		if config.hat then
			hat = config.hat .. hat .. '}}'
			hat = frame:preprocess( hat )
		else
			hat = mw.html.create( 'div' ):addClass( 'dablink excerpt-hat' ):wikitext( hat )
		end
	else
		hat = nil
	end

	-- Build the "Read more" link
	if more and not inline then
		more = "'''[[" .. page .. '#' .. ( section or '' ) .. "|" .. getMessage( 'more' ) .. "]]'''"
		more = mw.html.create( 'div' ):addClass( 'noprint excerpt-more' ):wikitext( more )
	else
		more = nil
	end

	-- Build the options for Module:Transcluder out of the template parameters and the desired defaults
	local options = {
		files = files,
		lists = lists,
		tables = tables,
		paragraphs = paragraphs,
		sections = subsections,
		categories = 0,
		references = references,
		only = only and mw.text.trim( only, 's' ) .. 's',
		noLinks = noLinks,
		noBold = noBold,
		noSelfLinks = true,
		noNonFreeFiles = onlyFreeFiles,
		noBehaviorSwitches = true,
		fixReferences = true,
		linkBold = true,
	}

	-- Get the excerpt itself
	local title = page .. '#' .. ( section or '' )
	local ok, excerpt = pcall( Transcluder.get, title, options )
	if not ok then return getError( excerpt ) end
	if mw.text.trim( excerpt ) == '' and not only then
		if section then return getError( 'section-empty', section ) else return getError( 'lead-empty' ) end
	end

	-- Fix birth and death dates, but only in the first paragraph
	if briefDates then
		local startpos = 1 -- skip initial templates
		local s
		local e = 0
		repeat
			startpos = e + 1
			s, e = mw.ustring.find( excerpt, "%s*%b{}%s*", startpos )
		until not s or s > startpos
		s, e = mw.ustring.find( excerpt, "%b()", startpos ) -- get (...), which may be (year–year)
		if s and s < startpos + 100 then -- look only near the start
			local year1, conjunction, year2 = mw.ustring.match( mw.ustring.sub( excerpt, s, e ), '(%d%d%d+)(.-)(%d%d%d+)' )
			if year1 and year2 and (mw.ustring.match( conjunction, '[%-–—]' ) or mw.ustring.match( conjunction, '{{%s*[sS]nd%s*}}' )) then
				local y1 = tonumber(year1)
				local y2 = tonumber(year2)
				if y2 > y1 and y2 < y1 + 125 and y1 <= tonumber( os.date( "%Y" )) then
					excerpt = mw.ustring.sub( excerpt, 1, s ) .. year1 .. "–" .. year2 .. mw.ustring.sub( excerpt, e )
				end
			end
		end
	end

	-- If no file was found, try to get one from the infobox
	local fileNamespaces = Transcluder.getNamespaces( 'File' )
	if ( ( only == 'file' or only == 'files' ) or ( not only and ( files ~= '0' or not files ) ) ) and -- caller asked for files
		not Transcluder.matchAny( excerpt, '%[%[', fileNamespaces, ':' ) and -- and there are no files in Transcluder's output
		config.captions -- and we have the config option required to try finding files in templates
	then
		-- We cannot distinguish the infobox from the other templates so we search them all
		local infobox = Transcluder.getTemplates( excerpt );
		infobox = table.concat( infobox )
		local parameters = Transcluder.getParameters( infobox )
		local file, captions, caption
		for _, pair in pairs( config.captions ) do
			file = pair[1]
			file = parameters[file]
			if file and Transcluder.matchAny( file, '^.*%.', { '[Jj][Pp][Ee]?[Gg]', '[Pp][Nn][Gg]', '[Gg][Ii][Ff]', '[Ss][Vv][Gg]' }, '.*' ) then
				file = mw.ustring.match( file, '%[?%[?.-:([^{|]+)%]?%]?' ) or file -- [[File:Example.jpg{{!}}upright=1.5]] to Example.jpg
				captions = pair[2]
				for _, p in pairs( captions ) do
					if parameters[ p ] then caption = parameters[ p ] break end
				end
				excerpt = '[[File:' .. file .. '|thumb|' .. ( caption or '' ) .. ']]' .. excerpt
				if ( onlyFreeFiles ) then
					excerpt = Transcluder.removeNonFreeFiles( excerpt )
				end
				break
			end
		end
	end

	-- Unlike other elements, templates are filtered here
	-- because we had to search the infoboxes for files
	local trash
	if only and ( only == 'template' or only == 'templates' ) then
		trash, excerpt = Transcluder.getTemplates( excerpt, templates );
	else -- Remove blacklisted templates
		local blacklist = config.blacklist and table.concat( config.blacklist, ',' ) or ''
		if templates then
			if string.sub( templates, 1, 1 ) == '-' then --Unwanted templates. Append to blacklist
				blacklist = templates .. ',' .. blacklist
			else --Wanted templates. Replaces blacklist and acts as whitelist
				blacklist = templates
			end
		else
			blacklist = '-' .. blacklist
		end
		trash, excerpt = Transcluder.getTemplates( excerpt, blacklist );
	end

	-- Remove extra line breaks but leave one before and after so the parser interprets lists, tables, etc. correctly
	excerpt = mw.text.trim( excerpt )
	excerpt = string.gsub( excerpt, '\n\n\n+', '\n\n' )
	excerpt = '\n' .. excerpt .. '\n'

	-- Remove nested categories
	excerpt = frame:preprocess( excerpt )
	local categories, excerpt = Transcluder.getCategories( excerpt, options.categories )

	-- Add tracking categories
	if config.categories then
		local contentCategory = config.categories.content
		if contentCategory and mw.title.getCurrentTitle().isContentPage then
			excerpt = excerpt .. '[[Category:' .. contentCategory .. ']]'
		end
		local namespaceCategory = config.categories[ mw.title.getCurrentTitle().namespace ]
		if namespaceCategory then
			excerpt = excerpt .. '[[Category:' .. namespaceCategory .. ']]'
		end
	end

	-- Load the styles
	local styles
	if config.styles then
		styles = frame:extensionTag( 'templatestyles', '', { src = config.styles } )
	end

	-- Combine and return the elements
	if inline then
		return mw.text.trim( excerpt )
	end
	local tag = 'div'
	if quote then
		tag = 'blockquote'
	end
	excerpt = mw.html.create( 'div' ):addClass( 'excerpt' ):wikitext( excerpt )
	local block = mw.html.create( tag ):addClass( 'excerpt-block' ):addClass( class )
	return block:node( styles ):node( hat ):node( excerpt ):node( more )
end

-- Entry points for backwards compatibility
function p.lead( frame ) return p.main( frame ) end
function p.excerpt( frame ) return p.main( frame ) end

return p