Module:Signpost/sandbox

local INDEX_MODULE = 'Module:Signpost/index'

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.rowMethods = {

page = 'getPage',

fullpage = 'getFullPage',

date = 'getDate',

title = 'getTitle',

subpage = 'getSubpage',

}

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:getAuthors()

return self.data.authors

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+)})', 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 lang:formatDate('Y-m-d', 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 '' ..

'No articles found for the arguments specified'

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