မော်ဂျူး:Navseasoncats
ပုံပန်းသွင်ပြင်
မော်ဂျူးမှတ်တမ်းလက်စွဲ[ဖန်တီး]
သင့်အနေဖြင့် ဤ Scribunto module အတွက် အသုံးပြုလက်စွဲ စာမျက်နှာကို ဖန်တီး နိုင်ပါသည်။ တည်းဖြတ်သူများအနေဖြင့် ဤမော်ဂျူး၏ sandbox (ဖန်တီး | ပုံတူပွား) နှင့် testcases (ဖန်တီး) စာမျက်နှာများကို စမ်းသပ်နိုင်ပါသည်။ စာမျက်နှာခွဲ /doc တွင် ကဏ္ဍများထည့်သွင်းပါ။ ဤ မော်ဂျူး ၏ စာမျက်နှာခွဲများ။. |
local p = {}
local currtitle = mw.title.getCurrentTitle()
local nexistingcats = 0
local errors = ''
local testcasecolon = ''
local testcases = string.match(currtitle.subpageText, '^testcases')
if testcases then testcasecolon = ':' end
local navborder = true
local followRs = true
local skipgaps = false
local listall = false
local tlistall = {}
local tlistallbwd = {}
local tlistallfwd = {}
local ttrackingcats = {
'', -- [1] placeholder for [[Category:Navseasoncats using cat parameter]]
'', -- [2] placeholder for [[Category:Navseasoncats using testcase parameter]]
'', -- [3] placeholder for [[Category:Navseasoncats using unknown parameter]]
'', -- [4] placeholder for [[Category:Navseasoncats range not using en dash]]
'', -- [5] placeholder for [[Category:Navseasoncats range abbreviated (MOS)]]
'', -- [6] placeholder for [[Category:Navseasoncats range redirected (base change)]]
'', -- [7] placeholder for [[Category:Navseasoncats range redirected (end)]]
'', -- [8] placeholder for [[Category:Navseasoncats range redirected (MOS)]]
'', -- [9] placeholder for [[Category:Navseasoncats range redirected (other)]]
'', --[10] placeholder for [[Category:Navseasoncats range gaps]]
'', --[11] placeholder for [[Category:Navseasoncats range irregular]]
'', --[12] placeholder for [[Category:Navseasoncats range irregular, 0-length]]
'', --[13] placeholder for [[Category:Navseasoncats range ends (present)]]
'', --[14] placeholder for [[Category:Navseasoncats range ends (blank, MOS)]]
'', --[15] placeholder for [[Category:Navseasoncats isolated]]
'', --[16] placeholder for [[Category:Navseasoncats default season gap size]]
'', --[17] placeholder for [[Category:Navseasoncats decade redirected]]
'', --[18] placeholder for [[Category:Navseasoncats year redirected]]
'', --[19] placeholder for [[Category:Navseasoncats roman numeral redirected]]
'', --[20] placeholder for [[Category:Navseasoncats nordinal redirected]]
'', --[21] placeholder for [[Category:Navseasoncats wordinal redirected]]
'', --[22] placeholder for [[Category:Navseasoncats TV season redirected]]
'', --[23] placeholder for [[Category:Navseasoncats using skip-gaps parameter]]
'', --[24] placeholder for [[Category:Navseasoncats year and decade]]
'', --[25] placeholder for [[Category:Navseasoncats decade and century]]
'', --[26] placeholder for [[Category:Navseasoncats in mainspace]]
}
local avoidself = (not string.match(currtitle.text, 'Navseasoncats with') and
not string.match(currtitle.text, 'Navseasoncats.*/doc') and
not string.match(currtitle.text, 'Navseasoncats.*/sandbox') and
currtitle.text ~= 'Navseasoncats' and
currtitle.nsText ~= 'User_talk' and
currtitle.nsText ~= 'Template_talk' and
(currtitle.nsText ~= 'Template' or testcases)) --avoid nested transclusion errors (i.e. {{Infilmdecade}})
--[[==========================================================================]]
--[[ Utility & category functions ]]
--[[==========================================================================]]
--Error message handling
function p.errorclass( msg )
return mw.text.tag( 'span', {class='error mw-ext-cite-error'}, '<b>အမှား!</b> '..string.gsub(msg, '&#', '&#') )
end
--Failure handling
function p.failedcat( errors, sortkey )
if avoidself then
return (errors or '')..'***Navseasoncats failed to generate navbox***'..
'[['..testcasecolon..'Category:Navseasoncats failed to generate navbox|'..(sortkey or 'O')..']]\n'
end
return ''
end
--Check for unknown parameters.
--Used by main only.
function checkforunknownparams( tbl )
local knownparams = {
['min'] = 'min',
['max'] = 'max',
['cat'] = 'cat',
['testcase'] = 'testcase',
['testcasegap'] = 'testcasegap',
['skip-gaps'] = 'skip-gaps',
['list-all-links'] = 'list-all-links',
['follow-redirects'] = 'follow-redirects',
}
for k, _ in pairs (tbl) do
if knownparams[k] == nil and avoidself then
ttrackingcats[3] = '[['..testcasecolon..'Category:Navseasoncats using unknown parameter]]'
end
end
end
--Check for nav_*() navigational isolation (not necessarily an error).
--Used by all nav_*().
function isolatedcat()
if nexistingcats == 0 and avoidself then
ttrackingcats[15] = '[['..testcasecolon..'Category:Navseasoncats isolated]]'
end
end
--Similar to {{LinkCatIfExists2}}: make a piped link to a category, if it exists;
--if it doesn't exist, just display the greyed link title without linking.
--Follows {{Category redirect}}s.
--Returns { <#R target navelement>, <display text>, <basetext of #R target> } if {{Category redirect}} followed;
--returns { <original navelement>, <display text>, nil } otherwise.
--Used by all nav_*().
function catlinkfollowr( catname, displaytext, displayend )
catname = mw.text.trim(catname or '')
displaytext = mw.text.trim(displaytext or '')
displayend = displayend or false --bool flag to override displaytext IIF the cat/target is terminal (e.g. "2021–present" or "2021–")
local grey = '#888'
local disp = catname
if displaytext ~= '' then --use 'displaytext' parameter if present
disp = mw.ustring.gsub(displaytext, '%s+%(.+$', ''); --strip any trailing disambiguator
end
local link, nilorR
local exists = mw.title.new( catname, 'Category' ).exists
if exists then
nexistingcats = nexistingcats + 1
if followRs then
local R = rtarget(catname)
if displayend then
local y, hyph, ending = mw.ustring.match(R, '^.-(%d+)([–-])(.*)$')
if ending == 'present' then
disp = y..hyph..ending
elseif ending == '' then
disp = y..hyph..'<span style="visibility:hidden">'..y..'</span>' --hidden y to match spacing
end
end
if R ~= catname then --#R followed
nilorR = R
end
link = '[[:Category:'..R..'|'..disp..']]'
else
link = '[[:Category:'..catname..'|'..disp..']]'
end
else
link = '<span style="color:'..grey..'">'..disp..'</span>'
--return { '[[:Category:'..catname..'|'..disp..']]', nil } --for debugging
end
if listall then
if nilorR then --#R followed
table.insert( tlistall, '[[:Category:'..catname..']] → '..'[[:Category:'..nilorR..']] ('..link..')' )
else --no #R
table.insert( tlistall, '[[:Category:'..catname..']] ('..link..')' )
end
end
return { link, disp, nilorR }
end
--Returns the properly formatted central nav element.
--Expects an integer i, and a catlinkfollowr() table.
--Used by nav_decade() & nav_ordinal() only.
function navcenter( i, catlink )
if i == 0 then --center nav element
if navborder == true then
return '*<b>'..catlink[2]..'</b>\n'
else
return '*<b>'..catlink[1]..'</b>\n'
end
else
return '*'..catlink[1]..'\n'
end
end
--Returns the target of {{Category redirect}}, if it exists, else returns the original cat.
--Used by catlinkfollowr(), and so indirectly by all nav_*().
function rtarget( cat )
local catcontent = mw.title.new( cat or '', 'Category' ):getContent()
if string.match( catcontent or '', '{{ *[Cc]at' ) then
local regex = {
--the following 11 pages (7 condensed) redirect to [[Template:Category redirect]] (as of 6/2019):
'{{ *[Cc]ategory *[Rr]edirect', --most likely match 1st
'{{ *[Cc]at *redirect', --444+240 transclusions
'{{ *[Cc]at *redir', --8+3
'{{ *[Cc]ategory *move', --6
'{{ *[Cc]at *red', --6
'{{ *[Cc]atr', --4
'{{ *[Cc]at *move', --0
}
for _, v in pairs (regex) do
local rtarget = mw.ustring.match( catcontent, v..'%s*|%s*([^|}]+)' )
if rtarget then
rtarget = mw.ustring.gsub(rtarget, '^1%s*=%s*', '')
rtarget = string.gsub(rtarget, '^[Cc]ategory:', '')
return rtarget
end
end
end
return cat
end
--Returns a numbered list of all {{Category redirect}}s followed by catlinkfollowr() -> rtarget().
--For a nav_hyphen() cat, also returns a formatted list of all cats searched for & found, & all loop indices.
--Used by all nav_*().
function listalllinks()
local nl = '\n# '
local out = ''
if currtitle.nsText == 'Category' then
errors = p.errorclass('The <b><code>|list-all-links=yes</code></b> parameter/utility '..
'should not be saved in category space, only previewed.')
out = p.failedcat(errors, 'Z')
end
local bwd, fwd = '', ''
if tlistallbwd[1] then
bwd = '\n\nbackward search:\n# '..table.concat(tlistallbwd, nl)
end
if tlistallfwd[1] then
fwd = '\n\nforward search:\n# '..table.concat(tlistallfwd, nl)
end
if tlistall[1] then
return out..nl..table.concat(tlistall, nl)..bwd..fwd
else
return out..nl..'No links found!?'..bwd..fwd
end
end
--Returns the difference b/w 2 ints separated by endash|hyphen, nil if error.
--Used by nav_hyphen() only.
function find_duration( cat )
local from, to = mw.ustring.match(cat, '(%d+)[–-](%d+)')
if from and to then
if to == '00' then return nil end --doesn't follow MOS:DATERANGE
if (#from == 4) and (#to == 2) then --1900-01
to = string.match(from, '(%d%d)%d%d')..to --1900-1901
elseif (#from == 2) and (#to == 4) then -- 01-1902
from = string.match(to, '(%d%d)%d%d')..from --1901-1902
end
return (tonumber(to) - tonumber(from))
end
return 0
end
--Returns the ending of a terminal cat, and sets the appropriate tracking cat, else nil.
--Used by nav_hyphen() only.
function find_terminaltxt( cat )
local terminaltxt = nil
if mw.ustring.match(cat, '%d+[–-]present$') then
terminaltxt = 'present'
ttrackingcats[13] = '[['..testcasecolon..'Category:Navseasoncats range ends (present)]]'
elseif mw.ustring.match(cat, '%d+[–-]$') then
terminaltxt = ''
ttrackingcats[14] = '[['..testcasecolon..'Category:Navseasoncats range ends (blank, MOS)]]'
end
return terminaltxt
end
--Returns an unsigned string of the 1-4 digit decade ending in "0", else nil.
--Used by nav_decade() only.
function sterilizedec( decade )
if decade == nil or decade == '' then
return nil
end
local dec = string.match(decade, '^[-%+]?(%d?%d?%d?0)$') or
string.match(decade, '^[-%+]?(%d?%d?%d?0)%D')
if dec then
return dec
else
--fix 2-4 digit decade
local decade_fixed234 = string.match(decade, '^[-%+]?(%d%d?%d?)%d$') or
string.match(decade, '^[-%+]?(%d%d?%d?)%d%D')
if decade_fixed234 then
return decade_fixed234..'0'
end
--fix 1-digit decade
local decade_fixed1 = string.match(decade, '^[-%+]?(%d)$') or
string.match(decade, '^[-%+]?(%d)%D')
if decade_fixed1 then
return '0'
end
--unfixable
return nil
end
end
--Check for nav_hyphen default gap size + isolatedcat() (not necessarily an error).
--Used by nav_hyphen() only.
function defaultgapcat( bool )
if bool and nexistingcats == 0 and avoidself then
--using "nexistingcats > 0" isn't as useful, since the default gap size obviously worked
ttrackingcats[16] = '[['..testcasecolon..'Category:Navseasoncats default season gap size]]'
end
end
--12 -> 12th, etc.
--Used by nav_nordinal() & nav_wordinal().
function p.addord( i )
if tonumber(i) then
local s = tostring(i)
local tens = string.match(s, '1%d$')
if tens then return s..'th' end
local ones = string.match(s, '%d$')
if ones == '1' then return s..'st'
elseif ones == '2' then return s..'nd'
elseif ones == '3' then return s..'rd' end
return s..'th'
end
return i
end
--Return conditionally aligned stacked navs.
--Used by main only.
function nav1nav2( nav1, nav2 )
if avoidself then
local forcealign = '<div style="display:block !important; max-width: calc(100% - 25em);">'
return forcealign..'\n'..nav1..'\n'..nav2..'\n</div>'
else
return nav1..'\n'..nav2
end
end
--[[==========================================================================]]
--[[ Formerly separated templates/modules ]]
--[[==========================================================================]]
--[[==========================={{ nav_hyphen }}=============================]]
function nav_hyphen( start, hyph, finish, firstpart, lastpart, minseas, maxseas, testgap )
--Expects a PAGENAME of the form "Some sequential 2015–16 example cat", where
-- start = 2015
-- hyph = –
-- finish = 16 (sequential years can be abbreviated, but others should be full year, e.g. "2001–2005")
-- firstpart = Some sequential
-- lastpart = example cat
-- minseas = 1800 ('min' starting season shown; optional; defaults to -9999)
-- maxseas = 2000 ('max' starting season shown; optional; defaults to 9999; 2000 will show 2000-01)
-- testgap = 0 (testcasegap parameter for easier testing; optional)
--sterilize start
if string.match(start or '', '^%d%d?%d?%d?$') == nil then --1-4 digits, AD only
local start_fixed = mw.ustring.match(start or '', '^%s*(%d%d?%d?%d?)%D')
if start_fixed then
start = start_fixed
else
errors = p.errorclass('Function nav_hyphen can\'t recognize the number "'..(start or '')..'" '..
'in the first part of the "season" that was passed to it. '..
'For e.g. "2015–16", "2015" is expected via "|2015|–|16|".')
return p.failedcat(errors, 'H')
end
end
local nstart = tonumber(start)
--en dash check
if hyph ~= '–' then
ttrackingcats[4] = '[['..testcasecolon..'Category:Navseasoncats range not using en dash]]' --nav still processable, but track
end
--sterilize finish & check for weird parents
local tgaps = {} --table of gap sizes found b/w terms { [<gap size found>] = 1 }
local ttlens = {} --table of term lengths found w/i terms { [<term length found>] = 1 }
local tirregs = {} --table of ir/regular-term-length cats' "from"s & "to"s found
local regularparent = true
if (finish == -1) or --"Members of the Scottish Parliament 2021–present"
(finish == 0) --"Members of the Scottish Parliament 2021–"
then
regularparent = false
if maxseas == nil or maxseas == '' then
maxseas = start --hide subsequent ranges
end
if finish == -1 then ttrackingcats[13] = '[['..testcasecolon..'Category:Navseasoncats range ends (present)]]'
else ttrackingcats[14] = '[['..testcasecolon..'Category:Navseasoncats range ends (blank, MOS)]]' end
elseif (start == finish) and
(ttrackingcats[15] ~= '') --nav_year found isolated; check for surrounding hyphenated terms (e.g. UK MPs 1974)
then
ttrackingcats[15] = '' --reset for another check later
ttrackingcats[12] = '[['..testcasecolon..'Category:Navseasoncats range irregular, 0-length]]'
regularparent = 'isolated'
ttlens[0] = 1 --calc ttlens for std cases below
end
if (string.match(finish or '', '^%d+$') == nil) and
(string.match(finish or '', '^%-%d+$') == nil)
then
local finish_fixed = mw.ustring.match(finish or '', '^%s*(%d%d?%d?%d?)%D')
if finish_fixed then
finish = finish_fixed
else
errors = p.errorclass('Function nav_hyphen can\'t recognize "'..(finish or '')..'" '..
'in the second part of the "season" that was passed to it. '..
'For e.g. "2015–16", "16" is expected via "|2015|–|16|".')
return p.failedcat(errors, 'I')
end
else
if string.len(finish) >= 5 then
errors = p.errorclass('The second part of the season passed to function nav_hyphen should only be four or fewer digits, not "'..(finish or '')..'". '..
'See [[MOS:DATERANGE]] for details.')
return p.failedcat(errors, 'J')
end
end
local nfinish = tonumber(finish)
--save sterilized parent range for easier lookup later
tirregs['from0'] = nstart
tirregs['to0'] = nfinish
--sterilize min/max
local nminseas_default = -9999
local nmaxseas_default = 9999
local nminseas = tonumber(minseas) or nminseas_default --same behavior as nav_year
local nmaxseas = tonumber(maxseas) or nmaxseas_default --same behavior as nav_year
if nminseas > nstart then nminseas = nstart end
if nmaxseas < nstart then nmaxseas = nstart end
local lspace = ' ' --assume a leading space (most common)
local tspace = ' ' --assume a trailing space (most common)
if string.match(firstpart, '%($') then lspace = '' end --DNE for "Madrid city councillors (2007–2011)"-type cats
if string.match(lastpart, '^%)') then tspace = '' end --DNE for "Madrid city councillors (2007–2011)"-type cats
--calculate term length/intRAseason size & finishing year
local term_limit = 10
local t = 1
while t <= term_limit and regularparent == true do
local nish = nstart + t --use switchADBC to flip this sign to work for years BC, if/when the time comes
if (nish == nfinish) or (string.match(nish, '%d?%d$') == finish) then
ttlens[t] = 1
break
end
if t == term_limit then
errors = p.errorclass('Function nav_hyphen can\'t determine a reasonable term length for "'..start..hyph..finish..'".')
return p.failedcat(errors, 'K')
end
t = t + 1
end
--apply MOS:DATERANGE to parent
local lenstart = string.len(start)
local lenfinish = string.len(finish)
if lenstart == 4 and regularparent == true then --"2001–..."
if t == 1 then --"2001–02" & "2001–2002" both allowed
if lenfinish ~= 2 and lenfinish ~= 4 then
errors = p.errorclass('The second part of the season passed to function nav_hyphen should be two or four digits, not "'..finish..'".')
return p.failedcat(errors, 'L')
end
else --"2001–2005" is required for t > 1; track "2001–05"; anything else = error
if lenfinish == 2 then
if avoidself then
ttrackingcats[5] = '[['..testcasecolon..'Category:Navseasoncats range abbreviated (MOS)]]'
end
elseif lenfinish ~= 4 then
errors = p.errorclass('The second part of the season passed to function nav_hyphen should be four digits, not "'..finish..'".')
return p.failedcat(errors, 'M')
end
end
if finish == '00' and avoidself then --full year required regardless of term length
ttrackingcats[5] = '[['..testcasecolon..'Category:Navseasoncats range abbreviated (MOS)]]'
end
end
--calculate intERseason gap size
local hgap_default = 0 --assume & start at the most common case: 2001–02 -> 2002–03, etc.
local hgap_limit_reg = 5 --less expensive per-increment (inc x 4)
local hgap_limit_irreg = 5 --more expensive per-increment (inc x 23: inc x (k_bwd + k_fwd) = inc x (12 + 11))
local hgap_success = false
local hgap = hgap_default
while hgap <= hgap_limit_reg and regularparent == true do --verify
local prevseason2 = firstpart..lspace..(nstart-t-hgap)..hyph..string.match(nstart-hgap, '%d?%d$') ..tspace..lastpart
local nextseason2 = firstpart..lspace..(nstart+t+hgap)..hyph..string.match(nstart+2*t+hgap, '%d?%d$')..tspace..lastpart
local prevseason4 = firstpart..lspace..(nstart-t-hgap)..hyph..(nstart-hgap) ..tspace..lastpart
local nextseason4 = firstpart..lspace..(nstart+t+hgap)..hyph..(nstart+2*t+hgap)..tspace..lastpart
if t == 1 then --test abbreviated range first, then full range
if mw.title.new(prevseason2, 'Category').exists or --use 'or', in case we're at the edge of the cat structure,
mw.title.new(nextseason2, 'Category').exists or --or we hit a "–00"/"–2000" situation on one side
mw.title.new(prevseason4, 'Category').exists or
mw.title.new(nextseason4, 'Category').exists
then
hgap_success = true
break
end
elseif t > 1 then --test full range first, then abbreviated range
if mw.title.new(prevseason4, 'Category').exists or --use 'or', in case we're at the edge of the cat structure,
mw.title.new(nextseason4, 'Category').exists or --or we hit a "–00"/"–2000" situation on one side
mw.title.new(prevseason2, 'Category').exists or
mw.title.new(nextseason2, 'Category').exists
then
hgap_success = true
break
end
end
hgap = hgap + 1
end
if hgap_success == false then
hgap = tonumber(testgap) or hgap_default --tracked via defaultgapcat()
end
--preliminary scan to determine ir/regular spacing of nearby cats;
--to limit expensive function calls, MOS:DATERANGE-violating cats are ignored;
--an irregular-term-length series should follow "YYYY..hyph..YYYY" throughout
if hgap <= hgap_limit_reg then --also to isolate temp vars
--find # of nav-visible ir/regular-term-length cats
local bwanchor = nstart --backward anchor/common year
local fwanchor = bwanchor + t --forward anchor/common year
if regularparent == 'isolated' then
fwanchor = bwanchor
end
local spangreen = '[<span style="color:green">j, g, k = ' --used for/when debugging via list-all-links=yes
local spanblue = '<span style="color:blue">'
local spanred = ' (<span style="color:red">'
local span = '</span>'
local lastg = nil --to check for run-on searches
local lastk = nil --to check for run-on searches
local endfound = false --switch used to stop searching forward
local iirregs = 0 --index of tirregs[] for j < 0, since search starts from parent
local j = -3 --pseudo nav position & index of tirregs[] for j > 0
while j <= 3 do
if j < 0 then --search backward from parent
local gbreak = false --switch used to break out of g-loop
local g = 0 --gap size
while g <= hgap_limit_irreg do
local k = 0 --term length; 0 = "0-length"; 1+ = normal
while k <= term_limit do
local from = bwanchor - k - g
local to = bwanchor - g
local full = mw.text.trim( firstpart..lspace..from..hyph..to..tspace..lastpart )
if k == 0 then
if regularparent ~= 'isolated' then --+restrict to g == 0 if repeating year problems arise
to = '0-length'
full = mw.text.trim( firstpart..lspace..from..tspace..lastpart )
if catlinkfollowr( full )[3] ~= nil then
table.insert( tlistallbwd, spangreen..j..', '..g..', '..k..span..'] '..full..spanred..'#R ignored'..span..')' )
full, to = '', '' --don't use/follow 0-length cat #Rs from nav_hyphen(); otherwise gets messy
end
end
end
if (k >= 1) or --the normal case; only continue k = 0 if 0-length found
(to == '0-length') --ghetto "continue" (thx Lua) to avoid expensive searches for "UK MPs 1974-1974", etc.
then
table.insert( tlistallbwd, spangreen..j..', '..g..', '..k..span..'] '..full )
if (k == 1) and
(g == 0) and
(mw.title.new( full, 'Category' ).exists == false)
then --allow bare-bones MOS:DATERANGE alternation, in case we're on a 0-gap, 1-year term series
local to2 = string.match(to, '%d%d$')
if to2 and to2 ~= '00' then --and not at a century transition (i.e. 1999–2000)
to = to2
full = mw.text.trim( firstpart..lspace..from..hyph..to..tspace..lastpart )
table.insert( tlistallbwd, spangreen..j..', '..g..', '..k..span..'] '..full )
end
end
if mw.title.new( full, 'Category' ).exists then
if to == '0-length' then
ttrackingcats[12] = '[['..testcasecolon..'Category:Navseasoncats range irregular, 0-length]]'
end
tlistallbwd[#tlistallbwd] = spanblue..tlistallbwd[#tlistallbwd]..span..' (found)'
ttlens[ find_duration(full) ] = 1
tgaps[g] = 1
iirregs = iirregs + 1
tirregs['from-'..iirregs] = from
tirregs['to-'..iirregs] = to
bwanchor = from --ratchet down
if to ~= '0-length' then
gbreak = true
break
else
j = j + 1 --save, but keep searching
if j > 3 then --lest we keep searching & finding 0-length cats ("MEPs for the Republic of Ireland 1973" & down)
gbreak = true
break
end
end
end
end --ghetto "continue"
k = k + 1
lastk = k
end
if gbreak == true then break end
g = g + 1
lastg = g
end
end --if j < 0
if j > 0 and endfound == false then --search forward from parent
local gbreak = false --switch used to break out of g-loop
local g = 0 --gap size
while g <= hgap_limit_irreg do
local k = -2 --term length; -2 = "0-length"; -1 = "2020–present"; 0 = "2020–"; 1+ = normal
while k <= term_limit do
local from = fwanchor + g
local to4 = fwanchor + k + g --override carefully
local to2 = nil --last 2 digits of to4, IIF exists
if k == -1 then to4 = 'present' --see if end-cat exists (present)
elseif k == 0 then to4 = '' end --see if end-cat exists (blank)
local full = mw.text.trim( firstpart..lspace..from..hyph..to4..tspace..lastpart )
if k == -2 then
if regularparent ~= 'isolated' then --+restrict to g == 0 if repeating year problems arise
to4 = '0-length' --see if 0-length cat exists
full = mw.text.trim( firstpart..lspace..from..tspace..lastpart )
if catlinkfollowr( full )[3] ~= nil then
table.insert( tlistallfwd, spangreen..j..', '..g..', '..k..span..'] '..full..spanred..'#R ignored'..span..')' )
full, to4 = '', '' --don't use/follow 0-length cat #Rs from nav_hyphen(); otherwise gets messy
end
end
end
if (k >= -1) or --only continue k = -2 if 0-length found
(to4 == '0-length') --ghetto "continue" (thx Lua) to avoid expensive searches for "UK MPs 1974-1974", etc.
then
table.insert( tlistallfwd, spangreen..j..', '..g..', '..k..span..'] '..full )
if (g == 0) and
(k == 1) and
(mw.title.new( full, 'Category' ).exists == false)
then --allow bare-bones MOS:DATERANGE alternation, in case we're on a 0-gap, 1-year term series
to2 = string.match(to4, '%d%d$')
if to2 and to2 ~= '00' then --and not at a century transition (i.e. 1999–2000)
full = mw.text.trim( firstpart..lspace..from..hyph..to2..tspace..lastpart )
table.insert( tlistallfwd, spangreen..j..', '..g..', '..k..span..'] '..full )
end
end
if mw.title.new( full, 'Category' ).exists then
if to4 == '0-length' then
if rtarget(full) == full then --only use 0-length cats that don't #R
ttrackingcats[12] = '[['..testcasecolon..'Category:Navseasoncats range irregular, 0-length]]'
end
end
tirregs['from'..j] = from
tirregs['to'..j] = (to2 or to4)
if (k == -1) or (k == 0) then
endfound = true --tentative
else --k == { -2, > 0 }
tlistallfwd[#tlistallfwd] = spanblue..tlistallfwd[#tlistallfwd]..span..' (found)'
ttlens[ find_duration(full) ] = 1
tgaps[g] = 1
endfound = false
if to4 ~= '0-length' then --k > 0
fwanchor = to4 --ratchet up
gbreak = true
break --only break on k > 0 b/c old end-cat #Rs still exist like "Members of the Scottish Parliament 2011–"
else --k == -2
j = j + 1 --save, but keep searching k's, in case "1974" → "1974-1979"
if j > 3 then --lest we keep searching & finding 0-length cats ("2018 CONCACAF Champions League" & up)
gbreak = true
break
end
end
end
end
end --ghetto "continue"
k = k + 1
lastk = k
end
if gbreak == true then break end
g = g + 1
lastg = g
end
end --if j > 0
if (lastg == (hgap_limit_irreg + 1)) and
(lastk == (term_limit + 1))
then --search exhausted
if j < 0 then j = 0 --bwd search exhausted; continue fwd
elseif j > 0 then break end --fwd search exhausted
end
j = j + 1
end --while j <= 3
end --if hgap <= hgap_limit_reg
--begin navhyphen
local navh = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n'
local terminalcat = false --switch used to hide future cats
local terminaltxt = nil
local i = -3 --nav position
while i <= 3 do
local from = nstart + i*(t+hgap) --the logical, but not necessarily correct, 'from'
if tirregs['from'..i] then from = tonumber(tirregs['from'..i]) end --prefer irregular term table
local from2 = string.match(from, '%d?%d$')
local to = tostring(from+t) --the logical, naive range, but
if tirregs['to'..i] then --prefer irregular term table
to = tirregs['to'..i]
elseif regularparent == false and tirregs and i > 0 then
to = tirregs['to-1'] --special treatment for parent terminal cats, since they have no natural 'to'
end
local to2 = string.match(to, '%d?%d$')
local tofinal = (to2 or '') --assume t=1 and abbreviated 'to' (the most common case)
if t > 1 or --per MOS:DATERANGE (e.g. 1999-2004)
(from2 - (to2 or from2)) > 0 --century transition exception (e.g. 1999–2000)
then
tofinal = (to or '') --default to the MOS-correct format, in case no fallbacks found
end
if to == '0-length' then
tofinal = to
end
--check existance of 4-digit, MOS-correct range, with abbreviation fallback
if tofinal ~= '0-length' then
if t > 1 and string.len(from) == 4 then --e.g. 1999-2004
--determine which link exists (full or abbr)
local full = firstpart..lspace..from..hyph..tofinal..tspace..lastpart
if not mw.title.new( full, 'Category' ).exists then
local abbr = firstpart..lspace..from..hyph..to2..tspace..lastpart
if mw.title.new( abbr, 'Category' ).exists then
tofinal = (to2 or '') --rv to MOS-incorrect format; if full AND abbr DNE, then tofinal is still in its MOS-correct format
end
end
elseif t == 1 then --full-year consecutive ranges are also allowed
local abbr = firstpart..lspace..from..hyph..tofinal..tspace..lastpart --assume tofinal is in abbr format
if not mw.title.new( abbr, 'Category' ).exists and tofinal ~= to then
local full = firstpart..lspace..from..hyph..to..tspace..lastpart
if mw.title.new( full, 'Category' ).exists then
tofinal = (to or '') --if abbr AND full DNE, then tofinal is still in its abbr format (unless it's a century transition)
end end end end
--populate navh
if i ~= 0 then --left/right navh
local orig = firstpart..lspace..from..hyph..tofinal..tspace..lastpart
local disp = from..hyph..tofinal
if tofinal == '0-length' then
orig = firstpart..lspace..from..tspace..lastpart
disp = from
end
local catlink = catlinkfollowr(orig, disp, true) --force terminal cat display
if terminalcat == false then
terminaltxt = find_terminaltxt( disp ) --also sets tracking cats
terminalcat = (terminaltxt ~= nil)
end
if catlink[3] and avoidself then --a {{Category redirect}} was followed, figure out why
--determine new term length & gap size
ttlens[ find_duration( catlink[3] ) ] = 1
if i > -3 then
local lastto = tirregs['to'..(i-1)]
if lastto == nil then
local lastfrom = nstart + (i-1)*(t+hgap)
lastto = lastfrom+t --use last logical 'from' to calc lastto
end
if lastto then
local gapcat = lastto..'-'..from --dummy cat to calc with
local gap = find_duration(gapcat) or -1 --in case of nil,
tgaps[ gap ] = 1 --tgaps[-1] is ignored
end
end
--display/tracking handling
local base_regex = '%d+[–-]%d+'
local origbase = mw.ustring.gsub(orig, base_regex, '')
local rtarbase = mw.ustring.gsub(catlink[3], base_regex, '')
local terminal_regex = '%d+[–-]'..(terminaltxt or '')..'$' --more manual ORs bc Lua regex sux
if mw.ustring.match(orig, terminal_regex) then
origbase = mw.ustring.gsub(orig, terminal_regex, '')
end
if mw.ustring.match(catlink[3], terminal_regex) then
--finagle/overload terminalcat type to set nmaxseas on 1st occurence only
if terminalcat == false then terminalcat = 1 end
local dummy = find_terminaltxt( catlink[3] ) --also sets tracking cats
rtarbase = mw.ustring.gsub(catlink[3], terminal_regex, '')
end
origbase = mw.text.trim(origbase)
rtarbase = mw.text.trim(rtarbase)
if origbase ~= rtarbase then
ttrackingcats[6] = '[['..testcasecolon..'Category:Navseasoncats range redirected (base change)]]'
elseif terminalcat == 1 then
ttrackingcats[7] = '[['..testcasecolon..'Category:Navseasoncats range redirected (end)]]'
else
local all4s_regex = '%d%d%d%d[–-]%d%d%d%d'
local all4s = (mw.ustring.match(orig, all4s_regex) and
mw.ustring.match(catlink[3], all4s_regex))
if all4s then
ttrackingcats[9] = '[['..testcasecolon..'Category:Navseasoncats range redirected (other)]]'
else
ttrackingcats[8] = '[['..testcasecolon..'Category:Navseasoncats range redirected (MOS)]]'
end
end
end
if terminalcat then --true or 1
if type(terminalcat) ~= 'boolean' then nmaxseas = from end --only want to do this once
terminalcat = true --done finagling/overloading
end
if (from >= 0) and (nminseas <= from) and (from <= nmaxseas) then
navh = navh..'*'..catlink[1]..'\n'
if terminalcat then nmaxseas = nminseas_default end --prevent display of future ranges
else
local hidden = '<span style="visibility:hidden">'..disp..'</span>'
navh = navh..'*'..hidden..'\n'
if listall then
tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
end
end
else --center navh
if finish == -1 then finish = 'present'
elseif finish == 0 then finish = '<span style="visibility:hidden">'..start..'</span>' end
local disp = start..hyph..finish
if regularparent == 'isolated' then disp = start end
navh = navh..'*<b>'..disp..'</b>\n'
end
i = i + 1
end
--tracking cats & finalize
if avoidself then
local igaps = 0 --# of diff gap sizes > 0 found
local itlens = 0 --# of diff term lengths found
for s = 1, hgap_limit_reg do --must loop; #tgaps, #ttlens unreliable
igaps = igaps + (tgaps[s] or 0)
end
for s = 0, term_limit do
itlens = itlens + (ttlens[s] or 0)
end
if igaps > 0 then ttrackingcats[10] = '[['..testcasecolon..'Category:Navseasoncats range gaps]]' end
if itlens > 1 and ttrackingcats[12] == '' then --avoid duplication in "Navseasoncats range irregular, 0-length"
ttrackingcats[11] = '[['..testcasecolon..'Category:Navseasoncats range irregular]]'
end
end
isolatedcat()
defaultgapcat(not hgap_success)
if listall then
return listalllinks()
else
return navh..'|}'
end
end
--[[=========================={{ nav_tvseason }}============================]]
function nav_tvseason( firstpart, tv, lastpart, maximumtv )
--Expects a PAGENAME of the form "Futurama (season 1) episodes", where
-- firstpart = Futurama (season
-- tv = 1
-- lastpart = ) episodes
-- maximumtv = 7 ('max' tv season parameter; optional; defaults to 9999)
tv = tonumber(tv)
if tv == nil then
errors = p.errorclass('Function nav_tvseason can\'t recognize the TV season number sent to its 2nd parameter.')
return p.failedcat(errors, 'T')
end
local maxtv = tonumber(maximumtv) or 9999 --allow +/- qualifier
if maxtv < tv then maxtv = tv end --input error; maxtv should be >= parent
--begin navtvseason
local navt = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n'
local i = -5 --nav position
while i <= 5 do
local t = tv + i
if i ~= 0 then --left/right navt
local catlink = catlinkfollowr( firstpart..' '..t..lastpart, t )
if (t >= 1 and t <= maxtv) then --hardcode mintv
if catlink[3] and avoidself then --a {{Category redirect}} was followed
ttrackingcats[22] = '[['..testcasecolon..'Category:Navseasoncats TV season redirected]]'
end
navt = navt..'*'..catlink[1]..'\n'
else
local hidden = '<span style="visibility:hidden">'..'0'..'</span>' --'0' to maintain dot spacing
navt = navt..'*'..hidden..'\n'
if listall then
tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
end
end
else --center navt
navt = navt..'*<b>'..tv..'</b>\n'
end
i = i + 1
end
isolatedcat()
if listall then
return listalllinks()
else
return navt..'|}'
end
end
--[[==========================={{ nav_decade }}=============================]]
function nav_decade( firstpart, decade, lastpart, mindecade, maxdecade )
--Expects a PAGENAME of the form "Some sequential 2000 example cat", where
-- firstpart = Some sequential
-- decade = 2000
-- lastpart = example cat
-- mindecade = 1800 ('min' decade parameter; optional; defaults to -9999)
-- maxdecade = 2020 ('max' decade parameter; optional; defaults to 9999)
--sterilize dec
local dec = sterilizedec(decade)
if dec == nil then
errors = p.errorclass('Function nav_decade was sent "'..(decade or '')..'" as its 2nd parameter, '..
'but expects a 1 to 4-digit year ending in "0".')
return p.failedcat(errors, 'D')
end
local ndec = tonumber(dec)
--sterilize mindecade & determine AD/BC
local mindefault = '-9999'
local mindec = sterilizedec(mindecade) --returns a tostring(unsigned int), or nil
if mindec then
if string.match(mindecade, '-%d') or
string.match(mindecade, 'BC')
then
mindec = '-'..mindec --better +/-0 behavior with strings (0-initialized int == "-0" string...)
end
elseif mindec == nil and mindecade and mindecade ~= '' then
errors = p.errorclass('Function nav_decade was sent "'..(mindecade or '')..'" as its 4th parameter, '..
'but expects a 1 to 4-digit year ending in "0", the earliest decade to be shown.')
return p.failedcat(errors, 'E')
else --mindec == nil
mindec = mindefault --tonumber() later, after error checks
end
--sterilize maxdecade & determine AD/BC
local maxdefault = '9999'
local maxdec = sterilizedec(maxdecade) --returns a tostring(unsigned int), or nil + error
if maxdec then
if string.match(maxdecade, '-%d') or
string.match(maxdecade, 'BC')
then --better +/-0 behavior with strings (0-initialized int == "-0" string...),
maxdec = '-'..maxdec --but a "-0" string -> tonumber() -> tostring() = "-0",
end --and a "0" string -> tonumber() -> tostring() = "0"
elseif maxdec == nil and maxdecade and maxdecade ~= '' then
errors = p.errorclass('Function nav_decade was sent "'..(maxdecade or '')..'" as its 5th parameter, '..
'but expects a 1 to 4-digit year ending in "0", the highest decade to be shown.')
return p.failedcat(errors, 'F')
else --maxdec == nil
maxdec = maxdefault
end
local tspace = ' ' --assume trailing space for "1950s in X"-type cats
if string.match(lastpart, '^-') then tspace = '' end --DNE for "1970s-related"-type cats
--AD/BC switches & vars
local parentBC = string.match(lastpart, '^BC') --following the "0s BC" convention for all years BC
lastpart = mw.ustring.gsub(lastpart, '^BC%s*', '') --handle BC separately; AD never used
--TODO?: handle BCE, but only if it exists in the wild
local dec0to40AD = (ndec >= 0 and ndec <= 40 and not parentBC) --special behavior in this range
local switchADBC = 1 -- 1=AD parent
if parentBC then switchADBC = -1 end -- -1=BC parent; possibly adjusted later
local BCdisp = ''
local D = -math.huge --secondary switch & iterator for AD/BC transition
--check non-default min/max more carefully
if mindec ~= mindefault then
if tonumber(mindec) > ndec*switchADBC then
mindec = tostring(ndec*switchADBC) --input error; mindec should be <= parent
end
end
if maxdec ~= maxdefault then
if tonumber(maxdec) < ndec*switchADBC then
maxdec = tostring(ndec*switchADBC) --input error; maxdec should be >= parent
end
end
local nmindec = tonumber(mindec) --similar behavior to nav_year & nav_nordinal
local nmaxdec = tonumber(maxdec) --similar behavior to nav_nordinal
--begin navdecade
local bnb = '' --border/no border
if navborder == false then --for Navseasoncats year and decade
bnb = ' border-style: none; background-color: transparent;'
end
local navd = '{| class="toccolours hlist" style="text-align: center; margin: auto;'..bnb..'"\n'..'|\n'
local i = -50 --nav position x 10
while i <= 50 do
local d = ndec + i*switchADBC
local BC = ''
BCdisp = ''
if dec0to40AD then
if D < -10 then
d = math.abs(d + 10) --b/c 2 "0s" decades exist: "0s BC" & "0s" (AD)
BC = 'BC '
if d == 0 then
D = -10 --track 1st d = 0 use (BC)
end
elseif D >= -10 then
D = D + 10 --now iterate from 0s AD
d = D --2nd d = 0 use
end
elseif parentBC then
if switchADBC == -1 then --parentBC looking at the BC side (the common case)
BC = 'BC '
if d == 0 then --prepare to switch to the AD side on the next iteration
switchADBC = 1 --1st d = 0 use (BC)
D = -10 --prep
end
elseif switchADBC == 1 then --switched to the AD side
D = D + 10 --now iterate from 0s AD
d = D --2nd d = 0 use (on first use)
end
end
if BC ~= '' and ndec <= 50 then
BCdisp = ' BC' --show BC for all BC decades whenever a "0s" is displayed on the nav
end
--determine target cat
local disp = d..'s'..BCdisp
local catlink = catlinkfollowr( firstpart..' '..d..'s'..tspace..BC..lastpart, disp )
if catlink[3] and avoidself then --a {{Category redirect}} was followed
ttrackingcats[17] = '[['..testcasecolon..'Category:Navseasoncats decade redirected]]'
end
--populate left/right navd
local shown = navcenter(i, catlink)
local hidden = '<span style="visibility:hidden">'..disp..'</span>'
local dsign = d --use d for display & dsign for logic
if BC ~= '' then dsign = -dsign end
if (nmindec <= dsign) and (dsign <= nmaxdec) then
if dsign == 0 and (nmindec == 0 or nmaxdec == 0) then --distinguish b/w -0 (BC) & 0 (AD)
--"zoom in" on +/- 0 and turn dsign/min/max temporarily into +/- 1 for easier processing
local zsign, zmin, zmax = 1, nmindec, nmaxdec
if BC ~= '' then zsign = -1 end
if mindec == '-0' then zmin = -1
elseif mindec == '0' then zmin = 1 end
if maxdec == '-0' then zmax = -1
elseif maxdec == '0' then zmax = 1 end
if (zmin <= zsign) and (zsign <= zmax) then
navd = navd..shown
hidden = nil
else
navd = navd..'*'..hidden..'\n'
end
else
navd = navd..shown --the common case
hidden = nil
end
else
navd = navd..'*'..hidden..'\n'
end
if listall and hidden then
tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
end
i = i + 10
end
isolatedcat()
if listall then
return listalllinks()
else
return navd..'|}'
end
end
--[[============================{{ nav_year }}==============================]]
function nav_year( firstpart, year, lastpart, minimumyear, maximumyear )
--Expects a PAGENAME of the form "Some sequential 1760 example cat", where
-- firstpart = Some sequential
-- year = 1760
-- lastpart = example cat
-- minimumyear = 1758 ('min' year parameter; optional)
-- maximumyear = 1800 ('max' year parameter; optional)
local minyear_default = -9999
local maxyear_default = 9999
year = tonumber(year) or tonumber(mw.ustring.match(year or '', '^%s*(%d*)'))
local minyear = tonumber(string.match(minimumyear or '', '-?%d+')) or minyear_default --allow +/- qualifier
local maxyear = tonumber(string.match(maximumyear or '', '-?%d+')) or maxyear_default --allow +/- qualifier
if string.match(minimumyear or '', 'BC') then minyear = -math.abs(minyear) end --allow BC qualifier (AD otherwise assumed)
if string.match(maximumyear or '', 'BC') then maxyear = -math.abs(maxyear) end --allow BC qualifier (AD otherwise assumed)
if year == nil then
errors = p.errorclass('Function nav_year can\'t recognize the year sent to its 2nd parameter.')
return p.failedcat(errors, 'Y')
end
--AD/BC switches & vars
local yearBCElastparts = { --needed for parent = AD 1-5, when the BC/E format is unknown
--"BCE" removed to match both AD & BCE cats; easier & faster than multiple string.match()s
['example_Hebrew people_example'] = 'BCE', --example entry format; add to & adjust as needed
}
local parentAD = string.match(firstpart, 'AD$') --following the "AD 1" convention from AD 1 to AD 10
local parentBC = string.match(lastpart, '^BCE?') --following the "1 BC" convention for all years BC
firstpart = mw.ustring.gsub(firstpart, '%s*AD$', '') --handle AD/BC separately for easier & faster accounting
lastpart = mw.ustring.gsub(lastpart, '^BCE?%s*', '')
local BCe = parentBC or yearBCElastparts[lastpart] or 'BC' --"BC" default
local year1to15AD = (year >= 1 and year <= 15 and not parentBC) --special behavior in this range
local switchADBC = 1 -- 1=AD parent
if parentBC then switchADBC = -1 end -- -1=BC parent; possibly adjusted later
local Y = 0 --secondary iterator for AD-on-a-BC-parent
if minyear > year*switchADBC then minyear = year*switchADBC end --input error; minyear should be <= parent
if maxyear < year*switchADBC then maxyear = year*switchADBC end --input error; maxyear should be >= parent
local tspace = ' ' --trailing space after year, before lastpart
if string.match(lastpart, '^-') then
tspace = '' --e.g. "2018-related timelines"
end
--determine interyear gap size to condense special category types, if possible
local ygapdefault = 1 --assume/start at the most common case: 2001, 2002, etc.
local ygap = ygapdefault
if string.match(lastpart, 'presidential') then
local ygap1, ygap2 = ygapdefault, ygapdefault --need to determine previous & next year gaps indepedently
local ygap1_success, ygap2_success = false, false
local prevseason = nil
while ygap1 <= 5 do --Czech Republic, Poland, Sri Lanka, etc. have 5-year terms
prevseason = firstpart..' '..(year-ygap1)..tspace..lastpart
if mw.title.new(prevseason, 'Category').exists then
ygap1_success = true
break
end
ygap1 = ygap1 + 1
end
local nextseason = nil
while ygap2 <= 5 do --Czech Republic, Poland, Sri Lanka, etc. have 5-year terms
nextseason = firstpart..' '..(year+ygap2)..tspace..lastpart
if mw.title.new(nextseason, 'Category').exists then
ygap2_success = true
break
end
ygap2 = ygap2 + 1
end
if ygap1_success and ygap2_success then
if ygap1 == ygap2 then ygap = ygap1 end
elseif ygap1_success then ygap = ygap1
elseif ygap2_success then ygap = ygap2
end
end
--skip non-existing years, if requested
local ynogaps = {} --populate with existing years in the range, at most, [year-60, year+60]
local skipgaps_limit = 25
if skipgaps then
if minyear == minyear_default then
minyear = 0 --automatically set minyear to 0, as AD/BC not supported anyway
end
if (year > 70) or --add support for AD/BC (<= AD 10) if/when needed
(minyear >= 0 and --must be a non-year series like "AC with 0 elements"
not parentAD and not parentBC)
then
local yskipped = {} --track skipped y's to avoid double-checking
local cat, found, Yeary
--populate nav element queue outwards positively from the parent
local Year = year --to save/ratchet progression
local i = 1
while i <= 5 do
local y = 1
while y <= skipgaps_limit do
found = false
Yeary = Year + y
if yskipped[Yeary] == nil then
yskipped[Yeary] = Yeary
cat = firstpart..' '..Yeary..tspace..lastpart
found = mw.title.new( cat, 'Category' ).exists
if found then break end
end
y = y + 1
end
if found then Year = Yeary
else Year = Year + 1 end
ynogaps[i] = Year
i = i + 1
end
ynogaps[0] = year --the parent
--populate nav element queue outwards negatively from the parent
Year = year --reset ratchet
i = -1
while i >= -5 do
local y = -1
while y >= -skipgaps_limit do
found = false
Yeary = Year + y
if yskipped[Yeary] == nil then
yskipped[Yeary] = Yeary
cat = firstpart..' '..Yeary..tspace..lastpart
found = mw.title.new( cat, 'Category' ).exists
if found then break end
end
y = y - 1
end
if found then Year = Yeary
else Year = Year - 1 end
ynogaps[i] = Year
i = i - 1
end
else
skipgaps = false --AD/BC not supported
end
end
--begin navyears
local navy = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n'
local y
local i = -5 --nav position
while i <= 5 do
if skipgaps then
y = ynogaps[i]
else
y = year + i*ygap*switchADBC
end
local BCdisp = ''
if i ~= 0 then --left/right navy
local AD = ''
local BC = ''
if year1to15AD then
if year >= 11 then --parent = AD 11-15
if y <= 10 then --prepend AD on y = 1-10 cats only, per existing cats
AD = 'AD '
end
elseif year >= 1 then --parent = AD 1-10
if y <= 0 then
BC = BCe..' '
y = math.abs(y - 1) --skip y = 0 (DNE)
elseif y >= 1 and y <= 10 then --prepend AD on y = 1-10 cats only, per existing cats
AD = 'AD '
end
end
elseif parentBC then
if switchADBC == -1 then --displayed y is in the BC regime
if y >= 1 then --the common case
BC = BCe..' '
elseif y == 0 then --switch from BC to AD regime
switchADBC = 1
end
end
if switchADBC == 1 then --displayed y is now in the AD regime
Y = Y + 1 --skip y = 0 (DNE)
y = Y --easiest solution: start another iterator for these AD y's displayed on a BC year parent
AD = 'AD '
end
end
if BC ~= '' and year <= 5 then --only show 'BC' for parent years <= 5: saves room, easier to read,
BCdisp = ' '..BCe --and 6 is the first/last nav year that doesn't need a disambiguator;
end --the center/parent year will always show BC, so no need to show it another 10x
--populate left/right navy
local ysign = y --use y for display & ysign for logic
local disp = y..BCdisp
if BC ~= '' then ysign = -ysign end
local catlink = catlinkfollowr( firstpart..' '..AD..y..tspace..BC..lastpart, disp )
if (minyear <= ysign) and (ysign <= maxyear) then --ex: 1758, 1759, 1761, 1762, 1763, 1764, 1765
if catlink[3] and avoidself then --a {{Category redirect}} was followed
ttrackingcats[18] = '[['..testcasecolon..'Category:Navseasoncats year redirected]]'
end
navy = navy..'*'..catlink[1]..'\n'
else --ex: 1755, 1756, 1757
local hidden = '<span style="visibility:hidden">'..disp..'</span>'
navy = navy..'*'..hidden..'\n'
if listall then
tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
end
end
else --center navy; ex: 1760
if parentBC then BCdisp = ' '..BCe end
navy = navy..'*<b>'..year..BCdisp..'</b>\n'
end
i = i + 1
end
isolatedcat()
if listall then
return listalllinks()
else
return navy..'|}'
end
end
--[[==========================={{ nav_roman }}==============================]]
function nav_roman( firstpart, roman, lastpart, minimumrom, maximumrom )
local toarabic = require('Module:ConvertNumeric').roman_to_numeral
local toroman = require('Module:Roman').main
--sterilize/convert rom/num
local num = tonumber(toarabic(roman))
local rom = toroman({ [1] = num })
if num == nil or rom == nil then --out of range or some other error
errors = p.errorclass('Function nav_roman can\'t recognize one or more of "'..(num or 'nil')..'" & "'..
(rom or 'nil')..'" in category "'..firstpart..' '..roman..' '..lastpart..'".')
return p.failedcat(errors, 'R')
end
--sterilize min/max
local minrom = tonumber(minimumrom or '') or tonumber(toarabic(minimumrom or ''))
local maxrom = tonumber(maximumrom or '') or tonumber(toarabic(maximumrom or ''))
if minrom < 1 then minrom = 1 end --toarabic() returns -1 on error
if maxrom < 1 then maxrom = 9999 end --toarabic() returns -1 on error
if minrom > num then minrom = num end
if maxrom < num then maxrom = num end
--begin navroman
local navr = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n'
local i = -5 --nav position
while i <= 5 do
local n = num + i
if n >= 1 then
local r = toroman({ [1] = n })
if i ~= 0 then --left/right navr
local catlink = catlinkfollowr( firstpart..' '..r..' '..lastpart, r )
if minrom <= n and n <= maxrom then
if catlink[3] and avoidself then --a {{Category redirect}} was followed
ttrackingcats[19] = '[['..testcasecolon..'Category:Navseasoncats roman numeral redirected]]'
end
navr = navr..'*'..catlink[1]..'\n'
else
local hidden = '<span style="visibility:hidden">'..r..'</span>'
navr = navr..'*'..hidden..'\n'
if listall then
tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
end
end
else --center navr
navr = navr..'*<b>'..r..'</b>\n'
end
else
navr = navr..'*<span style="visibility:hidden">'..'I'..'</span>\n'
end
i = i + 1
end
isolatedcat()
if listall then
return listalllinks()
else
return navr..'|}'
end
end
--[[=========================={{ nav_nordinal }}============================]]
function nav_nordinal( firstpart, ord, lastpart, minimumord, maximumord )
local nord = tonumber(ord)
local minord = tonumber(string.match(minimumord or '', '(-?%d+)[snrt]?[tdh]?')) or -9999 --allow full ord & +/- qualifier
local maxord = tonumber(string.match(maximumord or '', '(-?%d+)[snrt]?[tdh]?')) or 9999 --allow full ord & +/- qualifier
if string.match(minimumord or '', 'BC') then minord = -math.abs(minord) end --allow BC qualifier (AD otherwise assumed)
if string.match(maximumord or '', 'BC') then maxord = -math.abs(maxord) end --allow BC qualifier (AD otherwise assumed)
local temporal = string.match(lastpart, 'century') or
string.match(lastpart, 'millennium')
local tspace = ' ' --assume a trailing space after ordinal
if string.match(lastpart, '^-') then tspace = '' end --DNE for "19th-century"-type cats
--AD/BC switches & vars
local ordBCElastparts = { --needed for parent = AD 1-5, when the BC/E format is unknown
--lists the lastpart of valid BCE cats
--"BCE" removed to match both AD & BCE cats; easier & faster than multiple string.match()s
['-century Hebrew people'] = 'BCE', --WP:CFD/Log/2016 June 21#Category:11th-century BC Hebrew people
['-century Jews'] = 'BCE', --co-nominated
['-century Judaism'] = 'BCE', --co-nominated
['-century rabbis'] = 'BCE', --co-nominated
}
local parentBC = mw.ustring.match(lastpart, '%s(BCE?)') --"1st-century BC" format
local lastpartNoBC = mw.ustring.gsub(lastpart, '%sBCE?', '') --easier than splitting lastpart up in 2; AD never used
local BCe = parentBC or ordBCElastparts[lastpartNoBC] or 'BC' --"BC" default
local switchADBC = 1 -- 1=AD parent
if parentBC then switchADBC = -1 end -- -1=BC parent; possibly adjusted later
local O = 0 --secondary iterator for AD-on-a-BC-parent
if not temporal and minord < 1 then minord = 1 end --nothing before "1st parliament", etc.
if minord > nord*switchADBC then minord = nord*switchADBC end --input error; minord should be <= parent
if maxord < nord*switchADBC then maxord = nord*switchADBC end --input error; maxord should be >= parent
--begin navnordinal
local bnb = '' --border/no border
if navborder == false then --for Navseasoncats decade and century
bnb = ' border-style: none; background-color: transparent;'
end
local navo = '{| class="toccolours hlist" style="text-align: center; margin: auto;'..bnb..'"\n'..'|\n'
local i = -5 --nav position
while i <= 5 do
local o = nord + i*switchADBC
local BC = ''
local BCdisp = ''
if parentBC then
if switchADBC == -1 then --parentBC looking at the BC side
if o >= 1 then --the common case
BC = ' '..BCe
elseif o == 0 then --switch to the AD side
BC = ''
switchADBC = 1
end
end
if switchADBC == 1 then --displayed o is now in the AD regime
O = O + 1 --skip o = 0 (DNE)
o = O --easiest solution: start another iterator for these AD o's displayed on a BC year parent
end
elseif o <= 0 then --parentAD looking at BC side
BC = ' '..BCe
o = math.abs(o - 1) --skip o = 0 (DNE)
end
if BC ~= '' and nord <= 5 then --only show 'BC' for parent ords <= 5: saves room, easier to read,
BCdisp = ' '..BCe --and 6 is the first/last nav ord that doesn't need a disambiguator;
end --the center/parent ord will always show BC, so no need to show it another 10x
--populate left/right navo
local oth = p.addord(o)
local osign = o --use o for display & osign for logic
if BC ~= '' then osign = -osign end
local hidden = '<span style="visibility:hidden">'..oth..'</span>'
if temporal then --e.g. "3rd-century BC"
local lastpart = lastpartNoBC --lest we recursively add multiple "BC"s
if BC ~= '' then
lastpart = string.gsub(lastpart, temporal, temporal..BC) --replace BC if needed
end
local catlink = catlinkfollowr( firstpart..' '..oth..tspace..lastpart, oth..BCdisp )
if (minord <= osign) and (osign <= maxord) then
if catlink[3] and avoidself then --a {{Category redirect}} was followed
ttrackingcats[20] = '[['..testcasecolon..'Category:Navseasoncats nordinal redirected]]'
end
navo = navo..navcenter(i, catlink)
else
navo = navo..'*'..hidden..'\n'
if listall then
tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
end
end
elseif BC == '' and minord <= osign and osign <= maxord then --e.g. >= "1st parliament"
local catlink = catlinkfollowr( firstpart..' '..oth..tspace..lastpart, oth )
if catlink[3] and avoidself then --a {{Category redirect}} was followed
ttrackingcats[20] = '[['..testcasecolon..'Category:Navseasoncats nordinal redirected]]'
end
navo = navo..navcenter(i, catlink)
else --either out-of-range (hide), or non-temporal + BC = something might be wrong (2nd X parliament BC?); handle exceptions if/as they arise
navo = navo..'*'..hidden..'\n'
end
i = i + 1
end
isolatedcat()
if listall then
return listalllinks()
else
return navo..'|}'
end
end
--[[========================={{ nav_wordinal }}=============================]]
function nav_wordinal( firstpart, word, lastpart, minimumword, maximumword, frame )
local eng2ord = require('Module:ConvertNumeric').english_to_ordinal
local ord2eng = require('Module:ConvertNumeric').numeral_to_english
local sc = string.match(word, '^%u') --sentence-case check
local lc = string.lower(word) --operate on/with lc, and restore any sc later
local nword = eng2ord(lc)
local case = nil
if sc then case = 'U' end
--sterilize min/max
local minword = 1
local maxword = 99
if minimumword then
local num = tonumber(minimumword)
if num and 0 < num and num < maxword then
minword = num
else
local ord = eng2ord(minimumword)
if 0 < ord and ord < maxword then
minword = ord
end
end
end
if maximumword then
local num = tonumber(maximumword)
if num and 0 < num and num < maxword then
maxword = num
else
local ord = eng2ord(maximumword)
if 0 < ord and ord < maxword then
maxword = ord
end
end
end
if minword > nword then minword = nword end
if maxword < nword then maxword = nword end
--begin navwordinal
local navw = '{| class="toccolours hlist" style="text-align: center; margin: auto;"\n'..'|\n'
local i = -5 --nav position
while i <= 5 do
local n = nword + i
if n >= 1 then
local nth = p.addord(n)
if i ~= 0 then --left/right navw
local frame_args = frame:newChild{ args = { n, ord = 'on', case = case } } --easier to do this than modify Module:ConvertNumeric
local w = ord2eng( frame_args )
local catlink = catlinkfollowr( firstpart..' '..w..' '..lastpart, nth )
if minword <= n and n <= maxword then
if catlink[3] and avoidself then --a {{Category redirect}} was followed
ttrackingcats[21] = '[['..testcasecolon..'Category:Navseasoncats wordinal redirected]]'
end
navw = navw..'*'..catlink[1]..'\n'
else
local hidden = '<span style="visibility:hidden">'..nth..'</span>'
navw = navw..'*'..hidden..'\n'
if listall then
tlistall[#tlistall] = tlistall[#tlistall]..' ('..hidden..')'
end
end
else --center navw
navw = navw..'*<b>'..nth..'</b>\n'
end
else
navw = navw..'*<span style="visibility:hidden">'..'0th'..'</span>\n'
end
i = i + 1
end
isolatedcat()
if listall then
return listalllinks()
else
return navw..'|}'
end
end
--[[==========================={{ find_var }}===============================]]
function p.find_var( pn )
--Extracts the variable text (e.g. 2015–16, 3rd, 2000s, III, etc.) from a string
local pagename = currtitle.text
if pn and pn ~= '' then
pagename = pn
end
local cpagename = 'Category:'..pagename --limited-Lua-regex workaround
local d_season = mw.ustring.match(cpagename, ':(%d+s).+%(%d+[–-]%d+%)') --i.e. "1760s in the Province of Quebec (1763–1791)"
local y_season = mw.ustring.match(cpagename, ':(%d+) .+%(%d+[–-]%d+%)') --i.e. "1763 establishments in the Province of Quebec (1763–1791)"
local e_season = mw.ustring.match(cpagename, '%s(%d+[–-])$') or --irreg; ending unknown, e.g. "Members of the Scottish Parliament 2021–"
mw.ustring.match(cpagename, '%s(%d+[–-]present)$') --e.g. "UK MPs 2019–present"
local season = mw.ustring.match(cpagename, '[:%s%(](%d+[–-]%d+)[%)%s]') or --split in 2 b/c you can't frontier '$'/eos?
mw.ustring.match(cpagename, '[:%s](%d+[–-]%d+)$')
local tvseason = mw.ustring.match(cpagename, 'season (%d+)') or
mw.ustring.match(cpagename, 'series (%d+)')
local nordinal = mw.ustring.match(cpagename, '[:%s](%d+[snrt][tdh])[-%s]') or
mw.ustring.match(cpagename, '[:%s](%d+[snrt][tdh])$')
local decade = mw.ustring.match(cpagename, '[:%s](%d+s)[%s-]') or
mw.ustring.match(cpagename, '[:%s](%d+s)$')
local year = mw.ustring.match(cpagename, '[:%s](%d%d%d%d)%s') or --prioritize 4-digit years
mw.ustring.match(cpagename, '[:%s](%d%d%d%d)$') or
mw.ustring.match(cpagename, '[:%s](%d+)%s') or
mw.ustring.match(cpagename, '[:%s](%d+)$') or
mw.ustring.match(cpagename, '[:%s](%d+)-related') --expand exceptions as needed
local roman = mw.ustring.match(cpagename, '%s([IVXLCDM]+)%s')
local found = d_season or y_season or e_season or season or tvseason or
nordinal or decade or year or roman
if found then
if string.match(found, '%d%d%d%d%d') == nil then
--return in order of decreasing complexity/least chance for duplication
if nordinal and season --i.e. "18th-century establishments in the Province of Quebec (1763–1791)"
then return { 'nordinal', nordinal } end
if d_season then return { 'decade', d_season } end
if y_season then return { 'year', y_season } end
if e_season then return { 'ending', e_season } end
if season then return { 'season', season } end
if tvseason then return { 'tvseason', tvseason } end
if nordinal then return { 'nordinal', nordinal } end
if decade then return { 'decade', decade } end
if year then return { 'year', year } end
if roman then return { 'roman', roman } end
end
else --try word ('zeroth' to 'ninety-ninth' only)
local eng2ord = require('Module:ConvertNumeric').english_to_ordinal
local split = mw.text.split(pagename, ' ')
for i=1, #split do
if eng2ord(split[i]) > -1 then
return { 'wordinal', split[i] }
end
end
end
errors = p.errorclass('Function find_var can\'t find the variable text in category "'..pagename..'".')
return { 'error', p.failedcat(errors, 'V') }
end
--[[==========================================================================]]
--[[ Main ]]
--[[==========================================================================]]
function p.navseasoncats( frame )
--arg checks & handling
local args = frame:getParent().args
checkforunknownparams(args) --for template args
checkforunknownparams(frame.args) --for #invoke'd args
local cat = args['cat'] --'testcase' alias for catspace
local list = args['list-all-links'] --debugging utility to output all links & followed #Rs
local follow = args['follow-redirects'] --default 'yes'
local testcase = args['testcase']
local testcasegap = args['testcasegap']
local minimum = args['min']
local maximum = args['max']
local skip_gaps = args['skip-gaps']
--apply args
local pagename = testcase or cat or currtitle.text
local testcaseindent = ''
if testcasecolon == ':' then testcaseindent = '\n::' end
if follow and follow == 'no' then followRs = false end
if list and list == 'yes' then listall = true end
if skip_gaps and skip_gaps == 'yes' then
skipgaps = true
if avoidself then
ttrackingcats[23] = '[['..testcasecolon..'Category:Navseasoncats using skip-gaps parameter]]'
end
end
--ns checks
if currtitle.nsText == 'Category' then
if cat and cat ~= '' then
ttrackingcats[1] = '[['..testcasecolon..'Category:Navseasoncats using cat parameter]]'
end
if testcase and testcase ~= '' then
ttrackingcats[2] = '[['..testcasecolon..'Category:Navseasoncats using testcase parameter]]'
end
elseif currtitle.nsText == '' then
ttrackingcats[26] = '[['..testcasecolon..'Category:Navseasoncats in mainspace]]'
end
--find the variable parts of pagename
local findvar = p.find_var(pagename)
if findvar[1] == 'error' then --basic format error checking in find_var()
return findvar[2]..table.concat(ttrackingcats)
end
local start = string.match(findvar[2], '^%d+')
--the rest is static
local findvar_escaped = string.gsub( findvar[2], '%-', '%%%-')
local firstpart, lastpart = string.match(pagename, '^(.-)'..findvar_escaped..'(.*)$')
if findvar[1] == 'tvseason' then --double check for cases like '30 Rock (season 3) episodes'
firstpart, lastpart = string.match(pagename, '^(.-season )'..findvar_escaped..'(.*)$')
if firstpart == nil then
firstpart, lastpart = string.match(pagename, '^(.-series )'..findvar_escaped..'(.*)$')
end
end
firstpart = mw.text.trim(firstpart or '')
lastpart = mw.text.trim(lastpart or '')
--call the appropriate nav function
if findvar[1] == 'year' then --e.g. "500", "2001"; nav_year..nav_decade; ~75%
local nav1 = nav_year( firstpart, start, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
local dec = math.floor(findvar[2]/10)
local decadecat = nil
local firstpart_dec = firstpart
if firstpart_dec ~= '' then
firstpart_dec = firstpart_dec..' the'
elseif firstpart_dec == 'AD' and dec <= 1 then
firstpart_dec = ''
if dec == 0 then dec = '' end
end
local decade = dec..'0s '
decadecat = mw.text.trim( firstpart_dec..' '..decade..lastpart )
local exists = mw.title.new( decadecat, 'Category' ).exists
if exists then
navborder = false
if avoidself then
ttrackingcats[24] = '[['..testcasecolon..'Category:Navseasoncats year and decade]]'
end
local nav2 = nav_decade( firstpart_dec, decade, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
return nav1nav2( nav1, nav2 )
elseif ttrackingcats[15] ~= '' then --nav_year isolated; check nav_hyphen (e.g. UK MPs 1974, Moldovan MPs 2009, etc.)
hyphen = '–'
finish = start
local nav2 = nav_hyphen( start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..testcaseindent..table.concat(ttrackingcats)
if ttrackingcats[15] ~= '' then return nav1 --still isolated; rv to nav_year
else return nav2 end
else --regular nav_year
return nav1
end
elseif findvar[1] == 'decade' then --e.g. "0s", "2010s"; nav_decade..nav_nordinal; ~12%
local nav1 = nav_decade( firstpart, start, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
local decade = tonumber(string.match(findvar[2], '^(%d+)s'))
local century = math.floor( ((decade-1)/100) + 1 ) --from {{CENTURY}}
if century == 0 then century = 1 end --no 0th century
if string.match(decade, '00$') then
century = century + 1 --'2000' is in the 20th, but the rest of the 2000s is in the 21st
end
local clastpart = ' century '..lastpart
local centurycat = mw.text.trim( firstpart..' '..p.addord(century)..clastpart )
local exists = mw.title.new( centurycat, 'Category' ).exists
if not exists then --check for hyphenated century
clastpart = '-century '..lastpart
centurycat = mw.text.trim( firstpart..' '..p.addord(century)..clastpart )
exists = mw.title.new( centurycat, 'Category' ).exists
end
if exists then
navborder = false
if avoidself then
ttrackingcats[25] = '[['..testcasecolon..'Category:Navseasoncats decade and century]]'
end
local nav2 = nav_nordinal( firstpart, century, clastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
return nav1nav2( nav1, nav2 )
else
return nav1
end
elseif findvar[1] == 'nordinal' then --e.g. "4th"; ~7.5%
return nav_nordinal( firstpart, start, lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
elseif findvar[1] == 'season' then --e.g. "1–4", "1999–2000", "2001–02", "2001–2002", "2005–2010", etc.; ~5.25%
local hyphen, finish = mw.ustring.match(findvar[2], '%d([–-])(%d+)') --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd
return nav_hyphen( start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..testcaseindent..table.concat(ttrackingcats)
elseif findvar[1] == 'tvseason' then --e.g. "1", "15" but preceded with "season" or "series"; <1%
return nav_tvseason( firstpart, start, lastpart, maximum )..testcaseindent..table.concat(ttrackingcats) --"minimum" defaults to 1
elseif findvar[1] == 'wordinal' then --e.g. "first", "ninety-ninth"; <<1%
return nav_wordinal( firstpart, findvar[2], lastpart, minimum, maximum, frame )..testcaseindent..table.concat(ttrackingcats)
elseif findvar[1] == 'roman' then --e.g. "I", "XXVIII"; <<1%
return nav_roman( firstpart, findvar[2], lastpart, minimum, maximum )..testcaseindent..table.concat(ttrackingcats)
elseif findvar[1] == 'ending' then --e.g. "2021–" (irregular; ending unknown); 0%
local hyphen, finish = mw.ustring.match(findvar[2], '%d([–-])present$'), -1 --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd
if hyphen == nil then
hyphen, finish = mw.ustring.match(findvar[2], '%d([–-])$'), 0 --0/-1 are hardcoded switches for nav_hyphen()
end
return nav_hyphen( start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..testcaseindent..table.concat(ttrackingcats)
else --malformed
errors = p.errorclass('Failed to determine the appropriate nav function from malformed season "'..findvar[2]..'". ')
return p.failedcat(errors, 'N')..table.concat(ttrackingcats)
end
end
return p