پودمان:Navseasoncats
ظاهر
local p = {}
local num_con = require('Module:Numeral converter').convert
--[[==========================================================================]]
--[[ Globals ]]
--[[==========================================================================]]
local errors = ''
local nexistingcats = 0
local currtitle = mw.title.getCurrentTitle()
local testcasecolon = ''
local testcases = (currtitle.subpageText == 'testcases')
if testcases then testcasecolon = ':' end
local navborder = true
local followRs = true
local listall = false
local listofalllinks = {}
local skipgaps = false
local tlistall = {}
local tlistallbwd = {}
local tlistallfwd = {}
local misctrackingcats = {
'', -- [1] placeholder for [[Category:Navseasoncats using cat parameter]]'
'', -- [2] placeholder for [[Category:Navseasoncats using testcase parameter]]'
'', -- [3] placeholder for [[Category:Navseasoncats range not using en dash]]
'', -- [4] placeholder for [[Category:Navseasoncats range abbreviated]]
'', -- [5] placeholder for [[Category:Navseasoncats range redirected (base change)]]
'', -- [6] placeholder for [[Category:Navseasoncats range redirected (MOS)]]
'', -- [7] placeholder for [[Category:Navseasoncats isolated]]
'', -- [8] placeholder for [[Category:Navseasoncats default season gap size]]
'', -- [9] placeholder for [[Category:Navseasoncats decade redirected]]
'', --[10] placeholder for [[Category:Navseasoncats year redirected]]
'', --[11] placeholder for [[Category:Navseasoncats roman numeral redirected]]
'', --[12] placeholder for [[Category:Navseasoncats nordinal redirected]]
'', --[13] placeholder for [[Category:Navseasoncats wordinal redirected]]
'', --[14] placeholder for [[Category:Navseasoncats TV season redirected]]
'', --[15] placeholder for [[Category:Navseasoncats using skip-gaps parameter]]
}
local ttrackingcats = { --when reindexing, Ctrl+H 'trackcat(13,' & 'ttrackingcats[16]'
'', -- [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 (var change)]] --new
'', -- [8] placeholder for [[Category:Navseasoncats range redirected (end)]]
'', -- [9] placeholder for [[Category:Navseasoncats range redirected (MOS)]]
'', --[10] placeholder for [[Category:Navseasoncats range redirected (other)]]
'', --[11] placeholder for [[Category:Navseasoncats range gaps]]
'', --[12] placeholder for [[Category:Navseasoncats range irregular]]
'', --[13] placeholder for [[Category:Navseasoncats range irregular, 0-length]]
'', --[14] placeholder for [[Category:Navseasoncats range ends (present)]]
'', --[15] placeholder for [[Category:Navseasoncats range ends (blank, MOS)]]
'', --[16] placeholder for [[Category:Navseasoncats isolated]]
'', --[17] placeholder for [[Category:Navseasoncats default season gap size]]
'', --[18] placeholder for [[Category:Navseasoncats decade redirected]]
'', --[19] placeholder for [[Category:Navseasoncats year redirected (base change)]]
'', --[20] placeholder for [[Category:Navseasoncats year redirected (var change)]]
'', --[21] placeholder for [[Category:Navseasoncats year redirected (other)]]
'', --[22] placeholder for [[Category:Navseasoncats roman numeral redirected]]
'', --[23] placeholder for [[Category:Navseasoncats nordinal redirected]]
'', --[24] placeholder for [[Category:Navseasoncats wordinal redirected]]
'', --[25] placeholder for [[Category:Navseasoncats TV season redirected]]
'', --[26] placeholder for [[Category:Navseasoncats using skip-gaps parameter]]
'', --[27] placeholder for [[Category:Navseasoncats year and range]]
'', --[28] placeholder for [[Category:Navseasoncats year and decade]]
'', --[29] placeholder for [[Category:Navseasoncats decade and century]]
'', --[30] placeholder for [[Category:Navseasoncats in mainspace]]
'', --[31] placeholder for [[Category:Navseasoncats redirection error]]
}
local avoidself = (currtitle.text ~= 'Navseasoncats' and --avoid self
currtitle.text ~= 'Navseasoncats/توضیحات' and --avoid self
currtitle.text ~= 'Navseasoncats/تمرین' and --avoid self
(currtitle.nsText ~= 'الگو' or testcases)) --avoid nested transclusion errors (i.e. {{Infilmdecade}})
--[[==========================================================================]]
--[[ Utility & category functions ]]
--[[==========================================================================]]
--Determine if a category exists (in a function for easier localization).
local function catexists( title )
return mw.title.new( title, 'رده' ).exists
end
--Error message handling
--Also used by {{Navseasoncats with centuries below decade}}.
function p.errorclass( msg )
return mw.text.tag( 'span', {class='error mw-ext-cite-error'}, '<b>خطا!</b> '..string.gsub(msg, '&#', '&#') )
end
--Failure handling
--Also used by {{Navseasoncats with centuries below decade}}.
function p.failedcat( errors, sortkey )
if avoidself then
return (errors or '')..'***Navseasoncats ناتوان در ایجاد جعبه ناوبری***'..
'[['..testcasecolon..'رده:Navseasoncats failed to generate navbox|'..(sortkey or 'O')..']]'
end
return ''
end
--Tracking cat handling.
-- key: 15 (when reindexing ttrackingcats{}, Ctrl+H 'trackcat(13,' & 'ttrackingcats[16]')
-- cat: 'Navseasoncats isolated'; '' to remove
--Used by main, all nav_*(), & several utility functions.
local function trackcat( key, cat )
if avoidself and key and cat then
if cat ~= '' then
ttrackingcats[key] = '[['..testcasecolon..'Category:'..cat..']]'
else
ttrackingcats[key] = ''
end
end
return
end
--Check for nav_*() navigational isolation (not necessarily an error).
--Used by all nav_*().
function isolatedcat()
if nexistingcats == 0 and avoidself then
misctrackingcats[7] = '[['..testcasecolon..'رده:Navseasoncats isolated]]'
end
end
--
--Now unused, in favor of catlinkfollowr().
--
--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.
function catlink( catname, displaytext )
catname = num_con("fa", mw.text.trim(catname or ''))
displaytext = num_con("fa", mw.text.trim(displaytext or ''))
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 exists = mw.title.new( catname, 'رده' ).exists
if exists then
nexistingcats = nexistingcats + 1
return '[[:رده:'..catname..'|'..disp..']]'
else
return '<span style="color:'..grey..'">'..disp..'</span>'
end
end
--Similar to catlink() but follows {{Category redirect}}s.
--Returns { <#R target navelement>, <basetext of #R target> } if {{Category redirect}} followed;
--returns { <original navelement>, nil } otherwise.
--Used by all nav_*().
function catlinkfollowr( catname, displaytext , displayend )
catname = num_con("fa", mw.text.trim(catname or ''))
displaytext = num_con("fa", mw.text.trim(displaytext or ''))
local grey = '#305'
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 = catexists(catname)
if exists then
nexistingcats = nexistingcats + 1
if followRs then
local R = num_con("fa", rtarget(catname))
if R == catname then --no #R
link = '[[:رده:'..catname..'|'..disp..']]'
nilorR = nil
else --#R followed
link = '[[:رده:'..R..'|'..disp..']]'
nilorR = R
end
else
link = '[[:رده:'..catname..'|'..disp..']]'
nilorR = nil
end
else
link = '<span style="color:'..grey..'">'..disp..'</span>'
nilorR = nil
--return { '[[:رده:'..catname..'|'..disp..']]', nil } --for debugging
end
if listall then
if nilorR then --#R followed
table.insert( listofalllinks, '[[:رده:'..catname..']] → '..'[[:رده:'..nilorR..']] ('..link..')' )
else --no #R
table.insert( listofalllinks, '[[:رده:'..catname..']] ('..link..')' )
end
end
return {
['cat'] = cat,
['catexists'] = exists,
['rtarget'] = nilorR,
['navelement'] = link,
['displaytext'] = disp,
}
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 '', 'رده' ):getContent()
if string.match( catcontent or '', '*رده }}' ) then
local regex = {
--the following 11 pages (7 condensed) redirect to [[Template:Category redirect]] (as of 6/2019):
{ '1', '{{ *[Cc]ategory *[Rr]edirect' }, --most likely match 1st
{ '2', '{{ *رده *بهتر' }, --444+240 transclusions
{ '3', '{{ *تغییر *مسیر *رده' }, --8+3
{ '4', '{{ *تغییرمسیر *رده' }, --6
{ '5', '{{ *تغییرمسیررده' }, --6
{ '6', '{{ *[Cc]atr' }, --4
{ '7', '{{ *[Cc]at *move' }, --0
}
for k, v in pairs (regex) do
local rtarget = mw.ustring.match( catcontent, v[2]..'%s*|%s*([^|}]+)' )
if rtarget then
rtarget = mw.ustring.gsub(rtarget, '^1%s*=%s*', '')
rtarget = string.gsub(rtarget, '^رده:', '')
return rtarget
end
end
end
return cat
end
--Returns a numbered list of all {{Category redirect}}s followed by catlinkfollowr() -> rtarget().
--Used by all nav_*().
function listalllinks()
local nl = '\n# '
local out = ''
if currtitle.nsText == 'رده' then
errors = p.errorclass('پارامتر <b><code>|list-followed-redirects=yes</code></b> '..
'نباید در فضای نام رده ذخیره شود، تنها باید پیشنمایانده شود.')
out = p.failedcat(errors, 'Z')
end
if listofalllinks[1] then
return out..nl..table.concat(listofalllinks, nl)
else
return out..nl..'پیوندی یافت نشد؟!'
end
end
--Returns the difference b/w 2 ints separated by endash|hyphen, nil if error.
--Used by nav_hyphen() only.
local 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.
local function find_terminaltxt( cat )
local terminaltxt = nil
if mw.ustring.match(cat, '%d+[–-]present$') then
terminaltxt = 'present'
trackcat(14, 'Navseasoncats range ends (present)')
elseif mw.ustring.match(cat, '%d+[–-]$') then
terminaltxt = ''
trackcat(15, 'Navseasoncats range ends (blank, MOS)')
end
return terminaltxt
end
--Returns an unsigned string of the 1-4 digit decade ending in "0", else error.
--Used by nav_decade() only.
function sterilizedec( decade )
if decade == nil or decade == '' then
return nil
end
local dec = mw.ustring.match(decade, '^[-%+]?(%d?%d?%d?۰)$') or
mw.ustring.match(decade, '^[-%+]?(%d?%d?%d?۰)%D')
if dec then
return dec
else
--fix 2-4 digit decade
local decade_fixed234 = mw.ustring.match(decade, '^[-%+]?(%d%d?%d?)%d$') or
mw.ustring.match(decade, '^[-%+]?(%d%d?%d?)%d%D')
if decade_fixed234 then
return decade_fixed234..'۰'
end
--fix 1-digit decade
local decade_fixed1 = mw.ustring.match(decade, '^[-%+]?(%d)$') or
mw.ustring.match(decade, '^[-%+]?(%d)%D')
if decade_fixed1 then
return '۰'
end
--unfixable
errors = 'sterilizedec() error'
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
misctrackingcats[8] = '[['..testcasecolon..'رده:Navseasoncats default season gap size]]'
end
end
--12 -> 12th, etc.
--Used by nav_nordinal(), nav_wordinal(), and {{Navseasoncats with centuries below decade}}.
function p.addord( i )
if tonumber(i) then
local s = tostring(i)
local tens = mw.ustring.match(s, '۱%d$')
if tens then return s..'' end
local ones = mw.ustring.match(s, '%d$')
if ones == '۱' then return s..''
elseif ones == '۲' then return s..''
elseif ones == '۳' then return s..'' end
return s..''
end
return i
end
--[[==========================={{ nav_decade }}=============================]]
function nav_decade( firstpart, decade, lastpart)
--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)
-- maxdecade = 2020 ('max' decade parameter; optional; defaults to next decade)
--sterilize dec
-- local dec = sterilizedec(decade)
if errors ~= '' then
errors = p.errorclass('عملگر nav_decade «'..(decade or '')..'» را بهعنوان پارامتر دومش دریافت کرده، '..
'اما انتظار دارد که ورودی یک سال چهار رقمی باشد که با «۰» پایان مییابد".')
return p.failedcat(errors, 'D')
end
local ndec = tonumber(num_con("en", decade))
local ncen = ndec
--begin navdecade
local bnb = '' --border/no border
if navborder == false then --for embedding in ...
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
while i <= 50 do
local d = ndec + i
--determine target cat
local disp = num_con("fa", d)
local catlink = catlinkfollowr(firstpart..' '..num_con("fa", d)..' '..lastpart, disp)
if catlink.rtarget and avoidself then --a {{Category redirect}} was followed
misctrackingcats[9] = '[['..testcasecolon..'رده:Navseasoncats decade redirected]]'
end
--populate left/right navd
local shown = '*'..catlink.navelement..'\n'
local hidden = '*'..disp..'\n'
local exists = mw.title.new(firstpart..' '..num_con("fa", d)..' '..lastpart, 'رده').exists
if exists then
navd = navd..shown
else
navd = navd..hidden
end
if listall and hidden then
listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')'
end
i = i + 10
end
isolatedcat()
if listall then
return listalllinks()
else
return navd..'|}'
end
end
--[[==========================={{ nav_century }}=============================]]
function nav_century( firstpart, century, lastpart)
local ncen = tonumber(num_con("en", century))
--begin navcentury
local bnb = '' --border/no border
if navborder == false then --for embedding in ...
bnb = ' border-style: none; background-color: transparent;'
end
local navc = '{| class="toccolours hlist" style="text-align: center; margin: auto;'..bnb..'"\n'..'|\n'
local i = -5
while i <= 5 do
local d = ncen + i
--determine target cat
local disp = num_con("fa", d)
local catlink = catlinkfollowr(firstpart..' '..num_con("fa", d)..' '..lastpart, disp)
if catlink.rtarget and avoidself then --a {{Category redirect}} was followed
misctrackingcats[9] = '[['..testcasecolon..'رده:Navseasoncats decade redirected]]'
end
--populate left/right navd
local shown = '*'..catlink.navelement..'\n'
local hidden = '*'..disp..'\n'
local exists = mw.title.new(firstpart..' '..num_con("fa", d)..' '..lastpart, 'رده').exists
if exists then
navc = navc..shown
else
navc = navc..hidden
end
if listall and hidden then
listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')'
end
i = i + 1
end
isolatedcat()
if listall then
return listalllinks()
else
return navc..'|}'
end
end
--[[============================{{ nav_year }}==============================]]
function nav_year(firstpart, year, lastpart)
--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)
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
local nyear = tonumber(num_con("en", year))
--begin navyear
local bnb = '' --border/no border
if navborder == false then --for embedding in ...
bnb = ' border-style: none; background-color: transparent;'
end
local navy = '{| class="toccolours hlist" style="text-align: center; margin: auto;'..bnb..'"\n'..'|\n'
local i = -5
while i <= 5 do
local d = nyear + i
--determine target cat
local disp = num_con("fa", d)
local catlink = catlinkfollowr(firstpart..' '..num_con("fa", d)..' '..lastpart, disp)
if catlink.rtarget and avoidself then --a {{Category redirect}} was followed
misctrackingcats[9] = '[['..testcasecolon..'رده:Navseasoncats decade redirected]]'
end
--populate left/right navd
local shown = '*'..catlink.navelement..'\n'
local hidden = '*'..disp..'\n'
local exists = mw.title.new(firstpart..' '..num_con("fa", d)..' '..lastpart, 'رده').exists
if exists then
navy = navy..shown
else
navy = navy..hidden
end
if listall and hidden then
listofalllinks[#listofalllinks] = listofalllinks[#listofalllinks]..' ('..hidden..')'
end
i = i + 1
end
isolatedcat()
if listall then
return listalllinks()
else
return navy..'|}'
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)
start = num_con("en", start)
finish = num_con("en", finish)
--sterilize start
if mw.ustring.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
trackcat(4, '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 trackcat(13, 'Navseasoncats range ends (present)')
else trackcat(14, '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
trackcat(15, '') --reset for another check later
trackcat(12, 'Navseasoncats range irregular, 0-length')
ttlens[0] = 1 --calc ttlens for std cases below
regularparent = 'isolated'
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 mw.ustring.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 mw.ustring.match(firstpart, '%($') then lspace = '' end --DNE for "Madrid city councillors (2007–2011)"-type cats
if mw.ustring.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="..start..hyph.."finish="..finish..'".')
return p.failedcat(errors, 'K')
end
t = t + 1
end
--apply MOS:DATERANGE to parent
local lenstart = mw.ustring.len(start)
local lenfinish = mw.ustring.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
trackcat(5, 'Navseasoncats range abbreviated (MOS)')
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' then --full year required regardless of term length
trackcat(5, '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 = 6 --less expensive per-increment (inc x 4)
local hgap_limit_irreg = 6 --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..mw.ustring.match(nstart-hgap, '%d?%d$') ..tspace..lastpart
local nextseason2 = firstpart..lspace..(nstart+t+hgap)..hyph..mw.ustring.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, to be frugal with expensive functions
if mw.title.new(num_con("fa", prevseason2), 'رده').exists or --use 'or', in case we're at the edge of the cat structure,
mw.title.new(num_con("fa", nextseason2), 'رده').exists or --or we hit a "–00"/"–2000" situation on one side
mw.title.new(num_con("fa", prevseason4), 'رده').exists or
mw.title.new(num_con("fa", nextseason4), 'رده').exists
then
hgap_success = true
break
end
elseif t > 1 then --test full range first, then abbreviated range, to be frugal with expensive functions
if mw.title.new(num_con("fa", prevseason4), 'رده').exists or --use 'or', in case we're at the edge of the cat structure,
mw.title.new(num_con("fa", nextseason4), 'رده').exists or --or we hit a "–00"/"–2000" situation on one side
mw.title.new(num_con("fa", prevseason2), 'رده').exists or
mw.title.new(num_con("fa", nextseason2), 'رده').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 ).rtarget ~= nil then --#R followed
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 or g == 1) and
(mw.title.new( full, 'رده' ).exists == false)
then --allow bare-bones MOS:DATERANGE alternation, in case we're on a 0|1-gap, 1-year term series
local to2 = mw.ustring.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( num_con("fa", full), 'رده' ).exists then
if to == '0-length' then
trackcat(12, '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
g = 0 --soft-reset g, to keep stepping thru k
j = j + 1 --save, but keep searching thru k
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 --while k
if gbreak == true then break end
g = g + 1
lastg = g
end --while g
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 ).rtarget ~= nil then --#R followed
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 (k == 1) and
(g == 0 or g == 1) and
(mw.title.new( full, 'Category' ).exists == false)
then --allow bare-bones MOS:DATERANGE alternation, in case we're on a 0|1-gap, 1-year term series
to2 = mw.ustring.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(frame, full) == full then --only use 0-length cats that don't #R
trackcat(12, '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 --while k
if gbreak == true then break end
g = g + 1
lastg = g
end --while g
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 = mw.ustring.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 = mw.ustring.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 mw.ustring.len(from) == 4 then --e.g. 1999-2004
--determine which link exists (full or abbr)
local full = firstpart..lspace..from..hyph..tofinal..tspace..lastpart
full = num_con("fa", full)
if not mw.title.new( full, 'رده' ).exists then
local abbr = firstpart..lspace..from..hyph..to2..tspace..lastpart
if mw.title.new( abbr, 'رده' ).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, 'رده' ).exists and tofinal ~= to then
local full = firstpart..lspace..from..hyph..to..tspace..lastpart
full = num_con("fa", full)
if mw.title.new( full, 'رده' ).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.rtarget and avoidself then --a {{Category redirect}} was followed, figure out why
--determine new term length & gap size
ttlens[ find_duration( catlink.rtarget ) ] = 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.rtarget, 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.rtarget, 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.rtarget ) --also sets tracking cats
rtarbase = mw.ustring.gsub(catlink.rtarget, terminal_regex, '')
end
origbase = mw.text.trim(origbase)
rtarbase = mw.text.trim(rtarbase)
if origbase ~= rtarbase then
trackcat(6, 'Navseasoncats range redirected (base change)')
elseif terminalcat == 1 then
trackcat(7, '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.rtarget, all4s_regex))
if all4s then
trackcat(9, 'Navseasoncats range redirected (other)')
else
trackcat(8, '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.navelement..'\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>'..num_con("fa", 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 trackcat(10, 'Navseasoncats range gaps') end
if itlens > 1 and ttrackingcats[12] == '' then --avoid duplication in "Navseasoncats range irregular, 0-length"
trackcat(11, 'Navseasoncats range irregular')
end
end
isolatedcat()
defaultgapcat(not hgap_success)
if listall then
return listalllinks()
else
return navh..'|}'
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 = 'رده:'..pagename--limited-Lua-regex workaround
local e_season = mw.ustring.match(cpagename, '%s(%d+[–-])$') --irreg; ending unknown, e.g. "Members of the Scottish Parliament 2021–"
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 decade = mw.ustring.match(cpagename, '[:%s]دهه%s(%d+)')
local century = mw.ustring.match(cpagename, '[:%s]سده%s(%d+)')
local year = mw.ustring.match(cpagename, '[:%s](%d%d%d%d)%s') or --prioritize 4-digit years first
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+).-$')
local found = e_season or season or decade or century or year
if found then
if e_season then return { 'ending', e_season } end
if season then return { 'season', season } end
if decade then return { 'decade', decade } end
if century then return { 'century', century } end
if year then return { 'year', year } end
end
errors = p.errorclass('Function find_var can\'t find the variable x text in the category: "'..cpagename..'"')
return { 'error', p.failedcat(errors, 'V') }
end
--[[==========================================================================]]
--[[ Main ]]
--[[==========================================================================]]
function p.navseasoncats( frame )
local args = frame:getParent().args
local dby = args['decade-below-year'] --used by {{Navseasoncats with decades below year}}
local cbd = args['century-below-decade'] --used by {{Navseasoncats with centuries below decade}}
local cat = args['cat'] --'testcase' alias for mainspace
local list = args['list-all-links'] --utility to output all links & followed #Rs instead of a navbar
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']
if dby then
navborder = false
dby = string.gsub(dby, ' ', ' ') --unicodify forced whitespace
end
if cbd then
navborder = false
cbd = string.gsub(cbd, ' ', ' ') --unicodify forced whitespace
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
misctrackingcats[15] = '[['..testcasecolon..'رده:Navseasoncats using skip-gaps parameter]]'
end
end
if currtitle.nsText == 'Category' then
if cat then misctrackingcats[1] = '[['..testcasecolon..'رده:Navseasoncats using cat parameter]]' end
if testcase then misctrackingcats[2] = '[['..testcasecolon..'رده:Navseasoncats using testcase parameter]]' end
end
local pagename = testcase or cat or dby or cbd or currtitle.text
local findvar = p.find_var(pagename)
if findvar[1] == 'error' then return findvar[2]..table.concat(misctrackingcats) end --basic format error checking in find_var()
local findvar_escaped = mw.ustring.gsub( findvar[2], '-', '%-')
local firstpart, lastpart = mw.ustring.match(pagename, '^(.-)'..findvar_escaped..'(.*)$')
firstpart = mw.text.trim(firstpart or '')
lastpart = mw.text.trim(lastpart or '')
local start = mw.ustring.match(findvar[2], '^%d+')
--determine the appropriate nav function
if findvar[1] == 'season' then --e.g. "1–4", "1999–2000", "2001–02", "2001–2002", "2005–2010", etc.
finish = start --در فارسی برعکس
local hyphen, start = 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 )..table.concat(misctrackingcats)
elseif findvar[1] == 'ending' then --e.g. "2021–" (irregular; ending unknown)
finish = start --در فارسی برعکس
local hyphen, start = mw.ustring.match(findvar[2], '%d([–-])$'), 0 --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd
return nav_hyphen( start, hyphen, finish, firstpart, lastpart, minimum, maximum, testcasegap )..table.concat(misctrackingcats)
elseif findvar[1] == 'decade' then --مثلاً دهه 1870 میلادی
return nav_decade( firstpart, start, lastpart)..table.concat(misctrackingcats)
elseif findvar[1] == 'century' then --مثلاً سده 18 میلادی
return nav_century( firstpart, start, lastpart)..table.concat(misctrackingcats)
elseif findvar[1] == 'year' then --مثلاً سال 2005 میلادی
return nav_year( firstpart, start, lastpart)..table.concat(misctrackingcats)
else --بدشکل
errors = p.errorclass('Failed to determine the appropriate nav function from malformed season "'..findvar[2]..'". ')
return p.failedcat(errors, 'N')..table.concat(misctrackingcats)
end
end
return p