Module:Unimplemented

From Rosetta Code

Module:Unimplemented builds lists of links to programming tasks that have not yet been implemented in a programming language. This module is invoked from Template:Unimpl_Page.

We work around Semantic MediaWiki's limitations by deriving a list of unimplemented tasks from multiple SMW inline queries.

Usage

Invoke Module:Unimplemented with one of the tasks, drafts or omitted functions and the target language. Here we've used TypeScript as the example language.

{{#invoke:Unimplemented|tasks|TypeScript}}
{{#invoke:Unimplemented|drafts|TypeScript}}
{{#invoke:Unimplemented|omitted|TypeScript}}

Combined unimplemented task page

Unimplemented|page|<language> combines unimplemented tasks, draft tasks and omitted tasks into one function call. It includes sub headings for each list and formats those lists grouped by the first character in a task's title.

{{#invoke:Unimplemented|page|TypeScript}}

Invoking from a template

When invoking Module:Unimplemented from a template and using template parameters as arguments, it is probably a good idea to wrap each call to #invoke in <includeonly></includeonly>. This should prevent unnecessary function calls and improve page load times when viewing the template page directly.

See Control template inclusion for more information.


local p = {}
local SEP = "|"

--- Return the sequence of elements in `a` that are not in `b`.
-- Assumes that `a` and `b` are "arrays" with distinct items
-- that are already sorted. If `a` or `b` are empty, return `a`
-- without copying it.
local function difference(a, b)
    local size_a = #a
    local size_b = #b

    if size_a == 0 or size_b == 0 then return a end

    local i = 1
    local j = 1
    local result = {}

    while i <= size_a and j <= size_b do
        local a_i = a[i]
        local b_j = b[j]

        if a_i == b_j then
            i = i + 1
            j = j + 1
        elseif a_i < b_j then
            table.insert(result, a_i)
            i = i + 1
        else
            j = j + 1
        end
    end

    -- and the rest of a, if a is longer than b
    while i <= size_a do
        table.insert(result, a[i])
        i = i + 1
    end

    return result
end

--- Split the input string on the configured separator token `SEP`.
--
-- Don't be tempted to replace this with `mw.text.split`. As of Feb 2023,
-- `mw.text.split` is particularly slow, causing this this module to
-- timeout when invoked.
--
-- Returns an array of strings split on `SEP`.
local function split(str)
    local result = {}
    for match in string.gmatch(str, "([^" .. SEP .. "]+)") do
        table.insert(result, match)
    end
    table.sort(result)
    return result
end

--- Call `#ask` repeatedly until we've got all results defined by `args`.
-- This works around the SMW result limit of 500 records.
--
-- Returns the concatenation of `#ask` results separated by `args.sep`.
-- Assumes `args.format` is "plainlist" and `args.sep` is set. It is not
-- safe to reuse `args` after calling this function.
local function ask_all(args)
    local frame = mw.getCurrentFrame()
    local limit = tonumber(args.limit or 500)
    local offset = tonumber(args.offset or 0)
    local strings = {}
    local response = ""

    args.limit = tostring(limit) -- TODO: do we need to cast to string?
    args.offset = tostring(offset)

    repeat
        response = frame:callParserFunction { name = "#ask", args = args }
        offset = offset + limit
        args.offset = tostring(offset)
        if #response > 0 then
            table.insert(strings, args.sep)
            table.insert(strings, response)
        end
    until #response == 0

    return table.concat(strings)
end

--- Return an array of task titles implemented in the given language.
-- Includes draft tasks.
local function language_tasks(language)
    local args = {
        "[[Implemented in language::" .. language .. "]]",
        "?Title",
        format = "plainlist",
        limit = "500",
        link = "none",
        sep = SEP,
        searchlabel = ""
    }
    return split(ask_all(args))
end

--- Return an array of task titles omitted from the given language.
-- Includes draft tasks.
local function omitted_language_tasks(language)
    local args = {
        "[[Category:" .. language .. "/Omit]]",
        "?Title",
        format = "plainlist",
        limit = "500",
        link = "none",
        sep = SEP,
        searchlabel = ""
    }
    return split(ask_all(args))
end

-- Return an array of task titles in the Programming Tasks category.
local function programming_tasks()
    local args = {
        "[[Category:Programming Tasks]]",
        "?Title",
        format = "plainlist",
        limit = "500",
        link = "none",
        sep = SEP,
        searchlabel = ""
    }
    return split(ask_all(args))
end

-- Return an array of task titles in the Draft Programming Tasks category.
local function draft_tasks()
    local args = {
        "[[Category:Draft Programming Tasks]]",
        "?Title",
        format = "plainlist",
        limit = "500",
        link = "none",
        sep = SEP,
        searchlabel = ""
    }
    return split(ask_all(args))
end

--- Format an array of task titles as a continuous unordered list.
local function format(tasks)
    local strings = {}
    for _, task in ipairs(tasks) do
        table.insert(strings, "* [[" .. task .. "]]")
    end
    return table.concat(strings, "\n")
end

--- Format an array of task titles into lists grouped by their first
-- character.
local function format_with_group_headings(tasks)
    if #tasks == 0 then
        return ""
    end

    local wiki_markup = {}
    local ch = ""

    for _, task in ipairs(tasks) do
        local task_ch = string.upper(string.sub(task, 1, 1))
        if ch ~= task_ch then
            table.insert(wiki_markup, "=== " .. task_ch .. " ===")
            ch = task_ch
        end
        table.insert(wiki_markup, "* [[" .. task .. "]]")
    end

    return table.concat(wiki_markup, "\n")
end


--- Return the target language given a frame object.
local function language_arg(frame)
    local language = frame.args[1]
    if language == nil then
        error("too few arguments, a programming language is required", 2)
    end
    return language
end

--- Display a list of programming tasks not implemented in a given language.
-- Usage: `{{#invoke:Unimplemented|tasks|<language>}}` where `<language>` is
-- the Rosetta Code language category name.
--
-- For example `{{#invoke:Unimplemented|tasks|TypeScript}}`
function p.tasks(frame)
    local language = language_arg(frame)
    local implemented = language_tasks(language)
    local omitted = omitted_language_tasks(language)
    local tasks = programming_tasks()
    local unimplemented = difference(difference(tasks, implemented), omitted)
    return format(unimplemented)
end

--- Display a list of draft programming tasks not implemented in a given
-- language. Usage: `{{#invoke:Unimplemented|drafts|<language>}}` where
-- `<language>` is the Rosetta Code language category name.
--
-- For example `{{#invoke:Unimplemented|drafts|TypeScript}}`
function p.drafts(frame)
    local language = language_arg(frame)
    local implemented = language_tasks(language)
    local omitted = omitted_language_tasks(language)
    local tasks = draft_tasks()
    local unimplemented = difference(difference(tasks, implemented), omitted)
    return format(unimplemented)
end

--- Display a list of tasks omitted from a given language, including draft tasks.
-- Usage: `{{#invoke:Unimplemented|omitted|<language>}}` where `<language>`
-- is the Rosetta Code language category name.
--
-- For example `{{#invoke:Unimplemented|omitted|TypeScript}}`
function p.omitted(frame)
    local language = language_arg(frame)
    local omitted = omitted_language_tasks(language)
    return format(omitted)
end

--- Display lists of unimplemented tasks, unimplemented drafts and omitted
-- tasks for a given language, including wiki headings.
--
-- Usage: `{{#invoke:Unimplemented|page|<language>}}` where `<language>`is the
-- Rosetta Code language category name.
--
-- For example `{{#invoke:Unimplemented|page|TypeScript}}`
function p.page(frame)
    local language = language_arg(frame)
    local implemented = language_tasks(language)
    local omitted = omitted_language_tasks(language)

    local unimplemented_tasks = difference(
        difference(programming_tasks(), implemented),
        omitted
    )

    local unimplemented_drafts = difference(
        difference(draft_tasks(), implemented),
        omitted
    )

    local wiki_markup = {
        "==Tasks not implemented in " .. language .. "==",
        format_with_group_headings(unimplemented_tasks),
        "==Draft tasks not implemented in " .. language .. "==",
        format_with_group_headings(unimplemented_drafts),
        "==Tasks omitted from " .. language .. "==",
        format_with_group_headings(omitted)
    }

    return table.concat(wiki_markup, "\n")
end

return p