Modul:Sort/cellDate

From Our Time Travel Wiki

local Sort = { suite = "Sort",

              sub     = "cellDate",
              serial  = "2021-01-01",
              item    = 90149250,
              globals = { Cell     = 90144855,
                          DateTime = 20652535 } }

--[=[ Sort/cellDate -- support table cells with sortable date and time ]=] local Failsafe = Sort local GlobalMod = Sort


Sort.mpz = 0.7 Sort.maxYear = 2099 Sort.minYear = 100 Sort.similar = mw.ustring.char( 8776 ) -- ~~ Sort.supreme = mw.ustring.char( 8734 ) -- infinit Sort.types = { "date",

                   "time",
                   "isoDate",
                   "usLongDate" }

Sort.weights = { } Sort.weights.en = {

   [true]           = Sort.similar .. "abeus",
   ["before"]       = 3,
   ["begin"]        = 4,
   ["begin of"]     = 4,
   ["beginning"]    = 4,
   ["beginning of"] = 4,
   ["since"]        = 6,
   ["until"]        = 7,
   ["end of"]       = 8,
   ["after"]        = 9,
   ["about"]        = true,
   [Sort.similar]   = true

}


local foreignModule = function ( access, advanced, append, alt, alert )

   -- Fetch global module
   -- Precondition:
   --     access    -- string, with name of base module
   --     advanced  -- true, for require(); else mw.loadData()
   --     append    -- string, with subpage part, if any; or false
   --     alt       -- number, of wikidata item of root; or false
   --     alert     -- true, for throwing error on data problem
   -- Postcondition:
   --     Returns whatever, probably table
   -- 2020-01-01
   local storage = access
   local finer = function ()
                     if append then
                         storage = string.format( "%s/%s",
                                                  storage,
                                                  append )
                     end
                 end
   local fun, lucky, r, suited
   if advanced then
       fun = require
   else
       fun = mw.loadData
   end
   GlobalMod.globalModules = GlobalMod.globalModules or { }
   suited = GlobalMod.globalModules[ access ]
   if not suited then
       finer()
       lucky, r = pcall( fun,  "Module:" .. storage )
   end
   if not lucky then
       if not suited  and
          type( alt ) == "number"  and
          alt > 0 then
           suited = string.format( "Q%d", alt )
           suited = mw.wikibase.getSitelink( suited )
           GlobalMod.globalModules[ access ] = suited or true
       end
       if type( suited ) == "string" then
           storage = suited
           finer()
           lucky, r = pcall( fun, storage )
       end
       if not lucky and alert then
           error( "Missing or invalid page: " .. storage )
       end
   end
   return r

end -- foreignModule()


local fetch = function ( access, append )

   -- Fetch global library
   -- Precondition:
   --     access    -- string|false, with name of base module
   --     append    -- string, with subpage part, if any; or false
   local store, sub, suite
   if access then
       suite = access
       store = access
   else
       suite = Sort.suite
       if append then
           sub   = append:lower()
           store = append
       else
           store = "Sorter"
       end
   end
   if type( Sort[ store ] ) == "nil" then
       local bib = foreignModule( suite,
                                  true,
                                  sub,
                                  Sort.globals[ store ],
                                  true )
       if bib  and  type( bib[ suite ] ) == "function" then
           Sort[ store ] = bib[ suite ]()
       else
           error( tostring( bib ) )
       end
   end

end -- fetch()


local fold = function ( access, alien, assign )

   -- Retrieve config table
   -- Precondition:
   --     access    -- string, external table
   --     alien     -- string, language code
   --     assign    -- string, local table
   -- Postcondition:
   --     Returns table, or not
   local r
   Sort[ assign ] = Sort[ assign ]  or  { }
   if type( Sort[ assign ][ alien ] ) == "nil" then
       local data = foreignModule( "DateTime", false, "local" )
       if data  and
          type( data[ access ] ) == "table" then
           Sort[ assign ][ alien ] = data[ access ][ alien ]
       else
           Sort[ assign ][ alien ] = false
       end
   end
   if type( Sort[ assign ][ alien ] ) == "table" then
       r = Sort[ assign ][ alien ]
   end
   return r

end -- fold()


local fore = function ( args )

   -- Create and merge sort attribute
   -- Precondition:
   --     args    -- table, parameters
   --                .d         -- table, with date
   --                .infinit   -- number|nil, out of ages, +/-1
   --                .pre       -- string|false, for prefix
   --                .type      -- string|false, for sorting
   -- Postcondition:
   --     attributes extended
   local d = { lang = args.d.lang }
   local latest, least, s, stamp
   if args.pre then
       local weights = fold( "sortWeights", d.lang, "weights" )
       local i
       if weights then
           i = weights[ args.pre ]
           if type( i ) == "number" then
               if i < 7 then
                  least = true
               else
                  latest = true
               end
           end
       end
   end
   if args.infinit then
       d.hour = 0
       if args.infinit > 0 then
           d.year  = Sort.maxYear
           d.month = 12
           d.dom   = 31
           d.min   = 59
           d.sec   = 59
       else
           d.month = 1
           d.dom   = 1
           d.min   = 0
           d.sec   = 0
           if args.type == "isoDate" then
               d.year = Sort.minYear
           elseif Sort.minYear < 0 then
               d.year = -1 * Sort.minYear
               d.bc   = true
           else
               d.year = Sort.minYear
           end
       end
       stamp = string.format( "%04d-%02d-%02d",
                              d.year, d.month, d.dom )
       if args.type == "isoDate" then
           stamp = string.format( "%sT%02d:%02d:%02d",
                                  stamp, d.hour, d.min, d.sec )
       end
   elseif args.type == "time" then
       if args.d.hour then
           d.hour = args.d.hour
       elseif least then
           d.hour = 0
       elseif latest then
           d.hour = 24
       else
           d.hour = 12
       end
       if args.d.min then
           d.min = args.d.min
       elseif least then
           d.min = 0
       elseif latest then
           d.min = 59
       else
           d.min = 30
       end
       if args.d.sec then
           d.sec = args.d.sec
       elseif least then
           d.sec = 0
       elseif latest then
           d.sec = 59
       else
           d.sec = 30
       end
       stamp = string.format( "%02d:%02d:%02d",
                              d.hour, d.min, d.sec )
   else
       args.d = Sort.DateTime( args.d )
       d.year = ( args.d.year or 0 )
       if args.d.bc  and  args.type == "isoDate" then
           d.bc = true
       else
           d.year = args.d.year or 0
       end
       if args.d.month then
           d.month = args.d.month
       elseif least then
           d.month = 1
       elseif latest then
           d.month = 12
       else
           d.month = 6
       end
       if args.d.dom then
           d.dom = args.d.dom
       elseif least then
           d.dom = 1
       else
           d.dom = tonumber( Sort.DateTime( d ):format( "t" ) )
           if not latest then
               d.dom = math.floor( 0.5 * d.dom )
           end
       end
       stamp = string.format( "%04d-%02d-%02d",
                              d.year, d.month, d.dom )
       if args.type == "isoDate"  and  args.d.hour then
           stamp = string.format( "%sT%02d",
                                  stamp, args.d.hour )
           if args.d.min then
               stamp = string.format( "%s:%02d",
                                      stamp, args.d.min )
               if args.d.sec then
                   stamp = string.format( "%s:%02d",
                                          stamp, args.d.sec )
               end
           end
       end
   end
   if args.type == "isoDate" then
       s = "c"
   elseif args.type == "time" then
       s = "H:i:s"
   elseif args.type == "usLongDate" then
       d.lang = "en"
       s = "F j, Y H:i:s"
   else
       s = "j M Y"
   end
   s = Sort.Cell.formatDate( s, stamp, true )
   if args.infinit then
       if args.infinit > 0 then
           s = s:gsub( tostring( Sort.maxYear ),  "9999" )
       elseif args.type == "isoDate" then
           s = s:gsub( string.format( "^%04d", Sort.minYear ),
                       "-999999" )
       end
   elseif args.type == "isoDate" and d.bc then
       s = "-" .. s
   end
   Sort.Cell.faced( args, s )

end -- fore()


local format = function ( args )

   -- Format visible date
   -- Precondition:
   --     args    -- table, parameters
   --                .d          -- table, with date
   --                .pattern    -- string, with format
   --                .target     -- string|nil, for formatting
   --                .url        -- string|nil, for formatting
   --                .pad        -- boolean, for padding
   --                .pre        -- string, for prefix
   --                .post       -- string, for postfix
   --                .type       -- string, for sorting
   -- Postcondition:
   --     Returns string
   local r, templates
   if not args.d.lang then
       args.d.lang = Sort.Cell.facility()
   end
   if args.pad  and  not args.pre then
       local scheme = args.pattern
       if scheme then
           local templates = fold( "templates",
                                   Sort.Cell.facility(),
                                   "templates" )
           if templates  and
              type( templates[ scheme ] ) == "table"  and
              type( templates[ scheme ].spec ) == "string" then
               scheme = templates[ scheme ].spec
           end
       end
       if scheme then
           local lift
           if args.type == "time" then
               lift = ( scheme:sub( 1, 1 ) == "G"  and
                        args.d.hour  and
                        args.d.hour < 10 )
           else
               lift = ( scheme:sub( 1, 1 ) == "j"  and
                        args.d.dom  and
                        args.d.dom < 10 )
           end
           if lift then
               if not Sort.shift then
                   if Sort.Cell.following() then
                       Sort.shift = "left"
                   else
                       Sort.shift = "right"
                   end
                   Sort.shift = "padding-" .. Sort.shift
               end
               Sort.Cell.feature( args,
                                  Sort.shift,
                                  string.format( "%.2fem !important",
                                                 Sort.mpz ) )
           end
       end
   end
   r = args.d:format( args.pattern or "*" )
   if type( args.target ) == "string" then
       if r == args.target then
           r = string.format( "%s", r )
       else
           r = string.format( "%s", args.target, r )
       end
   elseif type( args.url ) == "string" then
       r = string.format( "[%s %s]", args.url, r )
   end
   if args.pre or args.post then
       local e
       if args.pre then
           r = string.format( "%s %s", args.pre, r )
       end
       if args.post then
           r = string.format( "%s %s", r, args.post )
       end
       e = mw.html.create( "span" )
                  :css( "white-space", "nowrap" )
                  :wikitext( r )
       r = tostring( e )
   end
   return r

end -- format()


local furnish = function ( args )

   -- Execute task
   -- Parameter:
   --     args    -- table, parameters
   -- Postcondition:
   --     Returns string, or expands .cell
   --     Throws error on failure
   local r
   fetch( false, "Cell" )
   fetch( "DateTime" )
   if type( args ) == "table" then
       local present = Sort.Cell.first( args, true )
       local s
       Sort.Cell.fair( args, "d", present )
       if not present.lang then
           present.lang = Sort.Cell.facility()
       end
       if type( present.d ) == "string"  and
          mw.ustring.find( present.d, Sort.supreme, 1, true ) then
           s = mw.text.trim( present.d )
           if s == Sort.supreme then
               present.infinit = 1
           elseif mw.ustring.len( s ) == 2  and
                  mw.ustring.codepoint( s, 2, 2 ) == 8734 then
               local m = mw.ustring.codepoint( s, 1, 1 )
               if m == 45  or  m == 8722 then
                   present.infinit = -1
               elseif m == 43 then
                   present.infinit = 1
               end
           end
       end
       if present.infinit then
           present.d = { lang = Sort.Cell.facility() }
       else
           Sort.Cell.fair( args, "pre", present )
           s = type( present.d )
           if s == "string"  and  not present.pre then
               local weights = fold( "sortWeights",
                                     present.lang,
                                     "weights" )
               if weights  and  weights[ true ] then
                   local sw = weights[ true ]
                   if type( sw ) == "string" then
                       local slim
                       slim = mw.ustring.sub( present.d, 1, 1 )
                       slim = mw.ustring.lower( slim )
                       if mw.ustring.find( sw, slim, 1, true ) then
                           local n
                           for k, v in pairs( weights ) do
                               if type( k ) == "string" then
                                   n    = mw.ustring.len( k )
                                   slim = mw.ustring.sub( present.d,
                                                          1,
                                                          n )
                                   if slim == k then
                                       present.pre = k
                                       present.d   = mw.text.trim(
                                              mw.ustring.sub( present.d,
                                                              n + 1 ) )
                                       break -- for k, v
                                   end
                               end
                           end -- for k, v
                       end
                   end
               end
           end
           if s == "string" then
               if present.d == "" then
                   s = "now"
               else
                   s = present.d
               end
               present.d = Sort.DateTime( s, args.lang )
           elseif s ~= "table" then
               present.d = Sort.DateTime( "now", args.lang )
           end
       end
       if type( present.d ) == "table" then
           if present.d.hour then
               local memory = present.d.sec
               present.d:fix()
               if not memory then
                   present.d.sec = nil
               end
           end
           Sort.Cell.fair( args, "type", present )
           if type( present.type ) == "string" then
               local n
               s = present.type
               n = #s
               if n > 0 then
                   local sort
                   s = s:lower()
                   for i = 1, #Sort.types do
                       sort = Sort.types[ i ]:sub( 1, n ):lower()
                       if s == sort then
                           present.type = Sort.types[ i ]
                           break    -- for i
                       end
                   end -- i = 1, #Sort.types
               end
           end
           if not present.infinit then
               Sort.Cell.fair( args, "pattern", present )
               if present.pattern ~= "-" then
                   if present.pattern then
                       present.pattern =
                          present.pattern:gsub( "\\ ",    " " )
                                         :gsub( " ", " " )
                   end
                   Sort.Cell.fair( args, "target", present )
                   Sort.Cell.fair( args, "url", present )
                   s = type( args.pad )
                   if s == "string" then
                       present.pad = ( args.pad == "1" )
                   elseif s == "boolean" then
                       present.pad = args.pad
                   end
                   Sort.Cell.fair( args, "post", present )
                   r = format( present )
               end
           end
           fore( present )
           r = Sort.Cell.finalize( present, r )
       else
           r = Sort.Cell.fault( "?!?!?!", args )
       end
   else
       error( "'args' is not a table" )
   end
   return r

end -- furnish()


Sort.f = function ( args )

   -- Create table cell start
   -- Parameter:
   --     args    -- table, parameters
   --                .d          -- string|table, with date
   --                .pattern    -- string, with format
   --                .lang       -- string, for formatting
   --                .target     -- string|nil, for formatting
   --                .url        -- strin|nil, for formatting
   --                .pad        -- boolean, for padding
   --                .pre        -- string, for prefix
   --                .post       -- string, for postfix
   --                .cell       -- table|nil, sort environment
   --                .type       -- string, for sorting mode
   --                .rowspan    -- number|string, for cell attribute
   --                .colspan    -- number|string, for cell attribute
   --                .class      -- string, for cell attribute
   --                .style      -- string|table, for cell attribute
   --                .id         -- string, for cell attribute
   --                .dir        -- string, for cell attribute
   --                .cat        -- string|nil, for error category
   -- Postcondition:
   --     Returns string, or expands .cell
   local lucky, r = pcall( furnish, args )
   if not lucky then
       local e = mw.html.create( "span" )
                        :addClass( "error" )
                        :wikitext( "Module:Sort/cell * " .. r )
       if type( args.cell ) == "table"  and
          type( args.cell.wikitext ) == "function" then
           args.cell:node( e )
       else
           r = tostring( e )
       end
   end
   return r

end -- Sort.f()


Sort.furnish = function ()

   -- Retrieve list of project prefixes
   -- Postcondition:
   --     Returns  string  -- with wikitext list
   --              false   -- if none
   local r, weights
   fetch( false, "Cell" )
   weights = fold( "sortWeights", Sort.Cell.facility(), "weights" )
   if weights  and  weights[ true ] then
       local order = { }
       for k, v in pairs( weights ) do
           if type( k ) == "string" then
               table.insert( order, k )
           end
       end -- for k, v
       table.sort( order )
       for i = 1, #order do
           if r then
               r = r .. "\n"
           else
               r = ""
           end
           r = string.format( "%s* %s", r, order[ i ] )
       end -- i = 1, #order
   end
   return r

end -- Sort.furnish()


Failsafe.failsafe = function ( atleast )

   -- Retrieve versioning and check for compliance
   -- Precondition:
   --     atleast  -- string, with required version
   --                         or "wikidata" or "~" or "@" or false
   -- Postcondition:
   --     Returns  string  -- with queried version/item, also if problem
   --              false   -- if appropriate
   -- 2020-08-01
   local last  = ( atleast == "~" )
   local link  = ( atleast == "@" )
   local since = atleast
   local r
   if last  or  link  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 )
           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 link 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
   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 = { }

p.f = function ( frame )

   -- Template call
   Sort.frame = frame
   return Sort.f( frame.args )  or  ""

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

end -- p.failsafe

p.furnish = function ()

   return Sort.furnish()  or  ""

end -- p.f

p.Sort = function ()

   -- Module interface
   return Sort

end

return p