Module:Species
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Species/doc
-- ================================================================================
-- Module dependencies
-- ================================================================================
local cargo = mw.ext.cargo
local html = mw.html.create()
local getArgs = require('Module:Arguments').getArgs -- for processing arguments
local cf = require("Module:Custom_functions")
local cf2 = require("Module:Custom_functions_new")
local ibf = require("Module:Infobox_functions")
local tf = require("Module:Table_functions")
local utils = require("Module:Utilities")
-- ================================================================================
-- Main table
-- ================================================================================
local p = {}
-- ================================================================================
-- Species pest display box
-- ================================================================================
--[[
Create a pest display table that has data from both the species and and the resources table. The point of this function
is to basically perform a work-around for Cargo's limiations with performing a JOIN operation on fields of type list. It queries both the
Species and the Resource table and maps the results from the latter onto the former.
--]]
function p.get_pest_species_table(frame)
local args = getArgs(frame)
local parent_args = frame:getParent().args
local title = args.title or parent_args.title or mw.title.getCurrentTitle().text
title = title:match("^%s*(.-)%s*$") -- This just removes leading/trailing whitespace as a precaution
local no_results_message = "No results found. If you have expertise or materials on this topic that you are willing to contribute, [[Hopperwiki:About#Who manages HopperWiki?|please get in touch with an administrator]]."
-- ========================================
-- Query both Cargo tables independently
-- ========================================
-- Query Species table
local species_tables = "Species"
local species_fields = "Name, Image, Distribution, All_geography"
local species_where = "All_geography HOLDS '" .. title .. "' AND Is_pest = 'TRUE'"
local species_results = cargo.query(species_tables, species_fields, {where = species_where})
-- Query Subresource table
local subresource_tables = "Subresource"
local subresource_fields = "Name, Parent_resource, Species_purview, File_name"
local subresource_where = "All_geography HOLDS '" .. title .. "' AND Parent_resource = 'USDA ARS grasshopper species fact sheets'"
local subresource_results = cargo.query(subresource_tables, subresource_fields, {where = subresource_where})
-- ========================================
-- Map one table data to the other REFACTORED
-- ========================================
local function map_tables(left_table_results, right_table_results, left_table_join_field, right_table_join_field, right_table_fields_to_join)
-- Build a map from the right table using the specified join field
local right_table_map = {}
for _, row in ipairs(right_table_results) do
if row[right_table_join_field] then
for join_value in row[right_table_join_field]:gmatch("([^,]+)") do
join_value = mw.text.trim(join_value)
-- Store only specified fields in the map
local mapped_data = {}
for _, field in ipairs(mw.text.split(right_table_fields_to_join, ",")) do
field = mw.text.trim(field)
mapped_data[field] = row[field]
end
right_table_map[join_value] = mapped_data
end
end
end
-- Merge left table results with right table mapped data
local merged_results = {}
for _, left_row in ipairs(left_table_results) do
local join_value = left_row[left_table_join_field]
local mapped_data = right_table_map[join_value] or {}
-- Add mapped data to the left row
for k, v in pairs(mapped_data) do
left_row[k] = v
end
table.insert(merged_results, left_row)
end
return merged_results
end
local left_table_join_field = "Name"
local right_table_join_field = "Species_purview"
local right_table_fields_to_join = "File_name"
local merged_results = map_tables(species_results, subresource_results, left_table_join_field, right_table_join_field, right_table_fields_to_join)
-- ========================================
-- Format results for display
-- ========================================
local field_formats = {
Name = "page",
Official_common_name = "string",
Image = "file",
Distribution = "list_of_page",
All_geography = "list_of_page",
File_name = "file",
}
local formatted_results = utils.format_cargo_results(merged_results, field_formats)
-- Display table with optional collapse
local collapse_cutoff = tonumber(args.collapse_cutoff) or 300
local collapse = #formatted_results > collapse_cutoff
local display_fields = "Name, Image, Distribution, File_name"
if #formatted_results > 0 then
local header = ""
return tf.generate_wiki_table(formatted_results, header, display_fields, collapse)
else
return no_results_message
end
end
-- ================================================================================
-- Species infobox
-- ================================================================================
function p.infobox_new(frame)
-- ========================================
-- Process arguments
-- ========================================
local args = getArgs(frame)
local title = mw.title.getCurrentTitle().text
title = title:match("^%s*(.-)%s*$") --This just removes leading/trailing whitespace as a precaution
-- get taxon rank of parent page calling the module
local rank = cf2.rank_finder("Species", title)
if not rank then
-- Handle the error, e.g., by returning a message or logging an error
error(string.format("Error: The taxon '%s' could not be found in the Cargo database.", title))
end
local rank_lowercase = cf2.rank_lookup[rank].lowercase
local rank_sentence_case = cf2.rank_lookup[rank].sentence_case
local rank_cargo = cf2.rank_lookup[rank].cargo
-- if rank is species than use the `Name` field to lookup the title
if rank_cargo == "Species"
then rank_cargo = "Name"
end
-- control loop to see if no arguments are provided from the parent page
-- and patch with a Cargo query if they aren't
if rank_cargo == nil then
return("Test An infobox can't be built because the taxon for this page can't be found in the Cargo database!\n\n")
end
-- local Cargo query to patch values not provided as arguments
local tables = "Species"
local fields = "Image, Taxon_author, Official_common_name, Family, Subfamily, Genus, Species, Distribution, OSF_URL"
local cargo_args = {
where = string.format("%s = '%s'", rank_cargo, title)
}
local cargo_results = cargo.query( tables, fields, cargo_args )
-- extract the cargo_results from the cargo.query output
local image = cargo_results[1].Image or "Species icon.png"
local taxon_author = cargo_results[1].Taxon_author
local common_name = cargo_results[1].Official_common_name
local family = cargo_results[1].Family
local subfamily = cargo_results[1].Subfamily
local genus = cargo_results[1].Genus
local species = cargo_results[1].Species
local distribution = cargo_results[1].Distribution
local osf_url = cargo_results[1].OSF_URL
local osf_url_formatted = "[" .. osf_url .. " " .. "Full taxonomy at OSF]"
-- Make redundant fields nil so they don't display in infobox for records which are higher than species
if rank == "family" then
family, subfamily, genus, species = nil
elseif rank == "subfamily" then
subfamily, genus, speices = nil
elseif rank == "genus" then
genus, species = nil
end
-- start creating infobox
local root = ""
local html_theme_class = '-species'
root = ibf.create_infobox(root, title, html_theme_class)
-- add common name subtitle below the title
if common_name then
root = ibf.add_subtitle(root, common_name, html_theme_class)
end
-- add image
root = ibf.add_image(root, image, html_theme_class)
-- add Distribution section
if distribution then
root = ibf.add_header(root, "Distribution", html_theme_class, 2)
root = ibf.add_spanning_row(root, distribution, html_theme_class, "page")
end
-- add header that says taxonomic classification
if family ~= nil then
root = ibf.add_header(root, "Taxonomy", html_theme_class, 2)
end
root = ibf.add_row(root, 'Family:', family, html_theme_class, "page")
root = ibf.add_row(root, 'Subfamily:', subfamily, html_theme_class, "page")
root = ibf.add_row(root, 'Genus:', genus, html_theme_class, "page")
if osf_url then
root = ibf.add_header(root, "Additional resources", html_theme_class, 2)
root = ibf.add_spanning_row(root, osf_url_formatted, html_theme_class, "text")
end
return root
end
function p.infobox(frame)
-- ========================================
-- Process arguments
-- ========================================
local args = getArgs(frame)
local title = mw.title.getCurrentTitle().text
title = title:match("^%s*(.-)%s*$") --This just removes leading/trailing whitespace as a precaution
-- get taxon rank of parent page calling the module
local rank = cf2.rank_finder("Species", title)
local rank_lowercase = cf2.rank_lookup[rank].lowercase
local rank_sentence_case = cf2.rank_lookup[rank].sentence_case
local rank_cargo = cf2.rank_lookup[rank].cargo
local name = args.Name
local image = args.Image or "Species icon.png"
local class = args.Class
local family = args.Family
local subfamily = args.Subfamily
local genus = args.Genus
local species = args.Species
local taxon_author = args.Taxon_author
local common_name = args.Official_common_name
local distribution = args.Distribution
local osf_url = args.OSF_URL
local osf_url_formatted = "[" .. osf_url .. " " .. "Full taxonomy at OSF]"
-- control loop to see if no arguments are provided from the parent page
-- and patch with a Cargo query if they aren't
if rank_cargo == nil then
return("Test An infobox can't be built because the taxon for this page can't be found in the Cargo database!\n\n")
end
if not (family or subfamily or genus or species) then
-- local Cargo query to patch values not provided as arguments
local tables = "Species"
local fields = "Family, Subfamily, Genus, Species"
local cargo_args = {
where = string.format("%s = '%s'", rank_cargo, title)
}
local cargo_results = cargo.query( tables, fields, cargo_args )
-- extract the cargo_results from the cargo.query output
family = cargo_results[1].Family
subfamily = cargo_results[1].Subfamily
genus = cargo_results[1].Genus
species = cargo_results[1].Species
end
-- Make redundant fields nil so they don't display in infobox for records which are higher than species
if rank == "family" then
family, subfamily, genus, species = nil
elseif rank == "subfamily" then
subfamily, genus, speices = nil
elseif rank == "genus" then
genus, species = nil
end
-- start creating infobox
local root = ""
local html_theme_class = '-species'
root = ibf.create_infobox(root, title, html_theme_class)
-- add common name subtitle below the title
if common_name then
root = ibf.add_subtitle(root, common_name, html_theme_class)
end
-- add image
root = ibf.add_image(root, image, html_theme_class)
-- add Distribution section
if distribution then
root = ibf.add_header(root, "Distribution", html_theme_class, 2)
root = ibf.add_spanning_row(root, distribution, html_theme_class, "page")
end
-- add header that says taxonomic classification
if family ~= nil then
root = ibf.add_header(root, "Taxonomy", html_theme_class, 2)
end
root = ibf.add_row(root, 'Family:', family, html_theme_class, "page")
root = ibf.add_row(root, 'Subfamily:', subfamily, html_theme_class, "page")
root = ibf.add_row(root, 'Genus:', genus, html_theme_class, "page")
if osf_url then
root = ibf.add_header(root, "Additional resources", html_theme_class, 2)
root = ibf.add_spanning_row(root, osf_url_formatted, html_theme_class, "text")
end
return root
end
-- ================================================================================
-- Species for geography gallery
-- ================================================================================
--[[In theory the logic of this function is very similar to the "get X for Y" style Cargo query templates that can
just directly query the Module:Cargo_query functions but gallery producing function for species needs it's own function
as it requires a little prefilter logic.
--]]
function p.get_species_gallery_for_geographic_unit(frame)
-- ========================================
-- Process and prioritize arguments
-- ========================================
local args = getArgs(frame)
local parent_args = frame:getParent().args
local title = mw.title.getCurrentTitle().text
title = title:match("^%s*(.-)%s*$") -- This just removes leading/trailing whitespace as a precaution
local cargo_table = "Species"
local cargo_focal_field = "All_geography"
local cargo_fields = "Species, All_geography, Image"
local filter_values = args.filter_value or args.filter_values or parent_args.filter_value or parent_args.filter_values or title
local not_values = args.not_value or args.not_values or parent_args.not_value or parent_args.not_values
-- placeholder image for when there are no images
local placeholder_image = args.placeholder_image or 'No image available.svg'
-- set gallery mode
local gallery_mode = args.gallery_mode or "packed"
-- ========================================
-- Run Cargo query and format
-- ========================================
local where_condition = string.format("%s HOLDS '%s'", cargo_focal_field, filter_values)
local cargo_args = {where = where_condition}
local cargo_results = cargo.query(cargo_table, cargo_fields, cargo_args)
-- Get results from Cargo table
local gallery_wikitext = cf.create_cargo_gallery(cargo_results, "Species", "Image", placeholder_image, name_array, gallery_mode)
local header = "<h1> Species of locust and grasshoppers in " .. filter_values .. " represented in HopperWiki</h1>"
local gallery = frame:preprocess(gallery_wikitext)
return header .. gallery
end
-- ================================================================================
-- Content for higher taxon pages
-- ================================================================================
function p.get_taxon_list(frame)
local args = getArgs(frame)
local title = mw.title.getCurrentTitle().text
-- get taxon rank of parent page calling the module
local rank_table = cf.rank_finder(title)
local rank = rank_table.lowercase
local rank_cargo = rank_table.cargo_field
local rank_title = rank_table.title
-- get downstream child ranks with respect to page rank
local child_ranks = ""
if rank ~= "species" then
child_ranks = cf.get_child_ranks(rank)
else
return "Warning: 'Module:Higher taxon content' shouldn't be used on species pages."
end
-- check and give error if Cargo arguments aren't present
if rank_cargo == nil then
return("Content can't be built because the taxon for this page can't be found in the Cargo database!\n\n")
end
-- cargo query
local fields = "Family, Subfamily, Genus, Species"
local tables = "Species"
local cargo_args = {
where = string.format("%s = '%s'", rank_cargo, title)
}
local cargo_results = cargo.query(tables, fields, cargo_args)
-- output section where output structure is built based on page type
local field_name = child_ranks["cargo_field"][1] -- Specify the field name
local taxon_list = cf.bulleted_list(cargo_results, field_name)
-- produce display for forward-facing page
local output = tostring(mw.html.create('h2'):wikitext("The following " .. child_ranks["plural"][1] .. " are available on HopperWiki for " .. title))
-- site 1
if taxon_list then
output = output .. "\n" .. taxon_list .. "\n"
end
return output
end
-- ================================================================================
-- Species title italicizer
-- ================================================================================
-- A function to handle italicizing species names based on the page title format
function p.species_title_italicizer(frame)
local args = getArgs(frame)
local title = mw.title.getCurrentTitle().text
if string.find(title, "%(") then
-- Expand the Italic dab template
return frame:expandTemplate{ title = 'Italic dab' }
else
-- Expand the Italic title template
return frame:expandTemplate{ title = 'Italic title' }
end
end
-- ================================================================================
-- Orthoptera Species File connecting information
-- ================================================================================
function p.get_osf(frame)
local args = getArgs(frame)
local title = mw.title.getCurrentTitle().text
local species = cf.title_to_sci(title)
local taxon_author = args.taxon_author
local output = ""
-- check that sci_name_author manual override arguments haven't been provided
if not taxon_author then
-- run cargo query to find the full species name (with author) in the Cargo table
local tables = "Species"
local fields = "Species, Taxon_author, OSF_URL"
local cargo_args = {
where = "Species = " .. "'" .. species .. "'"
}
local cargo_results = cargo.query(tables, fields, cargo_args)
-- get only the first row of cargo_results
local cargo_row = cargo_results[1]
-- Italicize only the first part of the Taxon_author field
local binomial_formatted = "<i>" .. cargo_row.Species .. "</i> "
local taxon_author = cargo_row.Taxon_author:gsub("(%a+ %a+)", "<i>%1</i>")
local osf_url = cargo_row.OSF_URL
local osf_link = "[" .. osf_url .. " Orthoptera Species File]"
output = binomial_formatted .. " " .. taxon_author .. ". For full nomenclature, see this taxon's page on " .. osf_link
else
output = output .. "<i>" .. cargo_row.Species .. "</i> " .. cargo_row.Taxon_author .. ". For full nomenclature, see this taxon's page on " .. " Orthoptera Species File.]"
end
return output
end
return p