Modul:Vorlage:Phab: Unterschied zwischen den Versionen

Aus ÖsterreichWiki
Zur Navigation springen Zur Suche springen
wp>PerfektesChaos
(2015-03-23)
wp>PerfektesChaos
(2015-03-25)
Zeile 1: Zeile 1:
--[=[ 2015-03-23
--[=[ 2015-03-25
{{Template:Phab}}
{{Template:Phab}}
]=]
]=]
-- local globals
local Site = "https://phabricator.wikimedia.org/"




Zeile 47: Zeile 52:
         error( r, 0 )
         error( r, 0 )
     end
     end
     r = string.format( "https://phabricator.wikimedia.org/F%d", n )
     r = string.format( "%sF%d", Site, n )
     if args.Show then
     if args.Show then
         show = args.Show
         show = args.Show
Zeile 178: Zeile 183:
         error( r, 0 )
         error( r, 0 )
     end
     end
     r = string.format( "https://phabricator.wikimedia.org/M%d", n )
     r = string.format( "%sM%d", Site, n )
     if args.Show then
     if args.Show then
         show = args.Show
         show = args.Show
Zeile 213: Zeile 218:
     return r
     return r
end -- phabAssigned()
end -- phabAssigned()
local function phabBoards( args )
    -- Link to a board, project, team: tag
    -- Precondition:
    --    args  -- table; assignments
    --                      .Engage
    --              string; single ID
    -- Postcondition:
    --    Returns string URL, if fine
    local s = type( args )
    local r, show, tags
    if s == "table" then
        if type( args.Engage ) == "string" then
            tags = args.Engage
            if args.title and ( args.title ~= "" ) then
                show = args.title
            end
        end
    elseif s == "string" then
        tags = args
    end
    if tags then
        local n
        tags = mw.text.split( tags, "%s+" )
        n    = #tags
        if show  and  n > 1 then
            show = false
        end
        for i = 1, n do
            s = tags[ i ]
            if r then
                r = r .. " | "
            else
                r = ""
            end
            if not show then
                show = "#" .. s
            end
            r = string.format( "%s[%stag/%s %s]",
                              r, Site, s:lower(), show )
        end -- for i
    end
    return r
end -- phabBoards()




Zeile 229: Zeile 280:
         local callsigns = { }
         local callsigns = { }
         local order    = { }
         local order    = { }
        local store = "https://phabricator.wikimedia.org/"
         local sub       = "diffusion/"
         local sub   = "diffusion/"
         local details, pages, s, sign, support
         local details, pages, s, sign, support
         r = "{| class='wikitable sortable'\n" ..
         r = "{| class='wikitable sortable'\n" ..
Zeile 264: Zeile 314:
             show  = callsigns[ sign ]
             show  = callsigns[ sign ]
             r    = string.format( "%s|-\n|[%s%s%s %s]||%s\n",
             r    = string.format( "%s|-\n|[%s%s%s %s]||%s\n",
                                   r, store, sub, sign, sign, show )
                                   r, Site, sub, sign, sign, show )
             pages = { }
             pages = { }
             details = connect[ sign ]
             details = connect[ sign ]
Zeile 275: Zeile 325:
                                             details.support ) )
                                             details.support ) )
             end
             end
             if type( details.bound ) == "table" then
             if type( details.tags ) == "table" then
                 for k, v in pairs( details.bound ) do
                 for k, v in pairs( details.tags ) do
                     table.insert( pages,
                     table.insert( pages, phabBoards( v ) )
                                  string.format( "[%s%s %s]",
                                                store, v, v  ) )
                 end -- for k, v
                 end -- for k, v
             end
             end
Zeile 289: Zeile 337:
                 end
                 end
             end
             end
             if support then
             if support and  support ~= "" then
                 table.insert( pages,
                 table.insert( pages,
                               string.format( "[[mw:%s]]", support ) )
                               string.format( "[[mw:%s]]", support ) )
Zeile 317: Zeile 365:
     --    frame    -- object or nil
     --    frame    -- object or nil
     -- Postcondition:
     -- Postcondition:
     --    Return URL, if identified, or nil
     --    Return       1  -- URL, if identified, or nil
    --                  2  -- callsign, or nil
     -- Uses:
     -- Uses:
     --    phabAssigned()
     --    phabAssigned()
     local translate = phabAssigned( frame )
     local translate = phabAssigned( frame )
     local r
     local r, shortcut
     if translate then
     if translate then
         local shortcut
         local shortcut
Zeile 336: Zeile 385:
         end
         end
         if shortcut then
         if shortcut then
            local store = "https://phabricator.wikimedia.org/"
             local state, sub, swift
             local state, sub, swift
             if adopt ~= "master"  and  adopt ~= "HEAD" then
             if adopt ~= "master"  and  adopt ~= "HEAD" then
Zeile 375: Zeile 423:
                 end
                 end
             end
             end
             r = store .. sub
             r = Site .. sub
         end
         end
    end
    return r, shortcut
end -- phabDiffusion()
local function phabUser( args, frame )
    -- Link to a user
    -- Precondition:
    --    args  -- table; assignments
    --                      .User
    --    frame  -- object or nil
    -- Postcondition:
    --    Returns string URL, if fine
    local r
    local single = args.User
    if type( single ) == "string"  and  single ~= "" then
        local show = args.title
        if not show  or  show == "" then
            show = "@" .. single
        end
        if not frame then
            frame = mw.getCurrentFrame()
        end
        r = string.format( "[%sp/%s %s]",
                          Site,
                          frame:callParserFunction( "urlencode",
                                                    single ),
                          show )
     end
     end
     return r
     return r
end -- phabDiffusion()
end -- phabUser()




Zeile 455: Zeile 532:
         if last then
         if last then
             swift = "history"
             swift = "history"
        else
         end
         end
     end
     end
     if args.title then
    r, show = phabDiffusion( swift, scope, slot, source, jump, since,
                            frame )
     if args.title and ( args.title ~= "" ) then
         show = args.title
         show = args.title
     elseif last then
     elseif last then
Zeile 468: Zeile 546:
         end
         end
     elseif load then
     elseif load then
         show = "GIT:" .. slot:sub( 1, 7 )
         if show then
            show = "r" .. show
        else
            show = "GIT:"
        end
        show = show .. slot:sub( 1, 7 )
     else
     else
         show = scope .. "/*"
         show = scope .. "/*"
     end
     end
    r = phabDiffusion( swift, scope, slot, source, jump, since, frame )
     if not r then
     if not r then
         r = git( swift, scope, slot, source, jump, since )
         r = git( swift, scope, slot, source, jump, since )
            .. "[[Category:Wikipedia:Technik/Phabricator/Diffusion]]"
        .. "[[Category:Wikipedia:Vorlagenfehler/Vorlage:Phab/Diffusion]]"
     end
     end
     return string.format( "[%s %s]", r, show )
     return string.format( "[%s %s]", r, show )
Zeile 507: Zeile 589:
         error( r, 0 )
         error( r, 0 )
     end
     end
     r = string.format( "https://phabricator.wikimedia.org/T%d", n )
     r = string.format( "%sT%d", Site, n )
     if args.Anchor then
     if args.Anchor then
         r = string.format( "%s#anchor-%s", r, args.Anchor )
         r = string.format( "%s#anchor-%s", r, args.Anchor )
Zeile 573: Zeile 655:




local function unified( args )
local function unified( args, frame )
     -- Link to a management issue
     -- Link to a management issue
     -- Precondition:
     -- Precondition:
Zeile 584: Zeile 666:
     --    taskBugzilla()
     --    taskBugzilla()
     --    task()
     --    task()
    --    phabBoards()
    --    phabUser()
     local r
     local r
     lonely( args,
     lonely( args,
             { "Bugzilla", "Countdown", "Differential", "File", "Gerrit",
             { "Bugzilla", "Countdown", "Differential", "Engage",
              "Join", "Mock", "Paste", "Review", "Task" } )
              "File", "Gerrit", "Join", "Mock", "Paste", "Review",
              "Task", "User" } )
     if args.Bugzilla then
     if args.Bugzilla then
         r = taskBugzilla( args )
         r = taskBugzilla( args )
    elseif args.Engage then
        r = phabBoards( args )
     elseif args.File then
     elseif args.File then
         r = file( args )
         r = file( args )
Zeile 596: Zeile 683:
     elseif args.Task then
     elseif args.Task then
         r = task( args )
         r = task( args )
    elseif args.User then
        r = phabUser( args, frame )
     else
     else
         r = "NOT YET READY"
         r = "NOT YET READY"
Zeile 606: Zeile 695:
     --    Paste
     --    Paste
     --    Review
     --    Review
    --    Leerzeichen-getrennte Liste
     return r
     return r
end -- unified()
end -- unified()
Zeile 628: Zeile 718:
                     Countdown    = 1,
                     Countdown    = 1,
                     Differential = 1,
                     Differential = 1,
                    Engage      = 1,
                     File        = 1,
                     File        = 1,
           --        Gerrit      = 1,
           --        Gerrit      = 1,
Zeile 636: Zeile 727:
                     Show        = 1,
                     Show        = 1,
                     Task        = 1,
                     Task        = 1,
                    User        = 1,
                     branch    = 2,
                     branch    = 2,
                     commit    = 2,
                     commit    = 2,

Version vom 26. März 2015, 11:13 Uhr

Modul:Vorlage:LuaModuleDoc:142: attempt to index field 'wikibase' (a nil value)


--[=[ 2015-03-25
{{Template:Phab}}
]=]



-- local globals
local Site = "https://phabricator.wikimedia.org/"



local function concatKeys( args )
    -- Concatenate keys
    -- Precondition:
    --     args   -- table; assignments
    local r
    for k in pairs( args ) do
        if r then
            r = r .. " "
        else
            r = ""
        end
        r = r .. tostring( k )
    end -- for k
    return r
end -- concatKeys()



local function file( args )
    -- Link to a File
    -- Precondition:
    --     args   -- table; assignments
    -- Postcondition:
    --     Returns string with bracketed external link, if fine
    --     Throws error on failure
    local r = args.Task
    local j = r:find( ",", 1, true )
    local n, params, show
    if j then
        params = r:sub( j + 1 )
        r      = r:sub( 1,  j - 1 )
        -- params = mw.text.split( split, "%s*,%s*" )
        -- , width=, height=, size=
        -- , layout, float, alt
    end
    n = r:match( "^[Ff]?([0-9]+)%s*$" )
    if n then
        n = tonumber( n )   -- discard leading zeros
    else
        r = string.format( "Invalid format: 'File=%s'", r )
        error( r, 0 )
    end
    r = string.format( "%sF%d", Site, n )
    if args.Show then
        show = args.Show
    else
        show = string.format( "phab:F%d", n )
    end
    r = string.format( "[%s %s]", r, show )
    return r
end -- file()



local function git( action, assembly, adopt, attach, anchor, against )
    -- Create URL for mediawiki GIT since 2013-06-06
    -- Precondition:
    --     action    -- string; kind of request
    --     assembly  -- string; project
    --     adopt     -- string; branch
    --     attach    -- string; file or directory (or empty)
    --     anchor    -- string; line number (or empty)
    --     against   -- string; diff ./. previous ID (or empty)
    -- Postcondition:
    --     Return URL
    -- Uses:
    --     mw.uri.encode()
    local r = "https://git.wikimedia.org/"
    if action == "file" then
        r = r .. "blob"
        if against then
            r = r .. "diff"
        end
    elseif action == "dir" then
        r = r .. "tree"
    elseif action == "plain" then
        r = r .. "raw"
    elseif action == "commit" or
           action == "commitdiff" or
           action == "history" then
        r = r .. action
    end
    r = r .. "/" .. mw.uri.encode( assembly ) .. "/" .. adopt
    if attach then
        r = r .. "/" .. mw.uri.encode( attach )
    end
    if anchor then
        r = r .. "#L" .. anchor
    end
    return r
end -- git()



local function id40( ask )
    -- Check for 40 hex lowercase id
    -- Precondition:
    --     ask  -- string; project
    -- Postcondition:
    --     Throws error, if failed
    if #ask ~= 40 or ask:match( "^[0-9a-f]+$" ) ~= ask then
        local e = "Invalid identifier: " .. ask
        error( e,  0 )
    end
end -- id40()



local function intersect( args, alone )
    -- Find elements occurring in both args and alone
    -- Precondition:
    --     args   -- table; assignments
    --     alone  -- table (sequence)
    -- Postcondition:
    --     Return table with sequence of matching elements, or false
    local r = { }
    local i
    for k, v in pairs( args ) do
        for i = 1, #alone do
            if k == alone[ i ] then
                table.insert( r, k )
            end
        end -- for i
    end -- for k, v
    if #r == 0 then
        r = false
    end
    return r
end -- intersect()



local function lonely( args, alone )
    -- Find elements occurring in both args and alone
    -- Precondition:
    --     args   -- table; assignments
    --     alone  -- table (sequence)
    -- Postcondition:
    --     Throws error, if occurring in both
    --     Return single matching element, or false
    -- Uses:
    --     intersect()
    --     mw.text.listToText()
    local r = intersect( args, alone )
    if r then
        if #r > 1 then
            r = "Must not be used together: ''"
                .. mw.text.listToText( r,  ", ",  " " ) .. "''"
            error( r, 0 )
        end
        r = r[ 1 ]
    end
    return r
end -- lonely()



local function mock( args )
    -- Link to a Mock
    -- Precondition:
    --     args   -- table; assignments
    -- Postcondition:
    --     Returns string with bracketed external link, if fine
    --     Throws error on failure
    local r = args.Task
    local n = r:match( "^[Mm]?([0-9]+)$" )
    local show
    if n then
        n = tonumber( n )   -- discard leading zeros
    else
        r = string.format( "Invalid format: 'Mock=%s'", r )
        error( r, 0 )
    end
    r = string.format( "%sM%d", Site, n )
    if args.Show then
        show = args.Show
    else
        show = string.format( "phab:M%d", n )
    end
    r = string.format( "[%s %s]", r, show )
    return r
end -- mock()



local function phabAssigned( frame )
    -- Retrieve table with callsign assignments
    -- Precondition:
    --     frame   -- object or nil
    -- Postcondition:
    --     Return table or nil
    --     Throws error on failure
    -- Uses:
    --     mw.getCurrentFrame()
    --     mw.loadData()
    local got, lucky, r, s
    if not frame then
        frame = mw.getCurrentFrame()
    end
    s = frame:getTitle() .. "/callsigns"
    lucky, got = pcall( mw.loadData, s )
    if type( got ) == "table" then
        r = got
    else
        error( "Invalid: " .. s,  0 )
    end
    return r
end -- phabAssigned()



local function phabBoards( args )
    -- Link to a board, project, team: tag
    -- Precondition:
    --     args   -- table; assignments
    --                      .Engage
    --               string; single ID
    -- Postcondition:
    --     Returns string URL, if fine
    local s = type( args )
    local r, show, tags
    if s == "table" then
        if type( args.Engage ) == "string" then
            tags = args.Engage
            if args.title and ( args.title ~= "" ) then
                show = args.title
            end
        end
    elseif s == "string" then
        tags = args
    end
    if tags then
        local n
        tags = mw.text.split( tags, "%s+" )
        n    = #tags
        if show  and  n > 1 then
            show = false
        end
        for i = 1, n do
            s = tags[ i ]
            if r then
                r = r .. " | "
            else
                r = ""
            end
            if not show then
                show = "#" .. s
            end
            r = string.format( "%s[%stag/%s %s]",
                               r, Site, s:lower(), show )
        end -- for i
    end
    return r
end -- phabBoards()



local function phabCallsigns( args, frame )
    -- Create wikitable of all callsigns
    -- Precondition:
    --     args   -- table; options
    --     frame  -- object or nil
    -- Uses:
    --     phabAssigned()
    local connect = phabAssigned( frame )
    local r
    if connect then
        local limit     = ( args.callsigns == "0" )
        local callsigns = { }
        local order     = { }
        local sub       = "diffusion/"
        local details, pages, s, sign, support
        r = "{| class='wikitable sortable'\n" ..
            "|- class='hintergrundfarbe6'\n" ..
            "! Callsign !! Titel !!\n"
        for k, v in pairs( connect ) do
            if k:find( "/", 3, true ) then
                -- GIT legacy
                v = false
            elseif limit  and  type( v ) == "string" then
                details = connect[ v ]
                if type( details ) == "table"   and
                   type( details.list ) == "boolean"   and
                   not details.list then
                    v = false
                end
            end
            if type( v ) == "string" then
                s = callsigns[ v ]
                if s then
                    if #s < #k then
                        callsigns[ v ] = k
                    end
                else
                    callsigns[ v ] = k
                    table.insert( order, v )
                end
            end
        end -- for k, v
        table.sort( order )
        for i = 1, #order do
            sign  = order[ i ]
            show  = callsigns[ sign ]
            r     = string.format( "%s|-\n|[%s%s%s %s]||%s\n",
                                   r, Site, sub, sign, sign, show )
            pages = { }
            details = connect[ sign ]
            if type( details ) ~= "table" then
                details = { }
            end
            if details.support then
                table.insert( pages,
                              string.format( "[[%s]]",
                                             details.support ) )
            end
            if type( details.tags ) == "table" then
                for k, v in pairs( details.tags ) do
                    table.insert( pages,  phabBoards( v ) )
                end -- for k, v
            end
            support = details.mwPage
            if not support then
                s = show:match( "^extension%-([%w_]+)$" )
                if s then
                    support = "Extension:" .. s
                end
            end
            if support  and  support ~= "" then
                table.insert( pages,
                              string.format( "[[mw:%s]]", support ) )
            end
            if #pages > 0 then
                r = string.format( "%s|%s\n",
                                   r,  table.concat( pages, "<br>" ) )
            end
        end -- for i
        r = r .. "|}\n"
    end
    return r
end -- phabCallsigns()



local function phabDiffusion( action, assembly, adopt, attach,
                              anchor, against, frame )
    -- Create URL for Phabricator Diffusion since 2015-03
    -- Precondition:
    --     action    -- string; kind of request
    --     assembly  -- string; project
    --     adopt     -- string; branch
    --     attach    -- string; file or directory (or empty)
    --     anchor    -- string; line number (or empty)
    --     against   -- string; diff ./. previous ID (or empty)
    --     frame     -- object or nil
    -- Postcondition:
    --     Return        1  -- URL, if identified, or nil
    --                   2  -- callsign, or nil
    -- Uses:
    --     phabAssigned()
    local translate = phabAssigned( frame )
    local r, shortcut
    if translate then
        local shortcut
        if assembly:match( "^%u%u%u?%u?$" ) then
            shortcut = assembly
        else
            shortcut = translate[ assembly ]
            if not shortcut  and  assembly:find( "/", 3, true ) then
                -- GIT legacy
                local s = assembly:gsub( "mediawiki/extensions/",
                                         "extension-" )
                shortcut = translate[ s ]
            end
        end
        if shortcut then
            local state, sub, swift
            if adopt ~= "master"  and  adopt ~= "HEAD" then
                state = adopt
            end
            if attach and  not state then
                state = "master"
            end
            if state then
                if action then
                    if action == "commit"   or
                       action == "commitdiff" then
                        swift = action
                        sub   = string.format( "r%s%s",
                                               shortcut, state )
                    elseif action == "history" then
                        swift = action
                    else
                        swift = "browse"
                    end
                else
                    swift = "browse"
                end
            end
            if not sub then
                sub = "diffusion/" .. shortcut
                if swift then
                    sub = string.format( "%s/%s", sub, swift )
                end
                if state then
                    sub = string.format( "%s/%s", sub, state )
                end
                if attach then
                    sub = string.format( "%s/%s", sub, attach )
                    if anchor then
                        sub = string.format( "%s#L%s", sub, anchor )
                    end
                end
            end
            r = Site .. sub
        end
    end
    return r, shortcut
end -- phabDiffusion()



local function phabUser( args, frame )
    -- Link to a user
    -- Precondition:
    --     args   -- table; assignments
    --                      .User
    --     frame  -- object or nil
    -- Postcondition:
    --     Returns string URL, if fine
    local r
    local single = args.User
    if type( single ) == "string"  and  single ~= "" then
        local show = args.title
        if not show  or  show == "" then
            show = "@" .. single
        end
        if not frame then
            frame = mw.getCurrentFrame()
        end
        r = string.format( "[%sp/%s %s]",
                           Site,
                           frame:callParserFunction( "urlencode",
                                                     single ),
                           show )
    end
    return r
end -- phabUser()



local function sourcing( args, frame )
    -- Link to a code source, directory, branch or related
    -- Precondition:
    --     args   -- table; assignments
    --     frame  -- object or nil
    -- Postcondition:
    --     Returns string, if fine
    --     Throws error on failure
    -- Uses:
    --     lonely()
    --     id40()
    --     mw.text.trim()
    --     phabDiffusion()
    --     git()
    local jump   = false
    local last   = false
    local scope  = "mediawiki/core"
    local show
    local since  = false
    local slot   = "HEAD"
    local source = false
    local swift  = "dir"
    local load, r
    r = lonely( args,
                { "branch", "commit", "commitdiff", "diff" } )
    if r then
        slot = args[ r ]
    end
    r = lonely( args,
                { "commit", "commitdiff", "dir", "file", "plain" } )
    if r then
        swift = r
    end
    if args.history then
        last = true
    end
    if args.project then
        scope = args.project
    end
    if args.line or args.diff then
        if not args.file then
            r = " valid for ''file'' only."
            if args.line then
                r = "Number ''line''" .. r
                if args.diff then
                    r = " / " .. r
                end
            end
            if args.diff then
                r = "Version ''diff''" .. r
            end
            error( r, 0 )
        end
        if args.line then
            if args.diff then
                r = "No ''line'' on ''diff'' page available"
                error( r, 0 )
            end
            jump = args.line
        end
    end
    r    = false
    load = ( args.commit or args.commitdiff )
    if load then
        id40( slot )
    elseif args.diff then
        since = args.diff
        id40( since )
    else
        source = args[ swift ]
        if last then
            swift = "history"
        end
    end
    r, show = phabDiffusion( swift, scope, slot, source, jump, since,
                             frame )
    if args.title and ( args.title ~= "" ) then
        show = args.title
    elseif last then
        show = "history: " .. source
    elseif source then
        show = source
        if args.diff then
            show = show .. " ./." .. since:sub( 1, 7 )
        end
    elseif load then
        if show then
            show = "r" .. show
        else
            show = "GIT:"
        end
        show = show .. slot:sub( 1, 7 )
    else
        show = scope .. "/*"
    end
    if not r then
        r = git( swift, scope, slot, source, jump, since )
        .. "[[Category:Wikipedia:Vorlagenfehler/Vorlage:Phab/Diffusion]]"
    end
    return string.format( "[%s %s]", r, show )
end -- sourcing()



local function task( args )
    -- Link to a Task
    -- Precondition:
    --     args   -- table; assignments
    -- Postcondition:
    --     Returns string with bracketed external link, if fine
    --     Throws error on failure
    local r = args.Task
    local j = r:find( "#", 1, true )
    local n, show
    if j then
        local scroll = r:sub( j + 1 )
        r      = r:sub( 1,  j - 1 )
        scroll = scroll:match( "^([0-9]+)$" )
        if scroll  and  not args.Anchor then
            args.Anchor = scroll
        end
    end
    n = r:match( "^[Tt]?([0-9]+)%s*$" )
    if n then
        n = tonumber( n )   -- discard leading zeros
    else
        r = string.format( "Invalid format: 'Task=%s'", r )
        error( r, 0 )
    end
    r = string.format( "%sT%d", Site, n )
    if args.Anchor then
        r = string.format( "%s#anchor-%s", r, args.Anchor )
    end
    if args.Show then
        show = args.Show
    else
        show = string.format( "phab:T%d", n )
        if args.Anchor then
            show = string.format( "%s&#160;#%s", show, args.Anchor )
        end
    end
    r = string.format( "[%s %s]", r, show )
    return r
end -- task()



local function taskBugzilla( args )
    -- Link to an old Bugzilla ticket
    -- Precondition:
    --     args   -- table; assignments
    -- Postcondition:
    --     Returns string, if fine
    --     Throws error on failure
    -- Uses:
    --     task()
    local r = args.Bugzilla
    local j = r:find( "#", 1, true )
    local item, n, show
    if j then
        local scroll = r:sub( j + 1 )
        r      = r:sub( 1,  j - 1 )
        scroll = scroll:match( "^[cC]?([0-9]+)$" )
        if scroll then
            item = tonumber( scroll )
        end
    end
    n = r:match( "^([0-9]+)%s*$" )
    if n then
        n = tonumber( n )   -- discard leading zeros
        if n < 100000 then
            args.Task = tostring( n + 2000 )
        else
            n = false
        end
    end
    if not n then
        r = string.format( "Invalid number: 'Bugzilla=%s'", r )
        error( r, 0 )
    end
    r = string.format( "https://old-bugzilla.wikimedia.org/%s%d",
                       "show_bug.cgi?id=", n )
    show = string.format( "Bugzilla:%d", n )
    if item then
        r    = string.format( "%s#c%d", r, item )
        show = string.format( "%s&nbsp;#c%d", show, item )
    end
    r = string.format( "[%s %s]", r, show )
    r = string.format( "%s <small style=\"font-weight:normal\">(%s)</small>",
                       task( args ),  r )
    return r
end -- taskBugzilla()



local function unified( args, frame )
    -- Link to a management issue
    -- Precondition:
    --     args   -- table; assignments
    -- Postcondition:
    --     Returns string, if fine
    --     Throws error on failure
    -- Uses:
    --     lonely()
    --     taskBugzilla()
    --     task()
    --     phabBoards()
    --     phabUser()
    local r
    lonely( args,
            { "Bugzilla", "Countdown", "Differential", "Engage",
              "File", "Gerrit", "Join", "Mock", "Paste", "Review",
              "Task", "User" } )
    if args.Bugzilla then
        r = taskBugzilla( args )
    elseif args.Engage then
        r = phabBoards( args )
    elseif args.File then
        r = file( args )
    elseif args.Mock then
        r = mock( args )
    elseif args.Task then
        r = task( args )
    elseif args.User then
        r = phabUser( args, frame )
    else
        r = "NOT YET READY"
        error( r, 0 )
    end
    --    Countdown
    --    Differential
    --    Gerrit
    --    Join
    --    Paste
    --    Review
    --    Leerzeichen-getrennte Liste
    return r
end -- unified()



local function main( args, frame )
    -- Do the job
    -- Precondition:
    --     args   -- table; assignments
    --     frame  -- object or nil
    -- Postcondition:
    --     Returns string with link, if fine
    --     Throws error on failure
    -- Uses:
    --     unified()
    --     sourcing()
    local got    = { }
    local mode   = 0
    local params = { Anchor       = 1,
                     Bugzilla     = 1,
                     Countdown    = 1,
                     Differential = 1,
                     Engage       = 1,
                     File         = 1,
          --         Gerrit       = 1,
                     Join         = 1,
                     Mock         = 1,
                     Paste        = 1,
                     Review       = 1,
                     Show         = 1,
                     Task         = 1,
                     User         = 1,
                     branch     = 2,
                     commit     = 2,
                     commitdiff = 2,
                     diff       = 2,
                     dir        = 2,
                     file       = 2,
                     history    = 2,
                     line       = 2,
                     plain      = 2,
                     project    = 2,
                     title      = 2,
                     callsigns = 3 }
    local gr
    for k, v in pairs( args ) do
        mode = params[ k ]
        if not mode then
            mode = -1
        end
        if not got[ mode ] then
            got[ mode ] = { }
        end
        got[ mode ][ k ] = v
    end -- for k, v
    r = got[ -1 ]
    if r then
        local s
        if #r == 1 then
            s = ""
        else
            s = "s"
        end
        r = string.format( "Unknown parameter%s: '%s'",
                           s,  concatKeys( r ) )
        error( r, 0 )
    elseif got[ 1 ] and got[ 2 ] then
        r = string.format( "'%s' conflicting with '%s'",
                           concatKeys( got[ 1 ] ),
                           concatKeys( got[ 2 ] ) )
        error( r, 0 )
    else
        local procs  = { unified, sourcing, phabCallsigns }
        if mode == 0 then
            mode = 2
            got[ 2 ] = { }
        end
        got = got[ mode ]
        for k, v in pairs( got ) do
            if v  and  #v == 0 then
                got[ k ] = nil
            end
        end -- for k, v
        r = procs[ mode ]( got, frame )
    end
    return r
end -- main()



-- Export
local p = {}

function p.test( a )
    local lucky, r = pcall( main, a )
    return r
end

function p.f( frame )
    local lucky, r = pcall( main, frame:getParent().args, frame )
    if not lucky then
        r = string.format( "<span class=\"error\">%s</span>", r )
    end
    return r
end

return p