Jump to content

Module:Species

From HopperWiki

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
-- Utilities
local str = require("Module:String utilities")
local out = require("Module:Wiki output utilities")
local cfmt = require("Module:Cargo format utilities")
local sc = str.sc -- Short cut for a safe Lua concatenation function
local ibf = require("Module:Infobox_functions")

-- JSON
local json = require("Module:JSON_pipeline")

-- Taxonomy
local ts = require("Module:Taxonomic schema")
local rank_lookup = ts.rank_lookup
local all_ranks = ts.all_ranks
local rank_dictionary = ts.taxonomic_rank_dictionary



-- ================================================================================
-- Main table
-- ================================================================================
local p = {}







-- ================================================================================
-- Subspecies list for species page
-- ================================================================================
function p.get_subspecies_for_species(frame)

    local otu_id, suborder, page_title, err = json.resolve_focal_taxon(frame)
    if err then
        return out.warning_box("⚠️ Nomenclature lookup error", "Could not resolve focal taxon.")
    end

    local args = getArgs(frame)
    local json_page = args.json_page or "JSON:Species_to_subspecies_map"

    -- Just this species' entry (from its shard, or the whole page before the map was sharded), copied and
    -- number-normalized as json.load would
    local subspecies_list = json.copy(json.get_record(json_page, otu_id), true)
    if not subspecies_list or #subspecies_list == 0 then
        return "No subspecies recorded for this taxon."
    end

    -- Subspecies with a HopperWiki page link to it (OSF as a small secondary link); the rest link to OSF only.
    -- OTU_to_title_map plans a title for every subspecies but only a few pages exist yet, hence the exists
    -- check -- which also re-renders this page when a subspecies page is created.
    local function existing_wiki_title(sub_otu)
        local title_rec = json.lookup("JSON:OTU_to_title_map", sub_otu)
        local title = title_rec and title_rec.Page_title
        if not title or title == "" then return nil end
        local title_obj = mw.title.new(title)
        return (title_obj and title_obj.exists) and title or nil
    end

    local items = {}
    for _, rec in ipairs(subspecies_list) do
        local name    = rec.Scientific_name or ""
        local author  = rec.Author_year or ""
        local sub_otu = rec.OTU_ID or ""
        local osf_url = "https://orthoptera.speciesfile.org/otus/" .. sub_otu .. "/overview"
        local wiki_title = existing_wiki_title(tostring(sub_otu))

        if wiki_title then
            table.insert(items, string.format(
                "* [[%s|<i>%s</i>]] %s <small>([%s OSF])</small>",
                wiki_title, name, author, osf_url
            ))
        else
            table.insert(items, string.format(
                "* [%s <i>%s</i>] %s",
                osf_url, name, author
            ))
        end
    end

    return "==Subspecies==\n\n" .. table.concat(items, "\n")
end

























-- ================================================================================
-- Species pest display box
-- ================================================================================

-- Current production version
function p.get_pest_species_table(frame)

    -- ========================================
    -- Resolve page title and setup
    -- ========================================
    local args = getArgs(frame)

    local page_title = args.page_title
        and mw.text.trim(args.page_title)
        or mw.text.trim(mw.title.getCurrentTitle().text)

    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 the Pest table
    -- ========================================
	local results = cargo.query(
	        "Pest",
	        "Name, Image, File_name, Original_source_link",
	        { where = string.format("All_geography HOLDS '%s'", page_title) }
	    )

    if not results or #results == 0 then
        return no_results_message
    end

    -- ========================================
    -- Build linked fact sheet file field
    -- ========================================
	for _, row in ipairs(results) do
	        -- Default image fallback
	        if not row.Image or row.Image == "" then
	            row.Image = "File:Species icon.png"
	        end
	
	        -- Linked fact sheet file
	        if row.File_name and row.Original_source_link then
	            local clean = str.bare_file_name(row.File_name)
	            row.Linked_file = string.format("[[File:%s|150px|link=%s]]", clean, row.Original_source_link)
	        elseif row.File_name then
	            local clean = str.bare_file_name(row.File_name)
	            row.Linked_file = string.format("[[File:%s|150px]]", clean)
	        else
	            row.Linked_file = ""
	        end
	    end

    -- ========================================
    -- Format and generate table
    -- ========================================
	local field_formats       = { Name = "page", Image = "file", Linked_file = "raw" }
	local field_name_mappings = { Name = "Species", Linked_file = "Fact sheet" }
	local display_fields      = { "Species", "Image", "Fact sheet" }

    local row_cutoff        = (tonumber(args.row_cutoff) or 3) + 1
    local character_cutoff  = tonumber(args.character_cutoff) or 600
    local collapse          = #results > row_cutoff

    local formatted = cfmt.format_cargo_results(results, field_formats, field_name_mappings)

    if #formatted > 0 then
        return out.generate_wiki_table_enhanced(
            formatted,
            display_fields,
            collapse,
            character_cutoff,
            nil,
            field_name_mappings,
            true,
            nil,
            row_cutoff
        )
    else
        return no_results_message
    end
end



-- These two functions are underdevelopment. This was an attempt to make a slicker version of the pest table that has little feature cards.
local function build_pest_card(row)

    -- Species photo
    local clean_image = str.bare_file_name(row.Image)
    local photo_html = string.format(
        "[[File:%s|300px|link=%s]]",
        clean_image,
        row.Name or ""
    )

    -- Fact sheet thumbnail
    local clean_file = str.bare_file_name(row.File_name)
    local sheet_link = row.Original_source_link or ""
    local sheet_img_html = ""
    if clean_file ~= "" and sheet_link ~= "" then
        sheet_img_html = string.format("[[File:%s|300px|link=%s]]", clean_file, sheet_link)
    elseif clean_file ~= "" then
        sheet_img_html = string.format("[[File:%s|300px]]", clean_file)
    end

	local label_html
	if sheet_link ~= "" then
	    label_html = string.format(
	        '<span class="pest-card__sheet-label">[%s Fact sheet ↗]</span>',
	        sheet_link
	    )
	else
	    label_html = '<span class="pest-card__sheet-label">Fact sheet</span>'
	end

    -- Name: split "Common name (Genus species)" if pattern matches
    local name = row.Name or ""
    local common, sci = name:match("^(.-)%s*%(([^)]+)%)%s*$")
    local name_html
    if common and sci then
        name_html = string.format(
            '<p class="pest-card__name">[[%s|%s]]</p><p class="pest-card__sci"><i>%s</i></p>',
            name, mw.text.trim(common), mw.text.trim(sci)
        )
    else
        name_html = string.format('<p class="pest-card__name">[[%s]]</p>', name)
    end

    return table.concat({
        '<div class="pest-card">',
            '<div class="pest-card__images">',
                '<div class="pest-card__photo-wrap">', photo_html, '</div>',
                '<div class="pest-card__sheet-wrap">', sheet_img_html, label_html, '</div>',
            '</div>',
            '<div class="pest-card__body">', name_html, '</div>',
        '</div>',
    })
end

function p.get_pest_species_table_new(frame)

    local args = getArgs(frame)
    local page_title = args.page_title
        and mw.text.trim(args.page_title)
        or mw.text.trim(mw.title.getCurrentTitle().text)

    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]]."

    local results = cargo.query(
        "Pest",
        "Name, Image, File_name, Original_source_link",
        { where = string.format("All_geography HOLDS '%s'", page_title) }
    )

    if not results or #results == 0 then
        return no_results_message
    end

    for _, row in ipairs(results) do
        if not row.Image or row.Image == "" then
            row.Image = "File:Species icon.png"
        end
    end

    local cards = {}
    for _, row in ipairs(results) do
        table.insert(cards, build_pest_card(row))
    end

    return '<div class="pest-grid">' .. table.concat(cards) .. '</div>'
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
-- ================================================================================
-- get_osf: fix the duplicate variable declarations and use correct module
function p.get_osf(frame)
    local otu_id, suborder, page_title, err = json.resolve_focal_taxon(frame)
    if err then
        local msg =
            "Could not resolve focal taxon.\n" ..
            "Check that the OTU ID is correct for this species in the database."
        return out.warning_box("⚠️ Nomenclature lookup error", msg)
    end
    local osf_url  = "https://orthoptera.speciesfile.org/otus/" .. tostring(otu_id) .. "/overview"
    local osf_link = "[" .. osf_url .. " Orthoptera Species File]"
    return "For full nomenclature and taxonomic details of this taxon, see " .. osf_link
end







return p
Cookies help us deliver our services. By using our services, you agree to our use of cookies.