Module:Custom functions
Appearance
Documentation for this module may be created at Module:Custom functions/doc
local cargo = mw.ext.cargo -- for cargo queries if needed
local html = mw.html.create
local cf = {}
--[[ This function takes a cargo results array and a flat, 1D table
containing strings representing field names and produces a nested bulleted wiki text list for display
--]]
function cf.bulleted_list_multi(dict, field_names, show_field_names)
-- Set default value for show_field_names if not provided
if show_field_names == nil then
show_field_names = true
end
local hierarchy = {}
-- Build the hierarchy based on the dict and field_names
for _, entry in ipairs(dict) do
local current_node = hierarchy
for _, field_name in ipairs(field_names) do
-- Skip empty fields to omit them from the list
if entry and entry[field_name] and entry[field_name] ~= "" then
local value = entry[field_name]:gsub("%**$", "") -- Remove trailing asterisks
current_node[value] = current_node[value] or {}
current_node = current_node[value]
else
-- If the field is empty, skip this iteration
break
end
end
end
-- Recursive function to render the hierarchy as a nested bulleted list using mw.html
local function recursive_list_render(node, depth)
local sorted_keys = {}
for key, _ in pairs(node) do
table.insert(sorted_keys, key)
end
table.sort(sorted_keys)
local list = mw.html.create('ul')
for _, key in ipairs(sorted_keys) do
local list_item = mw.html.create('li')
-- Determine if field names should be shown
local display_text
if show_field_names then
local field_name_display = field_names[depth]:gsub("_", " ") -- Replace underscores with spaces
display_text = field_name_display .. ": " .. key
else
display_text = key
end
-- Use mw.html:wikitext to add the content as wikitext
if depth == #field_names and (string.lower(field_names[depth]) == "species" or string.lower(field_names[depth]) == "genus") then
list_item:wikitext("''" .. display_text .. "''")
else
list_item:wikitext(display_text)
end
local sub_list = recursive_list_render(node[key], depth + 1)
if sub_list then
list_item:node(sub_list)
end
list:node(list_item)
end
return (#sorted_keys > 0) and list or nil
end
return tostring(recursive_list_render(hierarchy, 1))
end
--[[
This function takes a comma-delimited string and converts it into a table
of trimmed values. Each value in the input string is separated by a comma,
and leading and trailing whitespace from each value is removed.
Parameters:
csv_string (string|nil): A comma-delimited string containing values to be parsed.
If nil, an empty table is returned.
Returns:
table: A table containing the trimmed values from the input string.
If the input is nil, an empty table is returned.
Example usage:
local result = cf.parse_csv_to_table(" value1 , value2 ,value3 ")
-- result: {"value1", "value2", "value3"}
local empty_result = cf.parse_csv_to_table(nil)
-- empty_result: {}
--]]
function parse_csv_to_table(csv_string)
-- Return an empty table if the input is nil
if csv_string == nil then
return {}
end
-- Check if the argument is a string
if type(csv_string) ~= "string" then
error("Invalid argument: csv_string must be a string")
end
local t = {}
for field in csv_string:gmatch("[^,]+") do
table.insert(t, mw.text.trim(field))
end
return t
end
cf.parse_csv_to_table = parse_csv_to_table
-- takes an array and surrounds its elements with specified characters
function cf.element_sandwicher(array, start_char, end_char)
local result = {}
local length = #array
for i = 1, length do
result[i] = start_char .. array[i] .. end_char
end
return result
end
--[[ this function finds thing that are in the first array that are not in the second.
I first used it making custom pages that display orhpaned Data namespace pages that can't find
their forward facing partner
--]]
function cf.in_a_not_b(a, b)
local result = {}
local set_b = {}
-- Create a set of unique elements in table b for efficient lookup
for _, value in ipairs(b) do
set_b[value] = true
end
-- Create a set of unique elements in table a without duplicates
local set_a = {}
for _, value in ipairs(a) do
if not set_a[value] then
set_a[value] = true
-- Check if the element in table a is not present in table b and collect non-matching elements
if not set_b[value] then
table.insert(result, value)
end
end
end
return result
end
-- This parses text that is a single text string separated by commas into an array
function cf.parse_arguments(text, format, link_text)
if text then
local text_table = {} -- Table to store parsed text elements
-- Iterate over the delimited string and extract each text element
for item in text:gmatch("[^,]+") do
if format == "page" then
item = '[[' .. item .. ']]' -- Surround item with double brackets
elseif format == "ext_link" then
item = "[" .. item .. " " .. link_text .. "]"
elseif format == "italic page" then
item = "<i>[[" .. item .. "]]</i>"
else
item = item:gsub(",", ", ") -- Add a space after each comma
end
table.insert(text_table, item)
end
-- Iterate over the arguments table and concatenate them into a string
local full_text = table.concat(text_table, ", ")
return full_text
else
return full_text
end
end
-- Helper function to get keys from a table
function keys(t)
local keyset = {}
local n = 0
for k, _ in pairs(t) do
n = n + 1
keyset[n] = k
end
return keyset
end
--[[ function that parses a cargo_query result and converts it from a key/value
dictionary to a simple array where each element in the array has
a the raw text corresponding to the final table to display the 'fields' parameter
is necessary since the order of fields in Lua tables is not guaranteed
since they are implemented as hash tables. --]]
function cf.format_cargo_results(results, field_formats)
-- Preserve the original order of the keys
local originalKeyOrder = {}
for key in string.gmatch(field_formats, "%s*([^,]+)") do
table.insert(originalKeyOrder, key)
end
local table_data = {}
-- Insert the keys as the first row in table_data, following the original order
local columnHeaders = {}
for _, originalKey in ipairs(originalKeyOrder) do
table.insert(columnHeaders, originalKey)
end
table.insert(table_data, columnHeaders)
-- Insert the rows
for _, row in ipairs(results) do
local rowData = {}
for _, key in ipairs(originalKeyOrder) do
table.insert(rowData, row[key])
end
table.insert(table_data, rowData)
end
return table_data
end
-- Loop through the rows in an input array and remove contiguous duplicates in each field
function cf.dupes_remover(input_array)
local prev_values = {}
for i, row in ipairs(input_array) do
for j, field in ipairs(row) do
-- Check if the current field value is the same as the previous one for this column
if i > 1 and field == prev_values[j] then
-- If so, set the field value to an empty string
row[j] = ""
else
-- If not, update the previous value for this column
prev_values[j] = field
end
end
end
return input_array
end
--[[ Loop through the cells in an array before displaying on a forward-facing
page and surrounding brackets to make them wiki page links. Default behvior
will convert all columns or a list of only columns desired can be specified.
header = false will not skip the first row.]]
function cf.bracketer(input_array, columns)
local result = {}
local column_indices = {}
for i, row in ipairs(input_array) do
local modified_row = {}
for j, value in ipairs(row) do
local modified_value = value
-- Add brackets to the specified column values if columns are provided and the value is not blank or nil
if column_indices[j] and value ~= "" and value ~= nil then
modified_value = "[[" .. value .. "]]"
end
table.insert(modified_row, modified_value)
end
table.insert(result, modified_row)
-- Process the first row to determine column indices
if i == 1 then
-- Identify the column indices to modify based on the provided column names or bracket all columns by default
if columns then
for k, column in ipairs(row) do
if string.find(columns, column) then
column_indices[k] = true
end
end
else
for k = 1, #row do
column_indices[k] = true
end
end
end
end
return result
end
-- functiont that extracts the official versions of the site names on the wiki
function cf.get_site_name()
-- query cargo to get official compendium names
local tables = "Compendium_info"
local fields = "Site_number, Site_name, Site_name_long"
local cargo_results = cargo.query(tables, fields)
-- initialize the variables
local site = {}
-- for loop to extract the results from the cargo.query output
for _, row in ipairs(cargo_results) do
local site_num = tonumber(row.Site_number)
site[site_num] = {
name = row.Site_name,
name_long = row.Site_name_long
}
end
return site
end
-- simple function to capitalize only the first character of a string
function cf.first_char_caps(str)
return str:gsub("^%l", string.upper)
end
-- simple function to return the field names of a returned Cargo query
function cf.get_field_names(cargo_query)
for i, row in ipairs(cargo_query) do
local field_names = ""
for field_name, _ in pairs(row) do
field_names = field_names .. field_name .. ", "
end
field_names = field_names:sub(1, -3) -- Remove the trailing comma and space
return
end
end
--[[function that take a dictionary and one focal key/field and displays a sorted
-- bulleted list of the unique values for that key/field.]]
function cf.bulleted_list(dict, field_name)
local uniques_of_rank = {}
for i = 1, #dict do
local entry = dict[i]
if entry and entry[field_name] then
local focal_rank = entry[field_name]
uniques_of_rank[focal_rank] = true
else
mw.log("Invalid entry at index " .. i .. " in dict")
end
end
local sorted_rank = {}
for focal_rank, _ in pairs(uniques_of_rank) do
table.insert(sorted_rank, focal_rank)
end
table.sort(sorted_rank)
local list = {}
for i = 1, #sorted_rank do
local item = sorted_rank[i]:gsub("%**$", "") -- Remove trailing asterisks
local listItem = mw.html.create('li')
if string.lower(field_name) == "species" or string.lower(field_name) == "genus" then
listItem:wikitext("''[[" .. item .. "]]''")
else
listItem:wikitext("[[" .. item .. "]]")
end
table.insert(list, listItem)
end
if #list > 0 then
local resultList = mw.html.create('ul')
for i = 1, #list do
resultList:node(list[i])
end
return tostring(resultList)
else
return nil
end
end
return cf