Module:Lucifer authors

Documentation for this module may be created at Module:Lucifer authors/doc

local p = {}
local FS, RS, AS = mw.ustring.char(0x2063), mw.ustring.char(0x2064), mw.ustring.char(0x2062)

local function split(s, sep)
    local out, start = {}, 1
    while true do
        local a, b = string.find(s, sep, start, true)
        if not a then table.insert(out, string.sub(s, start)); return out end
        table.insert(out, string.sub(s, start, a - 1)); start = b + 1
    end
end

local function trim(s) return mw.text.trim(s or '') end

-- Legacy records sometimes separate linked coauthors with commas rather than
-- the property's semicolon separator. Never split commas inside a page name.
local function authors(value)
    local out = {}
    for _, part in ipairs(split(value, AS)) do
        if part:find('[[', 1, true) then
            local start = 1
            while true do
                local a, b = part:find('%[%[[^]]+%]%]', start)
                local rest = part:sub(start, a and a - 1 or #part)
                rest = trim(rest:gsub('^[%s,;]+', ''):gsub('[%s,;]+$', ''))
                if rest ~= '' and rest ~= 'and' then table.insert(out, rest) end
                if not a then break end
                table.insert(out, part:sub(a, b))
                start = b + 1
            end
        else
            table.insert(out, part)
        end
    end
    return out
end

local function fetch(frame)
    local rows, offset = {}, 0
    for batch = 1, 100 do
        local query = '{{#ask:[[Lucifer item author::+]]'
            .. '|?Lucifer item author=a|+sep=' .. AS
            .. '|?Lucifer item ID=id|?Lucifer item title=t'
            .. '|?Lucifer item volume=v|?Lucifer item number=n|?Lucifer item page=pg'
            .. '|?Lucifer item month=m|?Lucifer item year=y'
            .. '|?Lucifer item links to text=links|+sep=; '
            .. '|mainlabel=-|link=none|headers=hide|named args=yes'
            .. '|format=template|template=Lucifer authors raw row'
            .. '|sort=Lucifer item ID|order=asc|searchlabel=|limit=500|offset=' .. offset .. '}}'
        local text = frame:preprocess(query):gsub('%[%[SMW::off%]%]', ''):gsub('%[%[SMW::on%]%]', '')
        local count = 0
        for _, record in ipairs(split(text, RS)) do
            local f = split(record, FS)
            if #f == 9 and trim(f[2]) ~= '' then
                table.insert(rows, {a=trim(f[1]), id=f[2], t=f[3], v=f[4], n=f[5], pg=f[6], m=f[7], y=f[8], links=f[9]})
                count = count + 1
            end
        end
        if count == 0 then return rows end
        offset = offset + count
    end
    error('Lucifer author index exceeds the pagination safety limit.')
end

local function group(rows)
    local map, order = {}, {}
    for _, r in ipairs(rows) do
        local seen = {}
        for _, raw in ipairs(authors(r.a)) do
            raw = trim(raw)
            local target = raw:match('^%[%[([^]|]+)|[^]]+%]%]$') or raw:match('^%[%[([^]]+)%]%]$')
            local name = trim(target or raw)
            if name ~= '' and not seen[name] then
                seen[name] = true
                if not map[name] then
                    map[name] = {target=target, label=raw, items={}}
                    table.insert(order, name)
                end
                table.insert(map[name].items, r)
            end
        end
    end
    table.sort(order, function(a,b) return mw.ustring.lower(a) < mw.ustring.lower(b) end)
    return map, order
end

function p.list(frame)
    local map, order = group(fetch(frame))
    local out = {}
    for _, name in ipairs(order) do
        local author = map[name]
        local label = author.label
        if author.target then
            label = frame:expandTemplate{title='CTD show name', args={[1]=author.target, mode='full LF'}}
            label = '[[' .. author.target .. '|' .. label .. ']]'
        end
        table.insert(out, '<h3>' .. label .. '</h3>\n<ol>')
        table.sort(author.items, function(a,b)
            if a.t == b.t then return a.id < b.id end
            return mw.ustring.lower(a.t) < mw.ustring.lower(b.t)
        end)
        for _, r in ipairs(author.items) do
            local location = 'Lucifer/Vol' .. r.v .. '#n' .. r.n
            local details = 'vol. ' .. r.v .. ', no. ' .. r.n .. ', ' .. r.m .. ' ' .. r.y .. ', pp. ' .. r.pg
            if not r.pg:find('-', 1, true) then details = details:gsub(', pp%. ', ', p. ') end
            local links = trim(r.links) ~= '' and ('; ' .. r.links) or ''
            table.insert(out, '<li><i>' .. r.t .. '</i> <span style="color:grey; font-size:85%;">([[' .. location .. '|' .. details .. ']])' .. links .. '</span></li>')
        end
        table.insert(out, '</ol>')
    end
    return table.concat(out, '\n')
end

function p.debug(frame)
    local rows = fetch(frame)
    local _, order = group(rows)
    return 'Total rows fetched: ' .. #rows .. '; unique authors: ' .. #order
end

return p