Module:TemplateData

From Brunswick MD History
Jump to navigation Jump to search

local TemplateData = { serial = "2017-11-06",

                      suite  = "TemplateData" }

local plaintext = require("Module:Plain text") --[=[ improve template:TemplateData ]=] local Config = {

   -- multiple #invoke option names mapped into unique internal fields
   cat            = "strange",
   classNoNumTOC  = "suppressTOCnum",

-- classParams = "classTable",

   cssParams      = "stylesTable",
   cssParWrap     = "stylesTabWrap",
   debug          = false,
   docpageCreate  = "suffix",
   docpageDetect  = "subpage",
   msgDescMiss    = "solo",

-- classTable = false, -- class for params table

   loudly         = false,    -- show exported element, etc.
   solo           = false,    -- complaint on missing description
   strange        = false,    -- title of maintenance category
   stylesTable    = false,    -- styles for params table
   stylesTabWrap  = false,    -- styles for params table wrapper
   subpage        = false,    -- pattern to identify subpage
   suffix         = false,     -- subpage creation scheme
   suppressTOCnum = false    -- class for TOC number suppression

} local Data = {

div = false, --

   got     = false,    -- table, initial templatedata object
   heirs   = false,    -- table, params that are inherited
   less    = false,    -- main description missing
   lasting = false,    -- old syntax encountered
lazy = false, -- doc mode; do not generate effective

Syntax error in JSON.

", j, true )
       if k then
          r = mw.text.trim( s:sub( j + 1,  k - 1 ) )
       end
   end
   return r

end -- find()


local function flat( adjust )

   -- Remove formatting from text string
   -- Parameter:
   --     arglist  -- string, to be stripped, or nil
   -- Returns string, or nil
   local r
   if adjust then
       r = adjust:gsub( "\n", " " )
       if r:find( "<noexport>", 1, true ) then
           r = r:gsub( "<noexport>(.*)</noexport>", "" )
       end
       r = plaintext._main(r)
       if r:find( "&", 1, true ) then
           r = mw.text.decode( r )
       end
   end
   return r

end -- flat()


local function flush()

   -- JSON encode narrowed input; obey unnamed (numerical) parameters
   -- Returns <templatedata> JSON string
   local r
   if Data.tag then
       r = mw.text.jsonEncode( Data.tag ):gsub( "%}$", "," )
   else
       r = "{"
   end
   r = r .. "\n\"params\":{"
   if Data.order then
       local sep = ""
       local s
       for i = 1, #Data.order do
           s   = Data.order[ i ]
           r   = string.format( "%s%s\n%s:%s",
                                r,
                                sep,
                                mw.text.jsonEncode( s ),
                                mw.text.jsonEncode( Data.params[ s ] ) )
           sep = ",\n"
       end -- for i = 1, #Data.order
   end
   r = r .. "\n}\n}"
   return r

end -- flush()


local function focus( access )

   -- Check components; focus multilingual description, build trees
   -- Parameter:
   --     access  -- string, name of parameter, nil for root
   local f = function ( a, at )
                   local r
                   if at then
                       r = string.format( "params.%s", at )
                   else
                       r = "root"
                   end
                   if a then
                       r = string.format( "%s.%s", r, a )
                   end
                   return r
               end
   local parent
   if access then
       parent = Data.got.params[ access ]
   else
       parent = Data.got
   end
   if type( parent ) == "table" then
       local elem, got, permit, s, scope, slot, tag, target
       if access then
           permit = Permit.params
           if type( access ) == "number" then
               slot = tostring( access )
           else
               slot = access
           end
       else
           permit = Permit.root
       end
       for k, v in pairs( parent ) do
           scope = permit[ k ]
           if scope then
               s = type( v )
               if s == "string" then
                   v = mw.text.trim( v )
               end
               if scope:find( s, 1, true ) then
                   if scope:find( "I18N", 1, true ) then
                       if s == "string" then
                           elem = handleNoexportWhitespace( v )
                       else
                           local translated
                           v, translated = faraway( v )
                           if v then
                               if translated  and
                                  k == "description" then
                                   elem = { [ 1 ] = handleNoexportWhitespace( v ),
                                            [ 2 ] = translated }
                               else
                                   elem = handleNoexportWhitespace( v )
                               end
                           else
                               elem = false
                           end
                       end
                       if v then
                           if scope:find( "nowiki", 1, true ) then
                               elem = mw.text.nowiki( v )
                           else
                               v = flat( v )
                           end
                       end
                   else
                       if k == "params"  and  not access then
                           v    = nil
                           elem = nil
                       elseif k == "format"  and  not access then
                           v    = mw.text.decode( v )
                           elem = v
                       elseif k == "inherits" then
                           elem = v
                           if not Data.heirs then
                               Data.heirs = { }
                           end
                           Data.heirs[ slot ] = v
                           v                  = nil
                       elseif s == "string" then
                           v    = mw.text.nowiki( v )
                           elem = v
                       else
                           elem = v
                       end
                   end
                   if type( elem ) ~= "nil" then
                       if not target then
                            if access then
                                if not Data.tree.params then
                                    Data.tree.params = { }
                                end
                                Data.tree.params[ slot ] = { }
                                target = Data.tree.params[ slot ]
                            else
                                Data.tree = { }
                                target    = Data.tree
                            end
                       end
                       target[ k ] = elem
                       elem        = false
                   end
                   if type( v ) ~= "nil" then
                       if not tag then
                           if access then
                               if not Data.params then
                                   Data.params = { }
                               end
                               Data.params[ slot ] = { }
                               tag = Data.params[ slot ]
                           else
                               Data.tag = { }
                               tag      = Data.tag
                           end
                       end
                       tag[ k ] = v
                   end
               else
                   s = string.format( "Type %s bad for %s",
                                      scope,  f( k, slot ) )
                   Fault( s )
               end
           else
               Fault( "Unknown component " .. f( k, slot ) )
           end
       end -- for k, v
   else
       Fault( f() .. " needs to be of object type" )
   end

end -- focus()


local function format()

   -- Build presented documentation
-- Returns
   local r = mw.html.create( "div" )
   local s = feasible( Data.tree, true )
   if s then
       r:node( s )
   end
   if Data.leading then
       local toc = mw.html.create( "div" )
       if Config.suppressTOCnum then
           toc:addClass( Config.suppressTOCnum )
       end
       toc:css( "margin-top", "0.5em" )
:wikitext( "" )
       r:newline()
        :node( toc )
        :newline()
   end
   s = features()
   if s then
       if Data.leading then
           r:node( mw.html.create( "h2" )
                          :wikitext( getLocalizedText( "doc-params" ) ) )
            :newline()
       end
       r:node( s )
   end
   if Data.tree and Data.tree.format then
       local e, style
       s = Data.tree.format:lower( Data.tree.format )
       if s == "inline"  or  s == "block" then
           style = "i"
       else
           style = "code"
       end
       r:node( mw.html.create( "p" )
                      :wikitext( "Format: " )
                      :node( mw.html.create( style )
                                    :wikitext( s ) ) )
   end
   return r

end -- format()


local function free()

   -- Remove JSON comment lines
   Data.source:gsub( "([{,\"'])(%s*\n%s*//.*\n%s*)([},\"'])",
                     "%1%3" )

end -- free()


local function full()

   -- Build HTML table for display from JSON data, and append an invisible
   -- <templatedata> block.
   Data.div = mw.html.create( "div" )
                     :addClass( "mw-templatedata-doc-wrap" )
   focus()
   if Data.tag then
       if type( Data.got.params ) == "table" then
           for k, v in pairs( Data.got.params ) do
               focus( k )
           end -- for k, v
           if Data.heirs then
               fathers()
           end
       end
   end
   Data.div:node( format() )
   if not Data.lazy then
       Data.slim = flush()
       if TemplateData.frame then
           local div   = mw.html.create( "div" )
           local tdata = { [ 1 ] = "templatedata",
                           [ 2 ] = Data.slim }
           Data.strip = TemplateData.frame:callParserFunction( "#tag",
                                                               tdata )
           div:wikitext( Data.strip )
           if Config.loudly then
           	-- Display raw templatedata table all the time.
               Data.div:node( mw.html.create( "hr" ) )
               Data.div:node( div )
           else
           	-- Creates an expand link to check raw templatedata table.
           	local wrapper = mw.html.create( "div" )
               wrapper:addClass( "mw-collapsible" )
               wrapper:addClass( "mw-collapsed" )
               wrapper:css( "font-size", "85%" )
               div:addClass( "mw-collapsible-content" )
               wrapper:wikitext( "Test of raw TemplateData output: " )
               wrapper:node( div )
               Data.div:node( wrapper )
           end
       end
   end

end -- full()


local function furnish( adapt, arglist )

   -- Called by f, this function is the first to do any real work when the
   -- module is invoked.
   -- Parameter:
   --     adapt    -- table, #invoke parameters
   --     arglist  -- table, template parameters
   -- Returns string

--local spy=""

   local source
   for k, v in pairs( Config ) do
       if adapt[ k ]  and  adapt[ k ] ~= "" then
           Config[ v ] = adapt[ k ]
       end
   end -- for k, v
   Config.loudly = faculty( arglist.debug or adapt.debug )

--if mw.site.server:find( "//de.wikipedia.beta.wmflabs.org", 1, true ) then -- Config.loudly = true --end

   Data.lazy     = faculty( arglist.lazy )  and  not Config.loudly
   Data.leading  = faculty( arglist.TOC )
   if arglist.JSON then
       source = arglist.JSON
   elseif arglist[ 1 ] then
       local s     = mw.text.trim( arglist[ 1 ] )
       local start = s:sub( 1, 1 )
       if start == "<" then
           Data.strip = s
       elseif start == "{" then
           source = s
       elseif mw.ustring.sub( s, 1, 8 ) ==
              mw.ustring.char( 127, 39, 34, 96, 85, 78, 73, 81 ) then --  ' " ` U N I Q
           Data.strip = s
       end
   end
   if not source then
       Data.title = mw.title.getCurrentTitle()
       source = find()
       if not source  and
          Config.subpage  and  Config.suffix  and
          not Data.title.text:match( Config.subpage ) then
           local s = string.format( Config.suffix,
                                    Data.title.prefixedText )
           Data.title = mw.title.new( s )
           if Data.title.exists then
               source = find()
           end
       end

--if source and -- ( source:find( "|", 1, true ) or -- source:find( "}}", 1, true ) ) then -- -- <ref --spy=string.format( "", Config.strange ) --end

   end
   if not Data.lazy  and  Config.subpage then
       if not Data.title then
           Data.title = mw.title.getCurrentTitle()
       end
       Data.lazy = Data.title.text:match( Config.subpage )
   end
   TemplateData.getPlainJSON( source )
   return finalize()

--return spy .. finalize() end -- furnish()


TemplateData.failsafe = function ( assert ) -- Checks the age of this implementation against some minimum ("assert").

   local r
   if not assert  or  assert <= TemplateData.serial then
       r = TemplateData.serial
   else
       r = false
   end
   return r

end -- TemplateData.failsafe()


TemplateData.getPlainJSON = function ( adapt )

   -- Reduce enhanced JSON data to plain text localized JSON
   -- Parameter:
   --     adapt  -- string, with enhanced JSON
   -- Returns string, or not
   if type( adapt ) == "string" then
       Data.source = adapt
       free()
       Data.got = mw.text.jsonDecode( Data.source )
       if Data.got then
           full()
           if Data.lasting then
               Fault( "deprecated type syntax" )
           end
           if Data.less then
               Fault( Config.solo )
           end
       elseif not Data.strip then
           Fault( "fatal JSON error" )
       end
   end
   return Data.slim

end -- TemplateData.getPlainJSON()


TemplateData.test = function ( adapt, arglist )

   TemplateData.frame = mw.getCurrentFrame()
   return furnish( adapt, arglist )

end -- TemplateData.test()


-- Export local p = { }

p.f = function ( frame )

   -- The entry point for templates invoking the module.
   -- Just wraps furnish in an exception handler.
   local lucky, result
   TemplateData.frame = frame
   lucky, result = pcall( furnish, frame.args, frame:getParent().args )
   if not lucky then
       Fault( "INTERNAL: " .. result )
       result = failures()
   end
   return result

end -- p.f()

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 TemplateData.failsafe( since )  or  ""

end -- p.failsafe()

p.TemplateData = function ()

   -- Module interface
   return TemplateData

end

return p