Jump to content
Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

Since 7 October 2026 every request to landrace.wiki passes through Cloudflare, a security service brought in after a denial-of-service attack made the site unreachable. The Privacy policy now explains what this means for your data.

Module:External repositories

From Landrace.wiki

Documentation for this module may be created at Module:External repositories/doc

-- The outside genebanks and data archives an accession can be linked to, and how to link them.
-- Template:External accession reads this table to check an identifier's form, build the link to the
-- repository's own page, and fill the subobject it stores. A repository that moves its record
-- pages is corrected here, once, for every accession.
--
--   {{#invoke:External repositories|link|NPGS|G 33734}}  -> [https://… USDA NPGS G 33734]
--   {{#invoke:External repositories|url|NPGS|G 33734}}   -> https://…accid=G+33734
--   {{#invoke:External repositories|page|NPGS}}           -> National Plant Germplasm System
--   {{#invoke:External repositories|kind|NPGS}}           -> Genebank accession
--   {{#invoke:External repositories|id|NPGS|g  33734}}    -> G 33734
--   {{#invoke:External repositories|key|NPGS|G 33734}}    -> ext-NPGS-G_33734 (subobject name)
--   {{#invoke:External repositories|table}}               -> the documentation table
--
-- Each entry:
--   name     the repository as a reader knows it, printed before the identifier
--   page     its page on this wiki (Has external repository stores this)
--   kind     a value Property:Has external accession type allows
--   patterns Lua patterns the whole identifier must match, after normalising
--   url      the record page, with $1 for the encoded identifier
--   encode   how $1 is encoded: 'QUERY' (space -> +) or 'PATH' (space -> %20)
--   upper    true when the identifier is case-insensitive and stored upper-case
--   checked  the date the pattern and the address were confirmed against live records
local p = {}

local R = {}
p.repositories = R

R.NPGS = {
	name = 'USDA NPGS',
	page = 'National Plant Germplasm System',
	kind = 'Genebank accession',
	-- All 873 cannabis accessions in GRIN-Global on 2026-10-03 were numbered PI (Plant
	-- Introduction, 170) or G (703, the hemp collection at Geneva, New York). Add another NPGS
	-- prefix here only when a cannabis accession carries it.
	patterns = { '^PI %d+$', '^G %d+$' },
	url = 'https://npgsweb.ars-grin.gov/gringlobal/accessiondetail?accid=$1',
	encode = 'QUERY',
	checked = '2026-10-03',
}

-- The INSDC archives (NCBI, ENA, DDBJ) exchange records daily, so one NCBI address resolves an
-- identifier wherever it was submitted: SAMN/PRJNA/SRR were submitted to NCBI, SAMEA/PRJEB/ERR to
-- ENA, SAMD/PRJDB/DRR to DDBJ. Checked against live Cannabis entries on 2026-10-03:
-- PRJNA1206134, SAMN35085931, SAMEA112689286, SAMD02092122, SRR29240402, GCF_900626175.2.
R.BIOPROJECT = {
	name = 'NCBI BioProject',
	page = 'National Center for Biotechnology Information',
	kind = 'Sequencing project',
	patterns = { '^PRJ[NED][A-Z]%d+$' },
	url = 'https://www.ncbi.nlm.nih.gov/bioproject/$1',
	upper = true,
	checked = '2026-10-03',
}

R.BIOSAMPLE = {
	name = 'NCBI BioSample',
	page = 'National Center for Biotechnology Information',
	kind = 'Biological sample',
	patterns = { '^SAMN%d+$', '^SAMEA%d+$', '^SAMD%d+$' },
	url = 'https://www.ncbi.nlm.nih.gov/biosample/$1',
	upper = true,
	checked = '2026-10-03',
}

R.SRA = {
	name = 'NCBI SRA',
	page = 'National Center for Biotechnology Information',
	kind = 'Sequence run',
	-- Runs (SRR/ERR/DRR) are the data; experiments (X) and studies (P) are accepted as well.
	patterns = { '^[SED]R[RXP]%d+$' },
	url = 'https://www.ncbi.nlm.nih.gov/sra/$1',
	upper = true,
	checked = '2026-10-03',
}

R.ASSEMBLY = {
	name = 'NCBI Assembly',
	page = 'National Center for Biotechnology Information',
	kind = 'Genome assembly',
	patterns = { '^GC[AF]_%d%d%d%d%d%d%d%d%d%.%d+$' },
	url = 'https://www.ncbi.nlm.nih.gov/datasets/genome/$1/',
	upper = true,
	checked = '2026-10-03',
}

local ERROR_CATEGORY = '[[Category:Pages with external accession errors]]'

local function trim(s)
	return mw.text.trim(s or '')
end

local function args(frame)
	return trim(frame.args[1]):upper(), trim(frame.args[2])
end

local function entry(key)
	return R[key]
end

-- Collapse runs of spaces, and write a genebank number the way the genebank does: prefix
-- upper-cased and separated from the number ("pi  659885", "PI659885" -> "PI 659885"), so that
-- two spellings of one record store as one value.
local function normalise(rep, id)
	id = mw.ustring.gsub(trim(id), '%s+', ' ')
	if rep and rep.upper then
		id = mw.ustring.upper(id)
	elseif rep and rep.kind == 'Genebank accession' then
		id = mw.ustring.gsub(id, '^(%a+)(%d+)$', '%1 %2')
		local prefix, rest = mw.ustring.match(id, '^(%a+)( .*)$')
		if prefix and prefix ~= 'Ames' then
			id = mw.ustring.upper(prefix) .. rest
		end
	end
	return id
end

local function valid(rep, id)
	for _, pat in ipairs(rep.patterns) do
		if mw.ustring.find(id, pat) then
			return true
		end
	end
	return false
end

local function err(msg)
	return '<span class="error">' .. msg .. '</span>' .. ERROR_CATEGORY
end

local function build_url(rep, id)
	return (rep.url:gsub('%$1', function()
		return mw.uri.encode(id, rep.encode or 'PATH')
	end))
end

-- Returns the entry and the normalised identifier, or nil and an error string.
local function resolve(key, raw)
	local rep = entry(key)
	if not rep then
		return nil, err('unknown repository "' .. key .. '"')
	end
	local id = normalise(rep, raw)
	if id == '' then
		return nil, err('no identifier for ' .. rep.name)
	end
	if not valid(rep, id) then
		return nil, err('"' .. id .. '" is not a valid ' .. rep.name .. ' identifier')
	end
	return rep, id
end

function p.link(frame)
	local key, raw = args(frame)
	local rep, id = resolve(key, raw)
	if not rep then
		return id
	end
	return '[' .. build_url(rep, id) .. ' ' .. rep.name .. ' ' .. id .. ']'
end

-- The functions below feed #subobject. Template:External accession stores nothing unless p.ok
-- returns 1, so a malformed record prints its error (once, from p.link) and stores no subobject.
function p.ok(frame)
	local key, raw = args(frame)
	return resolve(key, raw) and '1' or ''
end

function p.url(frame)
	local key, raw = args(frame)
	local rep, id = resolve(key, raw)
	return rep and build_url(rep, id) or ''
end

function p.id(frame)
	local key, raw = args(frame)
	local rep, id = resolve(key, raw)
	return rep and id or ''
end

function p.page(frame)
	local rep = entry((args(frame)))
	return rep and rep.page or ''
end

function p.kind(frame)
	local rep = entry((args(frame)))
	return rep and rep.kind or ''
end

-- A stable subobject name, so a re-save replaces the record rather than adding a second one.
function p.key(frame)
	local key, raw = args(frame)
	local rep, id = resolve(key, raw)
	if not rep then
		return 'ext-invalid'
	end
	return 'ext-' .. key .. '-' .. mw.ustring.gsub(id, '[^%w%.%-]', '_')
end

function p.table(frame)
	local keys = {}
	for k in pairs(R) do
		keys[#keys + 1] = k
	end
	table.sort(keys)
	local out = {
		'{| class="wikitable sortable"',
		'! Key !! Repository !! Type !! Identifier forms !! Link pattern !! Checked',
	}
	for _, k in ipairs(keys) do
		local rep = R[k]
		local forms = {}
		for _, pat in ipairs(rep.patterns) do
			forms[#forms + 1] = '<code>' .. mw.text.nowiki(pat) .. '</code>'
		end
		out[#out + 1] = '|-'
		out[#out + 1] = '| <code>' .. k .. '</code> || [[' .. rep.page .. '|' .. rep.name .. ']] || '
			.. rep.kind .. ' || ' .. table.concat(forms, ' ') .. ' || <code>'
			.. mw.text.nowiki(rep.url) .. '</code> || ' .. (rep.checked or '')
	end
	out[#out + 1] = '|}'
	return table.concat(out, '\n')
end

return p