Module:votes

local export = {} local findTemplates = require("Module:template parser").findTemplates

-- This function returns the full vote page. local function getFullPage (pageName) local pageObject = mw.title.new (pageName) if pageObject.isRedirect then error(pageName .. " is a redirect.") end return pageObject:getContent end

local function errorMsg(msg) return ' ' .. msg .. " " end

-- This function returns the portion of a vote page with the actual votes, without the vote description and the decision. local function pageExcerpt (pageName) -- Get the text between "Enter '# ~' on next blank line", -- which appears in the first support section, and the "Decision" section, -- which appears below the votes. local page = getFullPage (pageName) if not page then error("The page '" .. pageName .. "' does not exist.") end return page:match ("Enter '# ~' on next blank line(.+)====? *Decision *====?") or error("Part of " .. pageName .. " with votes not found.") end

function normalizeNamespace(namespace) return namespace:lower:gsub("_", " ") end

-- Processes all links that may have a namespace, stores each username in a link -- to a "User" or "User talk" page, and returns the last username encountered, -- or else nil. -- Removes fragments (the part after #) and link text (the part after | if the -- link is piped). -- Consider the text: -- # per User:Example1 --Example2 (talk) 8:00 pm, 12 September 2015, Saturday (4 years, 5 months, 3 days ago) (UTC−5) -- It has three "User" or "User talk" links. -- The username "Example3" from the last link, talk, -- is returned. -- Sometimes, a person has only the link to the user page or the talk page, -- this makes sure their votes are counted too. function getLastUsername(text) local username for potentialNamespace, potentialUser in text:gmatch("%[%[([%a_ ]+):([^|%[%]#|]+).-%]%]") do		potentialNamespace = normalizeNamespace(potentialNamespace) if potentialNamespace == "user" or potentialNamespace == "user talk" then username = potentialUser end end return username end

-- This function returns a variable with a list of all people who voted in the page. function countVotes (pageName) local votesText = pageExcerpt (pageName) -- Initializes new fields to 0. local votes = setmetatable({}, {		__index = function(self, key)			self[key] = 0			return 0		end	})

local stances = { support = {}, oppose = {}, abstain = {}, }

-- Iterates over all lines that start with a "#" but not with "##", "#*" or -- "#:", which excludes discussion and crossed-out votes. -- Example of accepted line: # --Example (talk) 9:16 pm, 25 November 2015, Wednesday (4 years, 2 months, 21 days ago) (UTC−6) for numberedLine in votesText:gmatch("%f[^\n%z]#([^#*:][^\n]*)") do		local username = getLastUsername(numberedLine)

if username then votes[username] = votes[username] + 1;

-- Find the first instance of, , or -- and add username to the count for that stance. for templateName, _ in findTemplates(numberedLine) do				if templateName == "strong oppose" or templateName == "weak oppose" then templateName = "oppose" elseif templateName == "strong support" or templateName == "weak support" then templateName = "support" end if stances[templateName] then table.insert(stances[templateName], username) break end end end end

return votes, stances end

-- This function returns the start date of a vote, in the format of "Dec 9". function startDate (pageName) local fullPage = getFullPage(pageName)

local startDateFull = string.match (fullPage, "Vote start.-%(UTC%)") local startDateTable = mw.text.split (startDateFull, "Vote start.-: ") local languageObject = mw.language.new ("en") local ok, startDate = pcall(function return languageObject:formatDate ("M j", startDateTable[2]) end)

return ok and startDate or errorMsg("Unable to format date '" .. (startDateTable[2] or "nil") .. "'") end

-- Returns the status of a vote. Either it is a premature vote, or it is the number of votes. function export.status (frame) local fullPage = getFullPage(frame.args[1])

local votes, stances = countVotes (frame.args[1]) local count = select(2, string.gsub(fullPage, "==== Support", "==== Support")) local isMultipleVotes = count > 1 local allVotes = 0 local uniqueVoters = 0 local uniqueVoterNames = {}

for username, voteQuantity in pairs(votes) do 		allVotes = allVotes + voteQuantity uniqueVoters = uniqueVoters + 1 if isMultipleVotes then table.insert(uniqueVoterNames, username .. " (" .. voteQuantity .. ")")		else table.insert(uniqueVoterNames, username) end end

table.sort (uniqueVoterNames) local status = "" local p = ""

if uniqueVoters == 1 then p = "person" else p = "people" end

if isMultipleVotes then status = allVotes .. " (" .. uniqueVoters .. " " .. p .. ")" else status = "" .. #stances.support .. " " .. #stances.oppose .. " " .. #stances.abstain end

-- Checks for, which marks unstarted votes. If the current page is an unstarted vote, add the start date. if string.find (fullPage, "") then status = "starts: " .. startDate (frame.args[1]) end

-- If there are votes, then formats the end result with and the title text with the list of voters. if allVotes > 0 then status = '' .. status .. " "	end

return status end

return export