Module:Signpost Explained

local INDEX_MODULE = 'Module:Signpost/index'local lang = mw.language.getContentLanguagelocal libraryUtil = require('libraryUtil')local checkType = libraryUtil.checkTypelocal checkTypeForNamedArg = libraryUtil.checkTypeForNamedArg

---------------------------------------------------------------------------------- Article class--------------------------------------------------------------------------------

local Article = Article.__index = Article

Article.viewSpans =

Article.rowMethods =

-- Register viewsNN row methodsfor _, span in ipairs(Article.viewSpans) do Article.rowMethods["views" .. span] = "getViews" .. spanend

function Article.new(data) local self = setmetatable(Article) self.data = data self.matchedTags = return selfend

function Article:getSortKey return self.data.sortKeyend

function Article:getPage return self.data.pageend

function Article:getDate return self.data.dateend

function Article:getTitle return self.data.titleend

function Article:getSubpage return self.data.subpageend

function Article:getAuthor return self.data.authors[1]end

function Article:getAuthors return self.data.authorsend

-- Add getViewsNN methodsfor _, 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 endend

function Article:getSubhead return self.data.subheadend

function Article:getFragment local fragment = self:getMatchedTags[1] if fragment then return mw.uri.anchorEncode(fragment) endend

function Article:getFullPage local page = self:getPage local fragment = self:getFragment if fragment then return page .. '#' .. fragment else return page endend

function Article:addMatchedTag(tag) table.insert(self.matchedTags, tag)end

function Article:getMatchedTags table.sort(self.matchedTags) return self.matchedTagsend

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 trueend

function Article:makeRowArgs local methods = self.rowMethods local args = setmetatable return argsend

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:expandTemplateend

function Article:renderFormat(format) local args = self:makeRowArgs(articleObj) local ret = format:gsub('(%$)', function (match, key) return args[key] or match end) return retend

---------------------------------------------------------------------------------- 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 = 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 selfend

-- 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 retend

function List:getPageArticle(page) local data = self.index.pages[page] if data then return Article.new(data) endend

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 retend

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 retend

function List:getAllArticles local ret = for i, data in ipairs(self.index.list) do ret[i] = Article.new(data) end return retend

function List:getArticleCount return #self.articlesend

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 = retend

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 = retend

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 = retend

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') endend

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) endend

---------------------------------------------------------------------------------- Exports--------------------------------------------------------------------------------

local p =

local function makeInvokeFunc(func) return function (frame, index) local args = require('Module:Arguments').getArgs(frame,) return func(args, index) endend

function p._exportClasses return end

function p._count(args, index) local list = List.new return list:getArticleCountend

p.count = makeInvokeFunc(p._count)

function p._main(args, index) return tostring(List.new)end

p.main = makeInvokeFunc(p._main)

return p