모듈:Navseasoncats

local p = {}
local errors = 
local nexistingcats = 0
local currtitle = mw.title.getCurrentTitle()
local testcases = (currtitle.subpageText == '시험장')
local navborder = true --whether or not a border is displayed
--======================================================================**
--                      Utility & category functions                        
--======================================================================**
--Error message handling
function errorclass( msg )
	return mw.text.tag( 'span', {class='error mw-ext-cite-error'}, 'Error! '..string.gsub(msg, '&#', '&#') )
end
--Returns a colon if on a testcase subpage
function testcasecolon()
	if testcases then return ':' end
	return 
end
--Consolidate avoidance checks
function avoidself()
	return (currtitle.text ~= 'Navseasoncats' and          --avoid self
			currtitle.text ~= '시간 분류 둘러보기' and      --avoid self
			currtitle.text ~= '시간 분류 둘러보기/설명문서' and      --avoid self
			currtitle.text ~= 'Navseasoncats/sandbox' and  --avoid self
			(currtitle.nsText ~= '틀' or testcases)) --avoid nested transclusion errors (i.e. )
end
--Failure handling
function failedcat( errors, sortkey )
	if avoidself() then
		return (errors or )..'***Navseasoncats failed to generate navbox***'..
			   ..(sortkey or 'O')..
	end
	return 
end
--Check for nav*() navigational isolation (not necessarily an error)
function isolatedcat()
	if nexistingcats == 0 and avoidself() then
		return ..testcasecolon()..'Category:시간 분류 둘러보기 - 외톨이'
	end
	return 
end
--Check for navhyphen default gap size + isolatedcat() (not necessarily an error)
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
		return ..testcasecolon()..'Category:Navseasoncats default season gap size'
	end
	return 
end
--Similar to : 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, disp )
	catname = mw.text.trim(catname or )
	disp    = mw.text.trim(disp or )
	local grey = '#888'
	local displaytext = catname
	if disp ~=  then
		--use 'disp' parameter instead, and strip any trailing disambiguator
		displaytext = mw.ustring.gsub(disp, '%s+%(.+$', );
	end
	
	local catpage = mw.title.new( catname, 'Category' )
	if catpage.exists then
		--TODO?: ignore cat #Rs: see 2019 link on nav @ Category:2018 Asian Games
		--isRedirect did not change expensive function count (b/c catname passed instead of id?),
		--but isRedirect also did not recognize ,
		--and it's not worth calling & parsing getContent, so TODONT
		nexistingcats = nexistingcats + 1
		return ..displaytext..
	else
		return ..displaytext..
	end
end
--12 -> 12th, etc.
--Used in navordinal() only
function 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
--Returns an unsigned string of the 1-4 digit decade ending in "0", else error
--Used in navdecade() only
function sterilizedec( decade )
	if decade == nil 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
		local decade_fixed234 = string.match(decade, '^-%+?(%d%d?%d?)%d^%d') or
								string.match(decade, '^-%+?(%d%d?%d?)%d$')
		local decade_fixed1   = string.match(decade, '^-%+?(%d)^%d') or
								string.match(decade, '^-%+?(%d)$')
		if decade_fixed234 then
			return decade_fixed234..'0'
		elseif decade_fixed1 then
			return '0'
		else
			errors = 'sterilizedec() error'
			return nil
		end
	end
end
--======================================================================**
--                   Formerly separated templates/modules                   
--======================================================================**
--=======================================================**
function navyear( firstpart, year, lastpart, minimumyear, maximumyear )
	--- if true then
	--- 	return '@' .. firstpart .. '@' .. year .. '@' .. lastpart .. '@'
	--- end
	--Expects a PAGENAME of the form "Some sequential 1760 example cat", where 
	--	=Some sequential
	--	=1760
	--	=example cat
	--	=1758 ('min' year parameter; optional)
	--	=1800 ('max' year parameter; optional)
	year = tonumber(year) or tonumber(mw.ustring.match(year or , '^%s*(%d*)'))
	local minyear = tonumber(string.match(minimumyear or , '-?%d+')) or -9999    --allow +/- qualifier
	local maxyear = tonumber(string.match(maximumyear or , '-?%d+')) or  9999    --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)
	local temporal = mw.ustring.match(lastpart, '년') or
					 mw.ustring.match(lastpart, '세기') or
					 mw.ustring.match(lastpart, '천년기') or 
	
	if year == nil then
		errors = errorclass('Function navyear can\'t recognize the year sent to its 2nd parameter.')
		return failedcat(errors, 'Y')
	end
	
	--AD/BC switches & vars
	local parentBC = mw.ustring.match(firstpart, '기원전 $') --following the "1 BC" convention for all years BC
	firstpart  = mw.ustring.gsub(firstpart,  '기원전 $', )
	local BCe = parentBC or '기원전 ' --"BC" default
	
	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
	--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  tonumber() -> tostring() = "-0",
		end                      --and a  "0" string -> tonumber() -> tostring() =  "0"
	elseif errors ~=  then
		errors = errorclass('Function navdecade 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 failedcat(errors, 'F')
	else
		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 = mw.ustring.match(firstpart, '기원전 $') --following the "0s BC" convention for all years BC
	firstpart = mw.ustring.gsub(firstpart, '기원전 $', ) --handle BC separately; AD never used
	--TODO?: handle BCE, but only if it exists in the wild
	
	local dec0to40AD = (ndec >= 0 and ndec  ndec*switchADBC then
			mindec = tostring(ndec*switchADBC) --input error; mindec should be = parent
		end
	--else --offset only if max == maxdefault
	elseif mw.ustring.match(lastpart, '를 배경으로 한') == nil then
		local thisyear = mw.getContentLanguage():formatDate( 'Y' )
		local nthisdecade = tonumber(string.match(thisyear, '^%d%d%d')..'0')
		local diff = nthisdecade - ndec*switchADBC --in 2019: diff=30 for 1980; diff=0 for 2010; diff = -20 for 2030
		if diff = 0 and diff = 2 and string.len(ish) ~= 2 then
		errors = errorclass('The second part of the season passed to function navhyphen should be two digits, not "'..ish..'".')
		return failedcat(errors, 'K')..endashcat
	end
	
	--calculate intRAseason size/term length & finishing year
	local t = 1
	while t  nord*switchADBC then minord = nord*switchADBC end --input error; minord should be = parent
	
	--determine right-offset, to not show unnecessary future millenia
	local roffset = 0
	if temporal and nord  4th millennium
		   maxord == 9999 --only apply if max unspecified
		then
			if not parentBC and nord <= 3 then --1st, 2nd, & 3rd millennium parents
				roffset = nord + 1
			elseif parentBC and nord == 1 then --1st millennium BC only
				roffset = 1
			end
		end
	end
	
	--begin navordinal
	local navo = ..isolatedcat()
end
--====================================================**
function var_season( pn )
	--Extracts e.g. 2015–16, 3rd, 2000s, or 1999 out of a string
	local pagename = currtitle.baseText
	if pn and pn ~=  then
		pagename = pn
	end
	
	local cpagename = 'Category:'..pagename --limited-Lua-regex workaround
	
	local season  = mw.ustring.match(cpagename, ':%s(%d+–-%d+)%s') or --split in 2 b/c %f%s$ doesn't work
					mw.ustring.match(cpagename, ':%s(%d+–-%d+)$')
					
	local ordinal = mw.ustring.match(cpagename, ':%s(%d+snrttdh)-%s') or
					mw.ustring.match(cpagename, ':%s(%d+snrttdh)$')
					
	local decade  = mw.ustring.match(cpagename, ':%s(%d+년대)-%s를') or
					mw.ustring.match(cpagename, ':%s(%d+년대)$')
					
	local year    = mw.ustring.match(cpagename, ':%s(%d+)년세%s') or
					mw.ustring.match(cpagename, ':%s(%d+)$')
					
	local all4 = (season or )..','..(ordinal or )..','..(decade or )..','..(year or )
	
	if (season == nil and ordinal == nil and decade == nil and year == nil) or
		string.match(all4, '%d%d%d%d%d')
	then
		errors = errorclass('Function var_season can\'t recognize a season, ordinal, decade, nor year for category "'..pagename..'".')
		return failedcat(errors, 'S')
	end
	
	--return in order of decreasing complexity
	if season  then return season  end
	if ordinal then return ordinal end
	if decade  then return decade  end
	if year    then return year    end
end
--======================================================================**
--                                   Main                                   
--======================================================================**
function p.navseasoncats( frame )
	local args = frame:getParent().args
	local dby         = args'decade-below-year' --used by 
	local cat         = args'cat' --'testcase' alias
	local testcase    = args'testcase'
	local testcasegap = args'testcasegap'
	local minyear = args'min'
	local maxyear = args'max'
	if dby then
		navborder = false
		dby = string.gsub(dby, ' ', ' ') --unicodify forced whitespace
	end
	
	local paramtracking = 
	if currtitle.nsText == 'Category' then
		if cat      then paramtracking = paramtracking.. end
		if testcase then paramtracking = paramtracking.. end
	end
	
	local pagename = testcase or cat or dby or currtitle.baseText
	
	local varseason = var_season(pagename)
	if errors ~=  then return varseason..paramtracking end --some error checking in var_season()
	
	local firstpart, lastpart = string.match(pagename, '^(.*)'..varseason..'(.*)$')
	-- firstpart = mw.text.trim(firstpart or )
	-- lastpart  = mw.text.trim(lastpart or )
	
	local start = string.match(varseason, '^%d+')
	local hyphen, finish = mw.ustring.match(varseason, '%d(–-)(%d+)') --ascii 150 & 45 (ndash & keyboard hyphen); mw req'd
	
	--determine the appropriate nav function
	if hyphen and finish then                             --e.g. "2015–16"
		local ish = string.match(finish, '%d?%d$')        --in case  "1999–2000", "1–4"
		return navhyphen( start, hyphen, ish, firstpart, lastpart, minyear, maxyear, testcasegap )..paramtracking
		
	elseif string.match(varseason, '%dsnrttdh$') then --e.g. "4th"
		return navordinal( firstpart, start, lastpart, minyear, maxyear )..paramtracking
		
	elseif string.match(varseason, '%d년대$') then           --e.g. "0s", "2010s"
		return navdecade( firstpart, start, lastpart, minyear, maxyear )..paramtracking
		
	elseif string.match(varseason, '^%d+$') then          --e.g. "500" or "2001"
		return navyear( firstpart, start, lastpart, minyear, maxyear )..paramtracking
		
	else                                                  --malformed
		errors = errorclass('Failed to determine the appropriate nav function from malformed season "'..varseason..'". ')
		return failedcat(errors, 'N')..paramtracking
	end
end
return p