local INDEX_MODULE = 'মডিউল:উইকিপত্রিকা/সূচী' local lang = mw.language.getContentLanguage() local libraryUtil = require('libraryUtil') local checkType = libraryUtil.checkType local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg  -- Article class  local Article = {} Article.__index = Article  Article.viewSpans = {7, 15, 30, 60, 90, 120, 180}  Article.rowMethods = { 	page     = 'getPage', 	fullpage = 'getFullPage', 	date     = 'getDate', 	title    = 'getTitle', 	subpage  = 'getSubpage', 	author   = 'getAuthor', 	subhead  = 'getSubhead', }  -- Register viewsNN row methods for _, span in ipairs(Article.viewSpans) do 	Article.rowMethods["views" .. span] = "getViews" .. span end  function Article.new(data) 	local self = setmetatable({}, Article) 	self.data = data 	self.matchedTags = {} 	return self end  function Article:getSortKey() 	return self.data.sortKey end  function Article:getPage() 	return self.data.page end  function Article:getDate() 	return self.data.date end  function Article:getTitle() 	return self.data.title end  function Article:getSubpage() 	return self.data.subpage end  function Article:getAuthor() 	return self.data.authors[1] end  function Article:getAuthors() 	return self.data.authors end  -- Add getViewsNN methods for _, span in ipairs(Article.viewSpans) do 	Article["getViews" .. span] = function(self) 		if self.data.views then 			return self.data.views[string.format("d%03d", span)] 		else 			return nil 		end 	end end  function Article:getSubhead() 	return self.data.subhead end  function Article:getFragment() 	local fragment = self:getMatchedTags()[1] 	if fragment then 		return mw.uri.anchorEncode(fragment) 	end end  function Article:getFullPage() 	local page = self:getPage() 	local fragment = self:getFragment() 	if fragment then 		return page .. '#' .. fragment 	else 		return page 	end end  function Article:addMatchedTag(tag) 	table.insert(self.matchedTags, tag) end  function Article:getMatchedTags() 	table.sort(self.matchedTags) 	return self.matchedTags end  function Article:hasAllTags(t) 	local tags = self.data.tags 	for i, testTag in ipairs(t) do 		local hasTag = false 		for j, tag in ipairs(tags) do 			if tag == testTag then 				hasTag = true 			end 		end 		if not hasTag then 			return false 		end 	end 	return true end  function Article:makeRowArgs() 	local methods = self.rowMethods 	local args = setmetatable({}, { 		__index = function (t, key) 			local method = methods[key] 			if method then 				return self[method](self) 			else 				error(string.format( 					"'%s' is not a valid parameter name", 					key 				), 2) 			end 		end 	}) 	return args end  function Article:renderTemplate(template, frame) 	frame = frame or mw.getCurrentFrame() 	local args = {} 	for key, method in pairs(self.rowMethods) do 		args[key] = self[method](self) 	end 	return frame:expandTemplate{ 		title = template, 		args = args 	} end  function Article:renderFormat(format) 	local args = self:makeRowArgs(articleObj) 	local ret = format:gsub('(%${([%a%d]+)})', function (match, key) 		return args[key] or match 	end) 	return ret end  -- List class  local List = {} List.__index = List  function List.new(options) 	checkType('List.new', 1, options, 'table') 	checkTypeForNamedArg('List.new', 'args', options.args, 'table', true) 	local self = setmetatable({}, List) 	self.index = options.index or mw.loadData(INDEX_MODULE) 	self.frame = options.frame or mw.getCurrentFrame() 	local args = options.args or {}  	-- Set output formats 	if not options.suppressFormatErrors 		and args.rowtemplate 		and args.rowformat 	then 		error("you cannot use both the 'rowtemplate' and the 'rowformat' arguments", 2) 	elseif not options.suppressFormatErrors 		and not args.rowtemplate 		and not args.rowformat 	then 		error("you must use either the 'rowtemplate' or the 'rowformat' argument", 2) 	else 		self.rowtemplate = args.rowtemplate 		self.rowformat = args.rowformat 	end 	if args.rowseparator == 'newline' then 		self.rowseparator = '\n' 	else 		self.rowseparator = args.rowseparator 	end 	self.noarticles = args.noarticles 	 	-- Get article objects, filtered by page, date and tag, and sort them. 	if args.page then 		self.articles = { self:getPageArticle(args.page) } 	elseif args.date then 		self.articles = self:getDateArticles(args.date) 	else 		self.articles = self:getTagArticles(args.tags, args.tagmatch) 		if not self.articles then 			self.articles = self:getAllArticles() 		end 		self:filterArticlesByDate(args.startdate, args.enddate) 		self:filterArticlesByAuthor(args.author) 	end 	self:sortArticles(args.sortdir, args.sortfield) 	if (args.limit and tonumber(args.limit)) or (args.start and tonumber(args.start)) then 		self:limitArticleCount(tonumber(args.start), tonumber(args.limit)) 	end 	return self end  -- Static methods  function List.normalizeDate(date) 	if not date then 		return nil 	end 	return date end  -- Normal methods  function List:parseTagString(s) 	local ret = {}  	-- Remove whitespace and punctuation 	for i, tag in ipairs(mw.text.split(s, ',')) do 		tag = mw.ustring.gsub(tag, '[%s%p]', ) 		if tag ~=  then 			tag = mw.ustring.lower(tag) 			table.insert(ret, tag) 		end 	end  	-- Resolve aliases 	for i, tag in ipairs(ret) do 		ret[i] = self.index.aliases[tag] or tag 	end 	 	-- Remove duplicates 	local function removeDuplicates(t) 		local vals, ret = {}, {} 		for i, val in ipairs(t) do 			vals[val] = true 		end 		for val in pairs(vals) do 			table.insert(ret, val) 		end 		table.sort(ret) 		return ret 	end 	ret = removeDuplicates(ret)  	return ret end  function List:getPageArticle(page) 	local data = self.index.pages[page] 	if data then 		return Article.new(data) 	end end  function List:getDateArticles(date) 	date = self.normalizeDate(date) 	local dates = self.index.dates[date] 	local ret = {} 	if dates then 		for i, data in ipairs(dates) do 			ret[i] = Article.new(data) 		end 	end 	return ret end  function List:getTagArticles(s, tagMatch) 	if not s then 		return nil 	end 	local tagIndex = self.index.tags 	local ret, pages = {}, {} 	local tags = self:parseTagString(s) 	for i, tag in ipairs(tags) do 		local dataArray = tagIndex[tag] 		if dataArray then 			for i, data in ipairs(dataArray) do 				local obj = Article.new(data) 				-- Make sure we only have one object per page. 				if pages[obj:getPage()] then 					obj = pages[obj:getPage()] 				else 					pages[obj:getPage()] = obj 				end 				-- Record which tag we matched. 				obj:addMatchedTag(tag) 			end 		end 	end 	for page, obj in pairs(pages) do 		if not tagMatch 			or tagMatch == 'any' 			or tagMatch == 'all' and obj:hasAllTags(tags) 		then 			table.insert(ret, obj) 		end 	end 	return ret end  function List:getAllArticles() 	local ret = {} 	for i, data in ipairs(self.index.list) do 		ret[i] = Article.new(data) 	end 	return ret end  function List:getArticleCount() 	return #self.articles end  function List:filterArticlesByDate(startDate, endDate) 	startDate = self.normalizeDate(startDate) or '2005-01-01' 	endDate = self.normalizeDate(endDate) or lang:formatDate('Y-m-d') 	local ret = {} 	for i, article in ipairs(self.articles) do 		local date = article:getDate() 		if startDate <= date and date <= endDate then 			table.insert(ret, article) 		end 	end 	self.articles = ret end  function List:filterArticlesByAuthor(targetAuthor) 	if not targetAuthor then 		return 	end 	local ret = {} 	for i, article in ipairs(self.articles) do 		for j, author in ipairs(article:getAuthors()) do 			if author == targetAuthor then 				table.insert(ret, article) 			end 		end 	end 	self.articles = ret end  function List:sortArticles(direction, field) 	local accessor 	if not field or field == 'date' then 		accessor = function (article) return article:getSortKey() end 	elseif field == 'page' then 		accessor = function (article) return article:getPage() end 	elseif field == 'title' then 		accessor = function (article) return article:getTitle() end 	else 		error(string.format("'%s' is not a valid sort field", field), 2) 	end 	local sortFunc 	if not direction or direction == 'ascending' then 		sortFunc = function (a, b) 			return accessor(a) < accessor(b) 		end 	elseif direction == 'descending' then 		sortFunc = function (a, b) 			return accessor(a) > accessor(b) 		end 	else 		error(string.format("'%s' is not a valid sort direction", direction), 2) 	end 	table.sort(self.articles, sortFunc) end  function List:limitArticleCount(start, limit)  	local ret = {} 	for i, article in ipairs(self.articles) do  		if limit and #ret >= limit then 			break 		end 		if not start or i > start then 			table.insert(ret, article) 		end 	end 	self.articles = ret end  function List:renderRow(articleObj) 	if self.rowtemplate then 		return articleObj:renderTemplate(self.rowtemplate, self.frame) 	elseif self.rowformat then 		return articleObj:renderFormat(self.rowformat) 	else 		error('neither rowtemplate nor rowformat were specified') 	end end  function List:__tostring() 	local ret = {} 	for i, obj in ipairs(self.articles) do 		table.insert(ret, self:renderRow(obj)) 	end 	if #ret < 1 then 		return self.noarticles 			or '' .. 			'নির্দেশিত আর্গুমেন্টের জন্য কোনো নিবন্ধ পাওয়া যায়নি' 	else 		return table.concat(ret, self.rowseparator) 	end end  -- Exports  local p = {}  local function makeInvokeFunc(func) 	return function (frame, index) 		local args = require('Module:Arguments').getArgs(frame, { 			parentOnly = true 		}) 		return func(args, index) 	end end  function p._exportClasses() 	return { 		Article = Article, 		List = List 	} end  function p._count(args, index) 	local list = List.new{ 		args = args, 		index = index, 		suppressFormatErrors = true 	} 	return list:getArticleCount() end  p.count = makeInvokeFunc(p._count)  function p._main(args, index) 	return tostring(List.new{args = args, index = index}) end  p.main = makeInvokeFunc(p._main)  return p