Modul:TemplUtl

Aus FreeWiki
Zur Navigation springen Zur Suche springen

local TemplUtl = { suite = "TemplUtl",

                  serial = "2019-11-20",
                  item   = 52364930 };

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 );
   if c <= 59  and
      ( c==35 or c==42 or c==58 or c==59 ) then
       if ahead then
           r = "\n" .. r;
       else
           r = mw.text.nowiki( r:sub( 1, 1 ) )  ..  r:sub( 2 );
       end
   elseif ahead then
       local c2 = r:byte( 2, 1 );
       if ( c==123 and c2==124 )   or
          ( c==124 and c2==45 ) then
           r = "\n" .. r;
       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 )

   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.faculty = function ( analyze, another )

   -- Test template arg for boolean
   --     analyze  -- string, boolean, number or nil
   --     another  -- fallback: string, boolean, or nil
   -- Returns boolean
   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;
                   else
                       r = TemplUtl.faculty( another );
                   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( "5261" )
                  == "" );
   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( ">>>" );
       sign = string.format( "%s",  sign,  tostring( btn ) );
       top:wikitext( sign, " ", 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* %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 ) == "", 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" or "~"
   --                 or false
   -- Postcondition:
   --     Returns  string  -- with queried version, also if problem
   --              false   -- if appropriate
   local last  = ( atleast == "~" )
   local since = atleast
   local r
   if last  or  since == "wikidata" then
       local item = Failsafe.item
       since = false
       if type( item ) == "number"  and  item > 0 then
           local entity = mw.wikibase.getEntity( string.format( "Q%d",
                                                                item ) )
           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
                   else
                       r = vsn.value
                   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.faculty( frame )

   return TemplUtl.faculty( frame.args[ 1 ],
                            frame.args[ 2 ] )  and  "1"
          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( "{{%s}}", 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()

return p;