Module:External repositories
From Landrace.wiki
More actions
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