Module:Database report/sandbox

--[[

Lua module for generating {{database report}} configurations.

This module provides a class-based interface to create database report configurations.

Usage from Lua:

local p = {}
p.main = function() 
  local Report = require('Module:Database report')
  local report = Report:new()
  report:setSQL("SELECT page_title FROM page LIMIT 10")
  report:useWikilinks(1)
  report:setInterval(7)
  return report:generate()
end
return p
]]

local Report = {}
Report.__index = Report

--- Initialize a new database report instance.
-- This is the constructor method that should be called to create a new report instance.
-- @return Report A new Report instance with default settings
-- @usage local report = Report:new()
function Report:new()
    local obj = {
        sql = nil,
        wikilinks = {},
        widths = {},
        comments = {},
        remove_underscores = {},
        hide = {},
        excerpts = {},
        table_style = nil,
        table_class = nil,
        interval = nil,
        pagination = nil,
        max_pages = nil,
        row_template = nil,
        row_template_named_params = nil,
        skip_table = nil,
        header_template = nil,
        footer_template = nil,
        postprocess_js = nil,
        silent = nil
    }
    setmetatable(obj, self)
    return obj
end

--- Set the SQL query for the database report.
-- This is a required parameter.
-- The SQL must be a valid SELECT statement.
-- @param sql string The SQL query to execute (must be non-empty)
-- @return Report Returns self for method chaining
-- @raise error if sql is nil, not a string, or empty
-- @usage report:setSQL("SELECT page_title FROM page LIMIT 10")
function Report:setSQL(sql)
    if not sql or type(sql) ~= "string" or sql:match("^%s*$") then
        error("SQL must be a non-empty string")
    end
    self.sql = sql
    return self
end

--- Configure wikilinks for one or more columns in the report.
-- Wikilinks convert column values into clickable links to other pages.
-- Can be called multiple times to configure different columns.
-- @param column number|table Column number to be wikilinked, or table of column configurations
-- @param namespace number|string Optional namespace number for links. If the namespace number is in another column, use "c1" if it's in the first column, "c2" if it's in the second column, etc.
-- @param show boolean Whether the namespace prefix should be displayed in the link (default: false)
-- @return Report Returns self for method chaining
-- @usage report:useWikilinks(1) -- Simple wikilink for column 1
-- @usage report:useWikilinks(1, 0, true) -- Column 1, namespace 0, show prefix
-- @usage report:useWikilinks({{column=1, namespace="c2"}, {column=3}}) -- Multiple columns
function Report:useWikilinks(column, namespace, show)
    if type(column) == "number" then
        table.insert(self.wikilinks, {
            column = column,
            namespace = namespace,
            show = show
        })
    elseif type(column) == "table" then
        -- Support for multiple wikilinks at once
        for _, link in ipairs(column) do
            table.insert(self.wikilinks, link)
        end
    end
    return self
end

--- Set the width for one or more columns in the report.
-- Controls the display width of columns using CSS width values.
-- Can be called multiple times to set different column widths.
-- @param column number|table Column number to set width for, or table of width configurations
-- @param width string CSS width value (e.g., "10em", "50px", "20%", "200px")
-- @return Report Returns self for method chaining
-- @usage report:setWidth(1, "200px") -- Set column 1 to 200px width
-- @usage report:setWidth({{column=1, width="10em"}, {column=2, width="50%"}}) -- Multiple columns
function Report:setWidth(column, width)
    if type(column) == "number" and type(width) == "string" then
        table.insert(self.widths, {column = column, width = width})
    elseif type(column) == "table" then
        -- Support for multiple widths at once
        for _, w in ipairs(column) do
            table.insert(self.widths, w)
        end
    end
    return self
end

--- Set columns to be treated as comments (edit summaries or log action summaries) in the report.
-- @param ... number Variable number of column numbers to mark as comments
-- @return Report Returns self for method chaining
-- @usage report:setCommentColumns(3, 5) -- Mark columns 3 and 5 as comments
function Report:setCommentColumns(...)
    for _, col in ipairs({...}) do
        if type(col) == "number" then
            table.insert(self.comments, col)
        end
    end
    return self
end

--- Configure columns to have underscores removed from their values.
-- Useful for page titles where underscores should be converted to spaces. For columns with wikilinks or excerpts configured, this will be done automatically.
-- @param ... number Variable number of column numbers to remove underscores from
-- @return Report Returns self for method chaining
-- @usage report:removeUnderscores(1, 2) -- Remove underscores from columns 1 and 2
function Report:removeUnderscores(...)
    for _, col in ipairs({...}) do
        if type(col) == "number" then
            table.insert(self.remove_underscores, col)
        end
    end
    return self
end

--- Hide specified columns from the report display.
-- Hidden columns are still processed but not shown in the final output.
-- Useful for columns used only for processing (like namespace numbers) but not for display.
-- @param ... number Variable number of column numbers to hide
-- @return Report Returns self for method chaining
-- @usage report:hideColumns(2, 4) -- Hide columns 2 and 4 from display
function Report:hideColumns(...)
    for _, col in ipairs({...}) do
        if type(col) == "number" then
            table.insert(self.hide, col)
        end
    end
    return self
end

--- Configure generation of article excerpts (summaries).
-- Creates excerpts from source column that should contain page titles, and places them in a destination column.
-- @param srcColumn number Column containing page title whose excerpt should be created
-- @param destColumn number Destination column number to place the excerpt. 
-- @param namespace number|string Namespace number for the page title (default: 0 for main namespace). If the namespace number is in another column, use "c1" if it's in the first column, "c2" if it's in the second column, etc.
-- @param charLimit number Optional character limit for the excerpt
-- @param charHardLimit number Optional hard character limit (truncates if exceeded)
-- @return Report Returns self for method chaining
-- @usage report:useExcerpt(1, 2) -- Create excerpt for titles in column 1, place in column 2
-- @usage report:useExcerpt(1, 2, 0, 200, 250) -- Create excerpt for titles in column 1 with namespace 2 (user namespace), 200 char limit, 250 hard limit
function Report:useExcerpt(srcColumn, destColumn, namespace, charLimit, charHardLimit)
    table.insert(self.excerpts, {
        srcColumn = srcColumn,
        destColumn = destColumn,
        namespace = namespace,
        charLimit = charLimit,
        charHardLimit = charHardLimit
    })
    return self
end

--- Set the CSS style for the report table.
-- Applies custom CSS styling to the generated table element.
-- @param style string CSS style string to apply to the table
-- @return Report Returns self for method chaining
-- @usage report:setTableStyle("border: 1px solid #ccc; width: 100%")
function Report:setTableStyle(style)
    self.table_style = style
    return self
end

--- Set the CSS class for the report table.
-- Applies a CSS class to the generated table element for styling.
-- @param class string CSS class name to apply to the table
-- @return Report Returns self for method chaining
-- @usage report:setTableClass("wikitable sortable")
function Report:setTableClass(class)
    self.table_class = class
    return self
end

--- Set the update interval for the database report.
-- Controls how often the report is automatically refreshed with new data from the database.
-- @param days number Number of days between updates (must be >= 1)
-- @return Report Returns self for method chaining
-- @raise error if days is not a number or is less than 1
-- @usage report:setInterval(7) -- Update every 7 days
function Report:setInterval(days)
    if type(days) == "number" and days >= 1 then
        self.interval = days
    else
        error("Interval must be a number >= 1")
    end
    return self
end

--- Set the number of rows per page for pagination.
-- Enables pagination by limiting the number of rows displayed per page. By default, no pagination is done.
-- @param count number Number of rows to display per page. 
-- @return Report Returns self for method chaining
-- @raise error if count is not a positive number
-- @usage report:setPagination(1000) -- Show 1000 rows per page
function Report:setPagination(count)
    if type(count) == "number" and count > 0 then
        self.pagination = count
    else
        error("Pagination must be a positive number")
    end
    return self
end

--- Set the maximum number of pages when using pagination.
-- Limits the total number of pages that can be created in a paginated report. The default value is 5. 
-- @param count number Maximum number of pages to display. Should not be greater than 20.
-- @return Report Returns self for method chaining
-- @raise error if count is not a positive number
-- @usage report:setMaxPages(10) -- Limit to 10 pages maximum
function Report:setMaxPages(count)
    if type(count) == "number" and count > 0 then
        self.max_pages = count
    else
        error("Max pages must be a positive number")
    end
    return self
end

--- Set a template to be used for formatting each row.
-- Allows custom formatting of individual rows using MediaWiki templates. The "Template:" prefix can be skipped.
-- @param template string Name of the template to use for row formatting
-- @return Report Returns self for method chaining
-- @usage report:setRowTemplate("MyRowTemplate")
function Report:setRowTemplate(template)
    self.row_template = template
    return self
end

--- Enable use of named parameters in the row template.
-- When enabled, values to the row template are passed by column name instead of position. For a query with columns page_title, page_namespace and rev_len, the row template generated would be {{MyRowTemplate|page_title=...|page_namespace=...|rev_len=...}} instead of {{MyRowTemplate|1=...|2=...|3=...}}.
-- @param value boolean Whether to use named parameters (truthy/falsy)
-- @return Report Returns self for method chaining
-- @usage report:useNamedParamsInRowTemplate(true) -- Use named parameters
function Report:useNamedParamsInRowTemplate(value)
    self.row_template_named_params = value
    return self
end

--- Skip generating the table structure for the report.
-- Useful when row_template is used and the table structure is not needed. 
-- Useful when using custom templates for display.
-- @param value boolean Whether to skip table generation (truthy/falsy)
-- @return Report Returns self for method chaining
-- @usage report:skipTable(true) -- Skip table generation
function Report:skipTable(value)
    self.skip_table = value
    return self
end

--- Set a template to be used for generating the table header.
-- @param template string Name of the template to use for the header
-- @return Report Returns self for method chaining
-- @usage report:setHeaderTemplate("MyHeaderTemplate")
function Report:setHeaderTemplate(template)
    self.header_template = template
    return self
end

--- Set a template to be used as the report footer.
-- This can be used to complement the header template. For example, if the header template is {{div col}}, the footer template can be {{div col end}}.
-- @param template string Name of the template to use for the footer
-- @return Report Returns self for method chaining
-- @usage report:setFooterTemplate("MyFooterTemplate")
function Report:setFooterTemplate(template)
    self.footer_template = template
    return self
end

--- Set JavaScript code to be executed for postprocessing the report content.
-- Allows for arbitrary manipulation of the report data with limited access to Wikimedia APIs.
-- See [[Template:Database report#postprocess_js]] for more details.
-- @param js string JavaScript code
-- @return Report Returns self for method chaining
-- @usage report:setPostprocessJS("console.log('Report loaded');")
function Report:setPostprocessJS(js)
    self.postprocess_js = js
    return self
end

--- Enable or disable silent mode.
-- In silent mode, all visible output from the template is suppressed. Only the generated table is shown.
-- @param value boolean Whether to enable silent mode (truthy/falsy)
-- @return Report Returns self for method chaining
-- @usage report:setSilent(true) -- Enable silent mode
function Report:setSilent(value)
    self.silent = value
    return self
end

--- Generate the complete database report configuration.
-- This is the main method that produces the output for use by the bot.
-- @return string Complete database report template configuration
-- @raise error if validation fails (e.g., missing SQL)
-- @usage return report:generate() -- Return the serialized representation of the report config
function Report:generate()
    if not self.sql then
        error("SQL must be set before generating")
    end
    
    -- Build the configuration
    local config = {
    	sql = self.sql
    }

    -- Add optional parameters
    local optionalParams = {
        wikilinks = formatWikilinks(self.wikilinks),
        comments = formatColumnList(self.comments),
        widths = formatWidths(self.widths),
        table_style = self.table_style,
        table_class = self.table_class,
        excerpts = formatExcerpts(self.excerpts),
        remove_underscores = formatColumnList(self.remove_underscores),
        interval = self.interval,
        pagination = self.pagination,
        max_pages = self.max_pages,
        hide = formatColumnList(self.hide),
        row_template = self.row_template,
        row_template_named_params = self.row_template_named_params,
        skip_table = self.skip_table,
        header_template = self.header_template,
        footer_template = self.footer_template,
        postprocess_js = self.postprocess_js,
        silent = self.silent
    }
    
    -- Add non-empty optional parameters
    for param, value in pairs(optionalParams) do
        if value and value ~= "" then
            config[param] = tostring(value)
        end
    end

    return mw.text.jsonEncode(config, mw.text.JSON_PRETTY)
end

-- Helper function to format wikilinks
function formatWikilinks(wikilinks)
    if not wikilinks or #wikilinks == 0 then return nil end
    
    local parts = {}
    for _, link in ipairs(wikilinks) do
        if type(link) == "table" and link.column then
            local part = tostring(link.column)
            if link.namespace then
                part = part .. ":" .. tostring(link.namespace)
            end
            if link.show then
                part = part .. ":show"
            end
            table.insert(parts, part)
        elseif type(link) == "string" then
            table.insert(parts, link)
        end
    end
    
    return #parts > 0 and table.concat(parts, ", ") or nil
end

-- Helper function to format widths
function formatWidths(widths)
    if not widths or #widths == 0 then return nil end
    
    local parts = {}
    for _, width in ipairs(widths) do
        if type(width) == "table" and width.column and width.width then
            table.insert(parts, tostring(width.column) .. ":" .. width.width)
        elseif type(width) == "string" then
            table.insert(parts, width)
        end
    end
    
    return #parts > 0 and table.concat(parts, ", ") or nil
end

-- Helper function to format column lists
function formatColumnList(columns)
    if not columns or #columns == 0 then return nil end
    
    local parts = {}
    for _, col in ipairs(columns) do
        if type(col) == "number" then
            table.insert(parts, tostring(col))
        elseif type(col) == "string" and col:match("^%d+$") then
            table.insert(parts, col)
        end
    end
    
    return #parts > 0 and table.concat(parts, ", ") or nil
end

-- Helper function to format excerpts
function formatExcerpts(excerpts)
    if not excerpts or #excerpts == 0 then return nil end
    
    local parts = {}
    for _, excerpt in ipairs(excerpts) do
        if type(excerpt) == "table" and excerpt.srcColumn then
            local part = tostring(excerpt.srcColumn)
            if excerpt.destColumn then
                part = part .. ":" .. tostring(excerpt.destColumn)
            end
            if excerpt.namespace then
                part = part .. ":" .. tostring(excerpt.namespace)
            end
            if excerpt.charLimit then
                part = part .. ":" .. tostring(excerpt.charLimit)
            end
            if excerpt.charHardLimit then
                part = part .. ":" .. tostring(excerpt.charHardLimit)
            end
            table.insert(parts, part)
        elseif type(excerpt) == "string" then
            table.insert(parts, excerpt)
        end
    end
    
    return #parts > 0 and table.concat(parts, ", ") or nil
end

return Report

Content Disclaimer

Informasi ini disarikan dari Wikipedia dan disajikan kembali untuk tujuan edukasi. Konten tersedia di bawah lisensi CC BY-SA 3.0. Kami tidak bertanggung jawab atas ketidakakuratan data yang bersumber dari kontribusi publik tersebut.

  1. The information displayed on this website is sourced in part or in whole from Wikipedia and has been adapted for the purpose of restating it. We strive to provide accurate and relevant information, however:
  2. There is no guarantee of absolute accuracy. Wikipedia is an open, collaborative project that can be edited by anyone, so information is subject to change.
  3. It is not intended to constitute professional advice. The content displayed is for informational and educational purposes only. For important decisions (e.g., medical, legal, or financial), please consult a professional.
  4. Content copyright. Wikipedia is licensed under the Creative Commons Attribution-ShareAlike License (CC BY-SA). This means that content may be reused with appropriate attribution and shared under a similar license.
  5. Responsible use. Any risk arising from the use of information from this website is entirely the responsibility of the user.