<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-GB">
	<id>https://stockhub.co/index.php?action=history&amp;feed=atom&amp;title=Module%3ASignpost%2Fsandbox</id>
	<title>Module:Signpost/sandbox - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://stockhub.co/index.php?action=history&amp;feed=atom&amp;title=Module%3ASignpost%2Fsandbox"/>
	<link rel="alternate" type="text/html" href="https://stockhub.co/index.php?title=Module:Signpost/sandbox&amp;action=history"/>
	<updated>2026-05-24T12:46:18Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://stockhub.co/index.php?title=Module:Signpost/sandbox&amp;diff=146789&amp;oldid=prev</id>
		<title>imported&gt;Mr. Stradivarius: use production index module</title>
		<link rel="alternate" type="text/html" href="https://stockhub.co/index.php?title=Module:Signpost/sandbox&amp;diff=146789&amp;oldid=prev"/>
		<updated>2022-11-12T04:53:58Z</updated>

		<summary type="html">&lt;p&gt;use production index module&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local INDEX_MODULE = &amp;#039;Module:Signpost/index&amp;#039;&lt;br /&gt;
local lang = mw.language.getContentLanguage()&lt;br /&gt;
local libraryUtil = require(&amp;#039;libraryUtil&amp;#039;)&lt;br /&gt;
local checkType = libraryUtil.checkType&lt;br /&gt;
local checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Article class&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local Article = {}&lt;br /&gt;
Article.__index = Article&lt;br /&gt;
&lt;br /&gt;
Article.rowMethods = {&lt;br /&gt;
	page = &amp;#039;getPage&amp;#039;,&lt;br /&gt;
	fullpage = &amp;#039;getFullPage&amp;#039;,&lt;br /&gt;
	date = &amp;#039;getDate&amp;#039;,&lt;br /&gt;
	title = &amp;#039;getTitle&amp;#039;,&lt;br /&gt;
	subpage = &amp;#039;getSubpage&amp;#039;,&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function Article.new(data)&lt;br /&gt;
	local self = setmetatable({}, Article)&lt;br /&gt;
	self.data = data&lt;br /&gt;
	self.matchedTags = {}&lt;br /&gt;
	return self&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:getSortKey()&lt;br /&gt;
	return self.data.sortKey&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:getPage()&lt;br /&gt;
	return self.data.page&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:getDate()&lt;br /&gt;
	return self.data.date&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:getTitle()&lt;br /&gt;
	return self.data.title&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:getSubpage()&lt;br /&gt;
	return self.data.subpage&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:getAuthors()&lt;br /&gt;
	return self.data.authors&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:getFragment()&lt;br /&gt;
	local fragment = self:getMatchedTags()[1]&lt;br /&gt;
	if fragment then&lt;br /&gt;
		return mw.uri.anchorEncode(fragment)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:getFullPage()&lt;br /&gt;
	local page = self:getPage()&lt;br /&gt;
	local fragment = self:getFragment()&lt;br /&gt;
	if fragment then&lt;br /&gt;
		return page .. &amp;#039;#&amp;#039; .. fragment&lt;br /&gt;
	else&lt;br /&gt;
		return page&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:addMatchedTag(tag)&lt;br /&gt;
	table.insert(self.matchedTags, tag)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:getMatchedTags()&lt;br /&gt;
	table.sort(self.matchedTags)&lt;br /&gt;
	return self.matchedTags&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:hasAllTags(t)&lt;br /&gt;
	local tags = self.data.tags&lt;br /&gt;
	for i, testTag in ipairs(t) do&lt;br /&gt;
		local hasTag = false&lt;br /&gt;
		for j, tag in ipairs(tags) do&lt;br /&gt;
			if tag == testTag then&lt;br /&gt;
				hasTag = true&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		if not hasTag then&lt;br /&gt;
			return false&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return true&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:makeRowArgs()&lt;br /&gt;
	local methods = self.rowMethods&lt;br /&gt;
	local args = setmetatable({}, {&lt;br /&gt;
		__index = function (t, key)&lt;br /&gt;
			local method = methods[key]&lt;br /&gt;
			if method then&lt;br /&gt;
				return self[method](self)&lt;br /&gt;
			else&lt;br /&gt;
				error(string.format(&lt;br /&gt;
					&amp;quot;&amp;#039;%s&amp;#039; is not a valid parameter name&amp;quot;,&lt;br /&gt;
					key&lt;br /&gt;
				), 2)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	})&lt;br /&gt;
	return args&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:renderTemplate(template, frame)&lt;br /&gt;
	frame = frame or mw.getCurrentFrame()&lt;br /&gt;
	local args = {}&lt;br /&gt;
	for key, method in pairs(self.rowMethods) do&lt;br /&gt;
		args[key] = self[method](self)&lt;br /&gt;
	end&lt;br /&gt;
	return frame:expandTemplate{&lt;br /&gt;
		title = template,&lt;br /&gt;
		args = args&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Article:renderFormat(format)&lt;br /&gt;
	local args = self:makeRowArgs(articleObj)&lt;br /&gt;
	local ret = format:gsub(&amp;#039;(%${(%a+)})&amp;#039;, function (match, key)&lt;br /&gt;
		return args[key] or match&lt;br /&gt;
	end)&lt;br /&gt;
	return ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- List class&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local List = {}&lt;br /&gt;
List.__index = List&lt;br /&gt;
&lt;br /&gt;
function List.new(options)&lt;br /&gt;
	checkType(&amp;#039;List.new&amp;#039;, 1, options, &amp;#039;table&amp;#039;)&lt;br /&gt;
	checkTypeForNamedArg(&amp;#039;List.new&amp;#039;, &amp;#039;args&amp;#039;, options.args, &amp;#039;table&amp;#039;, true)&lt;br /&gt;
	local self = setmetatable({}, List)&lt;br /&gt;
	self.index = options.index or mw.loadData(INDEX_MODULE)&lt;br /&gt;
	self.frame = options.frame or mw.getCurrentFrame()&lt;br /&gt;
	local args = options.args or {}&lt;br /&gt;
&lt;br /&gt;
	-- Set output formats&lt;br /&gt;
	if not options.suppressFormatErrors&lt;br /&gt;
		and args.rowtemplate&lt;br /&gt;
		and args.rowformat&lt;br /&gt;
	then&lt;br /&gt;
		error(&amp;quot;you cannot use both the &amp;#039;rowtemplate&amp;#039; and the &amp;#039;rowformat&amp;#039; arguments&amp;quot;, 2)&lt;br /&gt;
	elseif not options.suppressFormatErrors&lt;br /&gt;
		and not args.rowtemplate&lt;br /&gt;
		and not args.rowformat&lt;br /&gt;
	then&lt;br /&gt;
		error(&amp;quot;you must use either the &amp;#039;rowtemplate&amp;#039; or the &amp;#039;rowformat&amp;#039; argument&amp;quot;, 2)&lt;br /&gt;
	else&lt;br /&gt;
		self.rowtemplate = args.rowtemplate&lt;br /&gt;
		self.rowformat = args.rowformat&lt;br /&gt;
	end&lt;br /&gt;
	if args.rowseparator == &amp;#039;newline&amp;#039; then&lt;br /&gt;
		self.rowseparator = &amp;#039;\n&amp;#039;&lt;br /&gt;
	else&lt;br /&gt;
		self.rowseparator = args.rowseparator&lt;br /&gt;
	end&lt;br /&gt;
	self.noarticles = args.noarticles&lt;br /&gt;
	&lt;br /&gt;
	-- Get article objects, filtered by page, date and tag, and sort them.&lt;br /&gt;
	if args.page then&lt;br /&gt;
		self.articles = { self:getPageArticle(args.page) }&lt;br /&gt;
	elseif args.date then&lt;br /&gt;
		self.articles = self:getDateArticles(args.date)&lt;br /&gt;
	else&lt;br /&gt;
		self.articles = self:getTagArticles(args.tags, args.tagmatch)&lt;br /&gt;
		if not self.articles then&lt;br /&gt;
			self.articles = self:getAllArticles()&lt;br /&gt;
		end&lt;br /&gt;
		self:filterArticlesByDate(args.startdate, args.enddate)&lt;br /&gt;
		self:filterArticlesByAuthor(args.author)&lt;br /&gt;
	end&lt;br /&gt;
	self:sortArticles(args.sortdir, args.sortfield)&lt;br /&gt;
	if (args.limit and tonumber(args.limit)) or (args.start and tonumber(args.start)) then&lt;br /&gt;
		self:limitArticleCount(tonumber(args.start), tonumber(args.limit))&lt;br /&gt;
	end&lt;br /&gt;
	return self&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Static methods&lt;br /&gt;
&lt;br /&gt;
function List.normalizeDate(date)&lt;br /&gt;
	if not date then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
	return lang:formatDate(&amp;#039;Y-m-d&amp;#039;, date)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Normal methods&lt;br /&gt;
&lt;br /&gt;
function List:parseTagString(s)&lt;br /&gt;
	local ret = {}&lt;br /&gt;
&lt;br /&gt;
	-- Remove whitespace and punctuation&lt;br /&gt;
	for i, tag in ipairs(mw.text.split(s, &amp;#039;,&amp;#039;)) do&lt;br /&gt;
		tag = mw.ustring.gsub(tag, &amp;#039;[%s%p]&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
		if tag ~= &amp;#039;&amp;#039; then&lt;br /&gt;
			tag = mw.ustring.lower(tag)&lt;br /&gt;
			table.insert(ret, tag)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Resolve aliases&lt;br /&gt;
	for i, tag in ipairs(ret) do&lt;br /&gt;
		ret[i] = self.index.aliases[tag] or tag&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Remove duplicates&lt;br /&gt;
	local function removeDuplicates(t)&lt;br /&gt;
		local vals, ret = {}, {}&lt;br /&gt;
		for i, val in ipairs(t) do&lt;br /&gt;
			vals[val] = true&lt;br /&gt;
		end&lt;br /&gt;
		for val in pairs(vals) do&lt;br /&gt;
			table.insert(ret, val)&lt;br /&gt;
		end&lt;br /&gt;
		table.sort(ret)&lt;br /&gt;
		return ret&lt;br /&gt;
	end&lt;br /&gt;
	ret = removeDuplicates(ret)&lt;br /&gt;
&lt;br /&gt;
	return ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:getPageArticle(page)&lt;br /&gt;
	local data = self.index.pages[page]&lt;br /&gt;
	if data then&lt;br /&gt;
		return Article.new(data)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:getDateArticles(date)&lt;br /&gt;
	date = self.normalizeDate(date)&lt;br /&gt;
	local dates = self.index.dates[date]&lt;br /&gt;
	local ret = {}&lt;br /&gt;
	if dates then&lt;br /&gt;
		for i, data in ipairs(dates) do&lt;br /&gt;
			ret[i] = Article.new(data)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:getTagArticles(s, tagMatch)&lt;br /&gt;
	if not s then&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
	local tagIndex = self.index.tags&lt;br /&gt;
	local ret, pages = {}, {}&lt;br /&gt;
	local tags = self:parseTagString(s)&lt;br /&gt;
	for i, tag in ipairs(tags) do&lt;br /&gt;
		local dataArray = tagIndex[tag]&lt;br /&gt;
		if dataArray then&lt;br /&gt;
			for i, data in ipairs(dataArray) do&lt;br /&gt;
				local obj = Article.new(data)&lt;br /&gt;
				-- Make sure we only have one object per page.&lt;br /&gt;
				if pages[obj:getPage()] then&lt;br /&gt;
					obj = pages[obj:getPage()]&lt;br /&gt;
				else&lt;br /&gt;
					pages[obj:getPage()] = obj&lt;br /&gt;
				end&lt;br /&gt;
				-- Record which tag we matched.&lt;br /&gt;
				obj:addMatchedTag(tag)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	for page, obj in pairs(pages) do&lt;br /&gt;
		if not tagMatch&lt;br /&gt;
			or tagMatch == &amp;#039;any&amp;#039;&lt;br /&gt;
			or tagMatch == &amp;#039;all&amp;#039; and obj:hasAllTags(tags)&lt;br /&gt;
		then&lt;br /&gt;
			table.insert(ret, obj)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:getAllArticles()&lt;br /&gt;
	local ret = {}&lt;br /&gt;
	for i, data in ipairs(self.index.list) do&lt;br /&gt;
		ret[i] = Article.new(data)&lt;br /&gt;
	end&lt;br /&gt;
	return ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:getArticleCount()&lt;br /&gt;
	return #self.articles&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:filterArticlesByDate(startDate, endDate)&lt;br /&gt;
	startDate = self.normalizeDate(startDate) or &amp;#039;2005-01-01&amp;#039;&lt;br /&gt;
	endDate = self.normalizeDate(endDate) or lang:formatDate(&amp;#039;Y-m-d&amp;#039;)&lt;br /&gt;
	local ret = {}&lt;br /&gt;
	for i, article in ipairs(self.articles) do&lt;br /&gt;
		local date = article:getDate()&lt;br /&gt;
		if startDate &amp;lt;= date and date &amp;lt;= endDate then&lt;br /&gt;
			table.insert(ret, article)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	self.articles = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:filterArticlesByAuthor(targetAuthor)&lt;br /&gt;
	if not targetAuthor then&lt;br /&gt;
		return&lt;br /&gt;
	end&lt;br /&gt;
	local ret = {}&lt;br /&gt;
	for i, article in ipairs(self.articles) do&lt;br /&gt;
		for j, author in ipairs(article:getAuthors()) do&lt;br /&gt;
			if author == targetAuthor then&lt;br /&gt;
				table.insert(ret, article)&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	self.articles = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:sortArticles(direction, field)&lt;br /&gt;
	local accessor&lt;br /&gt;
	if not field or field == &amp;#039;date&amp;#039; then&lt;br /&gt;
		accessor = function (article) return article:getSortKey() end&lt;br /&gt;
	elseif field == &amp;#039;page&amp;#039; then&lt;br /&gt;
		accessor = function (article) return article:getPage() end&lt;br /&gt;
	elseif field == &amp;#039;title&amp;#039; then&lt;br /&gt;
		accessor = function (article) return article:getTitle() end&lt;br /&gt;
	else&lt;br /&gt;
		error(string.format(&amp;quot;&amp;#039;%s&amp;#039; is not a valid sort field&amp;quot;, field), 2)&lt;br /&gt;
	end&lt;br /&gt;
	local sortFunc&lt;br /&gt;
	if not direction or direction == &amp;#039;ascending&amp;#039; then&lt;br /&gt;
		sortFunc = function (a, b)&lt;br /&gt;
			return accessor(a) &amp;lt; accessor(b)&lt;br /&gt;
		end&lt;br /&gt;
	elseif direction == &amp;#039;descending&amp;#039; then&lt;br /&gt;
		sortFunc = function (a, b)&lt;br /&gt;
			return accessor(a) &amp;gt; accessor(b)&lt;br /&gt;
		end&lt;br /&gt;
	else&lt;br /&gt;
		error(string.format(&amp;quot;&amp;#039;%s&amp;#039; is not a valid sort direction&amp;quot;, direction), 2)&lt;br /&gt;
	end&lt;br /&gt;
	table.sort(self.articles, sortFunc)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:limitArticleCount(start, limit) &lt;br /&gt;
	local ret = {}&lt;br /&gt;
	for i, article in ipairs(self.articles) do &lt;br /&gt;
		if limit and #ret &amp;gt;= limit then&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
		if not start or i &amp;gt; start then&lt;br /&gt;
			table.insert(ret, article)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	self.articles = ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:renderRow(articleObj)&lt;br /&gt;
	if self.rowtemplate then&lt;br /&gt;
		return articleObj:renderTemplate(self.rowtemplate, self.frame)&lt;br /&gt;
	elseif self.rowformat then&lt;br /&gt;
		return articleObj:renderFormat(self.rowformat)&lt;br /&gt;
	else&lt;br /&gt;
		error(&amp;#039;neither rowtemplate nor rowformat were specified&amp;#039;)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function List:__tostring()&lt;br /&gt;
	local ret = {}&lt;br /&gt;
	for i, obj in ipairs(self.articles) do&lt;br /&gt;
		table.insert(ret, self:renderRow(obj))&lt;br /&gt;
	end&lt;br /&gt;
	if #ret &amp;lt; 1 then&lt;br /&gt;
		return self.noarticles&lt;br /&gt;
			or &amp;#039;&amp;lt;span style=&amp;quot;font-color: red;&amp;quot;&amp;gt;&amp;#039; ..&lt;br /&gt;
			&amp;#039;No articles found for the arguments specified&amp;lt;/span&amp;gt;&amp;#039;&lt;br /&gt;
	else&lt;br /&gt;
		return table.concat(ret, self.rowseparator)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Exports&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
local function makeInvokeFunc(func)&lt;br /&gt;
	return function (frame, index)&lt;br /&gt;
		local args = require(&amp;#039;Module:Arguments&amp;#039;).getArgs(frame, {&lt;br /&gt;
			parentOnly = true&lt;br /&gt;
		})&lt;br /&gt;
		return func(args, index)&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p._exportClasses()&lt;br /&gt;
	return {&lt;br /&gt;
		Article = Article,&lt;br /&gt;
		List = List&lt;br /&gt;
	}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p._count(args, index)&lt;br /&gt;
	local list = List.new{&lt;br /&gt;
		args = args,&lt;br /&gt;
		index = index,&lt;br /&gt;
		suppressFormatErrors = true&lt;br /&gt;
	}&lt;br /&gt;
	return list:getArticleCount()&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
p.count = makeInvokeFunc(p._count)&lt;br /&gt;
&lt;br /&gt;
function p._main(args, index)&lt;br /&gt;
	return tostring(List.new{args = args, index = index})&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
p.main = makeInvokeFunc(p._main)&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>imported&gt;Mr. Stradivarius</name></author>
	</entry>
</feed>