Modul:Sort/cellDate

aus Wikipedia, der freien Enzyklopädie
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|%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( "&nbsp;", "&#160;" )
                    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