Módulo:TemplUtl

A documentação para este módulo pode ser criada na página Módulo:TemplUtl/doc

local TemplUtl = { suite  = "TemplUtl",
																			serial = "2022-05-16",
																			item   = 52364930 };
--[=[
Utilities to support template programming.
]=]
local Failsafe = TemplUtl;
local fallible = function ( adjust, ahead )
				-- Check for leading character disturbing syntax
				-- Precondition:
				--    adjust  -- string; trimmed wikitext
				--    ahead   -- true, if leading syntax shall start on new line
				-- Postcondition:
				--    Returns string, modified if necessary
				local r = adjust;
				local c = r:byte( 1, 1 );
				local lead;
				if c <= 59  and
							( c==35 or c==42 or c==58 or c==59 ) then
								lead = true;
				elseif c == 123  or  c == 124 then
								local c2 = r:byte( 2, 1 );
								if c == 123  and  c2 == 124 then
												lead = true;
								elseif ahead  and  c == 124  and
															( c2 == 43  or  c2 == 45  or  c2 == 125 ) then
												lead = true;
								end
				end
				if lead then
								if ahead then
												r = "\n" .. r;
								else
												r = mw.text.nowiki( r:sub( 1, 1 ) )  ..  r:sub( 2 );
								end
				end
				return r;
end -- fallible()
local fiatTitleRegExp = function ( accept )
				-- Create pattern to detect page name
				-- Precondition:
				--     accept  -- string; trimmed title
				-- Postcondition:
				--    Returns string with pattern
				local start = mw.ustring.sub( accept, 1, 1 );
				local r;
				if mw.ustring.match( start, "%a" ) then
								r = string.format( "[%s%s]%s",
																											mw.ustring.lower( start ),
																											mw.ustring.upper( start ),
																											mw.ustring.sub( accept, 2 ) );
				else
								r = accept;
				end
				if r:match( " " ) then
								r = r:gsub( "%", "%%" )
													:gsub( "[%-^.?+*()$]", "%$1" )
													:gsub( "_", " " )
													:gsub( "%s+", "[%s_]+" );
				end
				return r;
end -- fiatTitleRegExp()
local framing = function ( frame )
				-- Ensure availability of frame object
				-- Precondition:
				--     frame  -- object; #invoke environment, or false
				-- Postcondition:
				--     Return frame object
				if not TemplUtl.frame then
								if type( frame ) == "table" then
												TemplUtl.frame = frame;
								else
												TemplUtl.frame = mw.getCurrentFrame();
								end
				end
				return TemplUtl.frame;
end -- framing()
TemplUtl.facets = function ( ask, adjust )
				local r = ask;
				if adjust == "%"  and  r:find( "%%%x%x" ) then
								r = mw.uri.decode( r, "PATH" );
				elseif r:find( "&", 1, true ) then
								r = mw.text.decode( r );
				end
				r = mw.ustring.gsub( r, "[%s%p%c]+", " " );
				r = mw.text.trim( r );
				return r;
end -- TemplUtl.facets()
TemplUtl.faculty = function ( analyze, another )
				-- Test template arg for boolean
				--     analyze  -- string, boolean, number or nil
				--     another  -- fallback: string, boolean, or nil
				--                 "-" to test for explicit vocabulary choice
				-- Returns boolean, or "-"
				local s = type( analyze );
				local r;
				if s == "string" then
								r = mw.text.trim( analyze );
								if r == ""  then
												r = TemplUtl.faculty( another, nil );
								elseif r:find( "1", 1, true )  and
															r:match( "^[0%-]*1[01%-]*$" ) then
												r = true;
								elseif r:match( "^[0%-]+$" ) then
												r = false;
								else
												r = r:lower();
												if r == "y"  or
															r == "yes"  or
															r == "true"  or
															r == "on" then
																r = true;
												elseif r == "n"  or
																			r == "no"  or
																			r == "false"  or
																			r == "off" then
																r = false;
												else
																if not TemplUtl.boolang then
																				-- TODO: page language
																				local l, d = pcall( mw.ext.data.get, "i18n/01.tab" );
																				if type( d ) == "table"  and
																							type( d.data ) == "table" then
																								local f = function ( at )
																																		local e = d.data[ at ];
																																		l = e[ 1 ];
																																		s = e[ 2 ];
																																		if type( l ) == "boolean"  and
																																					type( s ) == "string" then
																																						s = mw.text.split( s, "|" );
																																						for i = 1, #s do
																																										TemplUtl.boolang[ s[ i ] ] = l;
																																						end -- for i
																																		end
																														end
																								TemplUtl.boolang = { };
																								f( 1 );
																								f( 2 );
																				else
																								TemplUtl.boolang = true;
																				end
																end
																if type( TemplUtl.boolang ) == "table" then
																				s = TemplUtl.boolang[ r ];
																				if type( s ) == "boolean" then
																								r = s;
																				end
																end
																if type( r ) ~= "boolean" then
																				s = type( another );
																				if s == "nil" then
																								r = true;
																				elseif s == "boolean" then
																								r = another;
																				elseif s == "string" then
																								s = mw.text.trim( another );
																								if s == "-" then
																												r = "-";
																								elseif s == "" then
																												r = true;
																								else
																												r = TemplUtl.faculty( s );
																								end
																				end
																end
												end
								end
				elseif s == "boolean" then
								r = analyze;
				elseif s == "number" then
								r = ( analyze ~= 0 );
				else
								r = false;
				end
				return r;
end -- TemplUtl.faculty()
TemplUtl.failure = function ( alert, always, addClass, frame )
				-- Format error message, mostly hidden
				--     alert     -- string: message
				--     always    -- boolean, or nil: do not hide
				--     addClass  -- string, or nil: add classes to element
				--     frame     -- object, or nil
				-- Returns string
				local err  = mw.html.create( "span" )
																								:addClass( "error" )
																								:wikitext( alert );
				local live = ( framing( frame ):preprocess( "{{REVISIONID}}" )
																			== "" );
				if type( addClass ) == "string" then
								err:addClass( addClass )
				end
				if live then
								local max  = 1000000000;
								local id   = math.floor( os.clock() * max );
								local sign = string.format( "error_%d", id );
								local btn  = mw.html.create( "span" );
								local top  = mw.html.create( "div" );
								err:attr( "id", sign );
								-- TODO: LTR
								btn:css( { ["background"]      = "#FFFF00",
																			["border"]          = "#FF0000 3px solid",
																			["font-weight"]     = "bold",
																			["padding"]         = "2px",
																			["text-decoration"] = "none" } )
											:wikitext( "&gt;&gt;&gt;" );
								sign = string.format( "[[#%s|%s]]",  sign,  tostring( btn ) );
								top:wikitext( sign, "&#160;", alert );
								mw.addWarning( tostring( top:attr( "role", "alert" ) ) );
				elseif not always then
								err:css( { ["display"]     = "none" } );
--      err:css( { ["display"]     = "inline-block",
--                 ["line-height"] = "0",
--                 ["max-height"]  = "0",
--                 ["max-width"]   = "0",
--                 ["visibility"]  = "hidden" } );
				end
				return tostring( err );
end -- TemplUtl.failure()
TemplUtl.fake = function ( access )
				-- Simulation of template transclusion
				-- Precondition:
				--    access  -- string; page name (template)
				if type( access ) == "string" then
								local s = mw.text.trim( access );
								if s ~= "" then
												local t = mw.title.new( s, 10 );
												if not mw.title.equals( mw.title.getCurrentTitle(), t )  and
															t.exists then
																t:getContent();
												end
								end
				end
end -- TemplUtl.fake()
TemplUtl.fakes = function ( array, frame, ahead, answer )
				-- Simulation of template transclusions
				-- Precondition:
				--    array   -- table, with template title strings
				--    frame   -- object, or nil
				--    ahead   -- string, or nil, with common prefix
				--    answer  -- true, or nil, for list creation
				-- Postcondition:
				--    Returns string, if answer requested
				local e = framing( frame );
				local f = function ( a )
																		e:expandTemplate{ title = a };
														end
				local s = ahead or "";
				local r;
				for k, v in pairs( array ) do
								if type( k ) == "number" and
											type( v ) == "string" then
												v = s .. mw.text.trim( v );
												pcall( f, v );
												if answer then
																if r then
																				r = r .. "\n";
																else
																				r = "";
																end
																r = string.format( "%s* [[Template:%s|%s]]", r, v, v );
												end
								end
				end -- for k, v
				return r;
end -- TemplUtl.fakes()
TemplUtl.feasible = function ( address )
				-- Does this describe an URL beginning?
				-- Precondition:
				--    address  -- string; what to inspect, URL presumed
				-- Postcondition:
				--    Returns true, if URL beginning
				local start, r = address:match( "^%s*((%a*:?)//)" );
				if start then
								if r == "" then
												r = true;
								elseif r:sub( -1, -1 ) == ":" then
												local schemes = ":ftp:ftps:http:https:";
												r = ":" .. r:lower();
												if schemes:find( r, 1, true ) then
																r = true;
												else
																r = false;
												end
								else
												r = false;
								end
				end
				return r;
end -- TemplUtl.feasible()
TemplUtl.feed = function ( area, ahead, at, after )
				-- Detect next free "|" or "}}"
				-- Precondition:
				--     area   -- string; template transclusion
				--     ahead  -- string; opening element, or false
				--     at     -- number; byte position in area where to start
				--     after  -- true, if only to search for "}}"
				-- Postcondition:
				--    Returns
				--          -- number; byte position in area
				--             -- before "|" or "}}", may be at end
				--             -- to continue search; ahead has been closed
				--          -- true, if to be continued at number
				local j    = at;
				local loop = true;
				local c, k, r, s, seek;
				if after then
								seek = "[{}<]";
				else
								seek = "[%[%]|{}<:]";
				end
				while loop do
								j = area:find( seek, j );
								if j then
												c = area:byte( j, j );
												if c == 123 then    -- {
																k = j + 1;
																if area:byte( k, k ) == 123 then
																				k = k + 1;
																				if area:byte( k, k ) == 123 then
																								j, loop = TemplUtl.feed( area, "{{{", k, after );
																				else
																								k = k - 1;
																								j, loop = TemplUtl.feed( area, "{{", k, after );
																				end
																				if not loop then
																								r = j;
																				end
																end
												elseif c == 125 then    -- }
																k = j + 1;
																if area:byte( k, k ) == 125 then
																				if ahead == "{{" then
																								r = k;
																								break;    -- while loop;
																				elseif ahead == "{{{" then
																								k = k + 1;
																								if area:byte( k, k ) == 125 then
																												r = k;
																												break;    -- while loop;
																								end
																				elseif not ahead then
																								r    = j - 1;
																								loop = false;
																				end
																end
												elseif c == 60 then    -- <
																k = j + 3;
																if area:sub( j, k ) == "<!--" then
																				k = area:find( "-->", k );
																				if k then
																								j = k + 2;
																				end
																else
																				local skip;
																				s    = area:sub( j + 1 ):lower();
																				skip = s:match( "^%s*nowiki%s*>" );
																				if skip then
																								local n = skip:len();
																								n, k = s:find( "<%s*/%s*nowiki%s*>", n );
																								if k then
																												j = j + k;
																								else
																												loop = false;
																								end
																				end
																end
												elseif c == 124 then    -- |
																if not r then
																				r = j - 1;
																end
																if not ahead then
																				loop = false;
																end
												elseif c == 91 then    -- [
																k = j + 1;
																if area:byte( k, k ) == 91 then
																				k = k + 1;
																				j, loop = TemplUtl.feed( area, "[[", k, after );
																elseif TemplUtl.feasible( area:sub( k ) ) then
																				k = k + 3;
																				j, loop = TemplUtl.feed( area, "[", k, after );
																end
																if not loop then
																				r = j;
																end
												elseif c == 93 then    -- ]
																if ahead == "[" then
																				r = j;
																				break;    -- while loop
																elseif ahead == "[[" then
																				k = j + 1;
																				if area:byte( k, k ) == 93 then
																								r = k;
																								break;    -- while loop
																				end
																end
												elseif c == 58 then    -- :
																s = area:sub( j + 1,  j + 2 );
																if s == "//" then
																				s = " " .. area:sub( 1,  j + 2 );
																				s = s:match( "%s(%a+://)$" );
																				if s  and  TemplUtl.feasible( s ) then
																								s = area .. " ";
																								s = s:match( "([^%s|]+)%s", j );
																								if s then
																												k = s:find( "}}" );
																												if k then
																																j = j + k + 1;
																												else
																																j = j + s:len();
																												end
																								end
																				end
																end
												end
												j = j + 1;
								else
												loop = false;
								end
				end -- while loop
				if not r then
								r = area:len();
				end
				return r, loop;
end -- TemplUtl.feed()
TemplUtl.feeder = function ( area, at )
				-- Retrieve all parameters
				-- Precondition:
				--     area   -- string; template transclusion
				--     at     -- optional number; byte position in area of "{{"
				-- Postcondition:
				--    Returns
				--          -- table
				--              [0]       -- template, page, parser function name
				--              [1]       -- unnamed parameter
				--              ["name"]  -- named parameter
				--          -- string; error message, if any, else nil
				local n = 0;
				local j, k, p, r, r2, s, v;
				if type( at ) == "number" then
								j = at + 2;
				else
								j = 3;
				end
				while true do
								k = TemplUtl.feed( area, false, j );
								s = area:sub( j, k );
								s = s:gsub( "<!--.*-->", "" );
								if n == 0 then
												r = { [ 0 ] = s };
												n = 1;
								else
												p, v = s:match( "^([^=]*)=(.*)$" );
												if p then
																if p:match( "^%s*%d+%s*$" )  then
																				p = tonumber( p );
																else
																				p = mw.text.trim( p );
																end
																v = mw.text.trim( v );
												else
																p = n;
																v = s;
																n = n + 1;
												end
												if r[ p ] then
																if r2 then
																				r2 = r2 .. " * ";
																else
																				r2 = "";
																end
																r2 = string.format( "%s%s '%s'",
																																				r2,
																																				"duplicated parameter",
																																				tostring( p ) );
												end
												r[ p ] = v;
								end
								s = area:sub( k + 1,  k + 2 );
								if s == "}}" then
												break;    -- while true
								elseif s == "" then
												r2 = "template not closed";
												break;    -- while true
								end
								j = k + 2;
				end -- while true
				return r, r2;
end -- TemplUtl.feeder()
TemplUtl.fetch = function ( area, ask )
				-- Find assignment of a named template parameter
				-- Precondition:
				--     area  -- string; template transclusion
				--     ask   -- string; parameter name
				-- Postcondition:
				--    Returns string with trimmed parameter value, or nil
				--     Does not return value if template inside
				local r;
				local scan = string.format( "%s%s%s",
																																"|%s*", ask, "%s*=(.+)$" );
				r = mw.ustring.match( area, scan );
				if r then
								local j = TemplUtl.feed( r, false, 1 );
								r = r:sub( 1, j );
								if r then
												r = mw.text.trim( r );
												if r == "" then
																r = nil;
												end
								end
				end
				return r;
end -- TemplUtl.fetch()
TemplUtl.find = function ( area, access, at, alter )
				-- Find next occurrence of a template
				-- Precondition:
				--     area    -- string; where to search
				--     access  -- string; trimmed (template) title
				--     at      -- optional number; ustring position in area, if not 1
				--     alter   -- optional string; lowercase namespace pattern
				--                                 "" for article
				--                                 no colon (:)
				-- Postcondition:
				--    Returns ustring position of "{{" in area, or false
				-- Requires:
				--     fiatTitleRegExp()
				local scan = string.format( "{{%s%s%s",
																																"([%w_%s:]*)%s*",
																																fiatTitleRegExp( access ),
																																"%s*([|}<]!?)" );
				local r, space, start, suffix;
				if type( at ) == "number" then
								r = at;
				else
								r = 1;
				end
				while true do
								r = mw.ustring.find( area, scan, r );
								if r then
												start, suffix = mw.ustring.match( area, scan, r );
												if start then
																start = mw.text.trim( start );
																if start == "" then
																				break; -- while true
																elseif alter then
																				if not space then
																								space = string.format( "^:?%s:$", alter );
																				end
																				start = mw.ustring.lower( start );
																				if mw.ustring.match( start, space ) then
																								break; -- while true
																				end
																else
																				start = start:match( "^:?(.+):$" );
																				if start then
																								start = mw.ustring.lower( start );
																								if start == "template" then
																												break; -- while true
																								else
																												if not space then
																																space = mw.site.namespaces[ 10 ].name;
																																space = mw.ustring.lower( space );
																												end
																												start = start:gsub( "_", " " )
																																									:gsub( "%s+", " " );
																												if start == space then
																																break; -- while true
																												end
																								end
																				end
																end
												else
																break; -- while true
												end
												r = r + 2;
								else
												r = false;
												break; -- while true
								end
				end -- while true
				return r;
end -- TemplUtl.find()
-- finder()
--      1 page name
--      2 template title / page name
--      3 4 5 6
--        more like 2
TemplUtl.firstbreak = function ( adjust )
				-- Precede leading character with newline if specific syntax
				-- Precondition:
				--    adjust  -- string; trimmed wikitext
				-- Postcondition:
				--    Returns string, modified if necessary
				return fallible( adjust, true );
end -- TemplUtl.firstbreak()
TemplUtl.flat = function ( area )
				-- Remove syntax elements that hide effective syntax only
				-- Precondition:
				--     area  -- string; unparsed wikitext to be reduced
				-- Postcondition:
				--    Returns cleared wikitext
				local delimiters = { { "<%s*NOWIKI%s*>", "<%s*/%s*NOWIKI%s*>" },
																									{ "<!--", "-->", true },
																									{ "<%s*PRE%s*>", "<%s*/%s*PRE%s*>" },
																									{ "<%s*SYNTAXHIGHLIGHT[^<>]*>",
																											"<%s*/%s*SYNTAXHIGHLIGHT%s*>" } };
				local i          = 1;
				local r          = area;
				local k, m, n;
				if not TemplUtl.Delimiters then
								local c, sD, sP;
								TemplUtl.Delimiters = { };
								for j = 1, #delimiters do
												table.insert( TemplUtl.Delimiters, { } );
												for ji = 1, 2 do
																sD = delimiters[ j ][ ji ];
																sP = "";
																for js = 1, #sD, 1 do
																				c = sD:byte( js, js );
																				if c >= 65  and  c <= 90 then
																								sP = string.format( "%s[%c%c]",
																																												sP,  c,  c + 32 );
																				else
																								sP = sP .. string.char( c );
																				end
																end -- for js
																table.insert( TemplUtl.Delimiters[ j ], sP );
												end -- for ji
								end -- for j
				end
				while ( true ) do
								k = false;
								for j = 1, #delimiters do
												m = r:find( TemplUtl.Delimiters[ j ][ 1 ],
																								i,
																								TemplUtl.Delimiters[ j ][ 3 ] );
												if m  and  ( not k  or  m < k ) then
																k = m;
																n = j;
												end
								end -- for j
								if k then
												local s
												if k > 1 then
																i = k - 1;
																s = r:sub( 1, i );
												else
																s = "";
												end
												j, m  =  r:find( TemplUtl.Delimiters[ n ][ 2 ],
																													k + 1,
																													TemplUtl.Delimiters[ n ][ 3 ] );
												if m then
																r = s  ..  r:sub( m + 1 );
												else
																r = s;
																break; -- while true
												end
								else
												break; -- while true
								end
				end -- while true
				return r;
end -- TemplUtl.flat()
TemplUtl.nowiki1 = function ( adjust )
				-- HTML-escape leading character if disturbing syntax
				-- Precondition:
				--    adjust  -- string; trimmed wikitext
				-- Postcondition:
				--    Returns string, modified if necessary
				return fallible( adjust, false );
end -- TemplUtl.nowiki1()
Failsafe.failsafe = function ( atleast )
				-- Retrieve versioning and check for compliance
				-- Precondition:
				--     atleast  -- string, with required version
				--                         or wikidata|item|~|@ or false
				-- Postcondition:
				--     Returns  string  -- with queried version/item, also if problem
				--              false   -- if appropriate
				-- 2020-08-17
				local since = atleast
				local last    = ( since == "~" )
				local linked  = ( since == "@" )
				local link    = ( since == "item" )
				local r
				if last  or  link  or  linked  or  since == "wikidata" then
								local item = Failsafe.item
								since = false
								if type( item ) == "number"  and  item > 0 then
												local suited = string.format( "Q%d", item )
												if link then
																r = suited
												else
																local entity = mw.wikibase.getEntity( suited )
																if type( entity ) == "table" then
																				local seek = Failsafe.serialProperty or "P348"
																				local vsn  = entity:formatPropertyValues( seek )
																				if type( vsn ) == "table"  and
																							type( vsn.value ) == "string"  and
																							vsn.value ~= "" then
																								if last  and  vsn.value == Failsafe.serial then
																												r = false
																								elseif linked then
																												if mw.title.getCurrentTitle().prefixedText
																															==  mw.wikibase.getSitelink( suited ) then
																																r = false
																												else
																																r = suited
																												end
																								else
																												r = vsn.value
																								end
																				end
																end
												end
								end
				end
				if type( r ) == "nil" then
								if not since  or  since <= Failsafe.serial then
												r = Failsafe.serial
								else
												r = false
								end
				end
				return r
end -- Failsafe.failsafe()
-- Export
local p = { };
function p.facets( frame )
				return TemplUtl.facets( frame.args[ 1 ]  or  "",
																												frame.args.decode );
end -- p.facets
function p.faculty( frame )
				local r = TemplUtl.faculty( frame.args[ 1 ],
																																frame.args[ 2 ] );
				if r ~= "-" then
								r = r and "1";
				end
				return r or "";
end -- p.faculty
function p.failure( frame )
				local scream = mw.text.trim( frame.args[ 1 ]  or  "" );
				local loud   = frame.args[ 2 ];
				local select = frame.args.class;
				if scream == "" then
								scream = "?????????";
				end
				if loud then
								loud = TemplUtl.faculty( loud, nil );
				end
				return TemplUtl.failure( scream, loud, select, frame );
end -- p.failure
function p.fake( frame )
				TemplUtl.fake( frame.args[ 1 ]  or  "",  frame );
				return "";
end -- p.fake
function p.fakes( frame )
				local list = ( frame.args.list == "1" );
				local r    = TemplUtl.fakes( frame.args,
																																	frame,
																																	frame.args.prefix,
																																	list );
				return r or "";
end -- p.fakes
function p.firstbreak( frame )
				local r = ( frame.args[ 1 ] );
				if r then
								r = mw.text.trim( r );
								if r ~= "" then
												r = TemplUtl.firstbreak( r );
								end
				end
				return r or "";
end -- p.firstbreak
function p.from( frame )
				local r = frame:getParent():getTitle();
				if r then
								r = string.format( "&#123;&#123;%s&#125;&#125;", r );
				end
				return r or "";
end -- p.from
function p.isRedirect()
				return mw.title.getCurrentTitle().isRedirect and "1"  or  "";
end -- p.isRedirect
function p.nowiki1( frame )
				local r = ( frame.args[ 1 ] );
				if r then
								r = mw.text.trim( r );
								if r ~= "" then
												r = TemplUtl.nowiki1( r );
								end
				end
				return r or "";
end -- p.nowiki1
p.failsafe = function ( frame )
				-- Versioning interface
				local s = type( frame )
				local since
				if s == "table" then
								since = frame.args[ 1 ]
				elseif s == "string" then
								since = frame
				end
				if since then
								since = mw.text.trim( since )
								if since == "" then
												since = false
								end
				end
				return Failsafe.failsafe( since )  or  ""
end -- p.failsafe
p.TemplUtl = function ()
				return TemplUtl;
end -- p.TemplUtl()
setmetatable( p,  { __call = function ( func, ... )
																																	setmetatable( p, nil );
																																	return Failsafe;
																													end } );
return p;