Module:vote table

This module generates the vote table in WT:TOV and subpages.

Use the function createTable to create the vote table. See WT:TOV for how the module is used.

The Lua code below should be completely commented.


local export = {}

-- This function returns the full vote page.
local function getFullPage(pageName)
	local pageObject = mw.title.new(pageName)
	return pageObject:getContent()
end

-- This function returns the start date of a vote, in the format of "Dec 9".
-- It uses the full page contents as the only parameter.
local function startDateFullPage(fullPage)

	local result = ""

	local startDateFull = string.match(fullPage, "Vote start.-%(UTC%)")
	local startDateParts = mw.text.split(startDateFull, "Vote start.-: ")
	local dateString = startDateParts[2]
	local languageObject = mw.language.new("en")
	local success, startDate = pcall(languageObject.formatDate, languageObject, "M j", dateString)
	if success then
		result = startDate
	else
		mw.log('Unparseable "date" in startDateFullPage function of Module:vote_table: ' .. (dateString or startDateFull))
		result = startDateFull
	end

	return result

end

-- This function returns the portion of a vote page with the actual votes, without the vote description and the decision.
-- It uses the full page contents as theonly parameter.
local function pageExcerptFullPage(fullPage)

	-- This is the offset of the match first match of "Enter '# {{support}} ~~~~' on next blank line", which appears in the first support section.
	local offset = string.find(fullPage, "Enter '# {{support}} ~~~~' on next blank line", 1, 1) or 0

	-- cutDescription cuts the description at the start of the vote, based on the offset parameter.
	-- cutDecision cuts the decision at the end of the vote, based on the "==== Decision ====" header.
	-- result is the part of the page that has the actual list of votes.
	local cutDescription = string.sub(fullPage, offset, 100000000)
	local cutDecision = mw.text.split(cutDescription, "==== Decision ====", true)
	local result = cutDecision[1]

	return result

end

-- This function returns a variable with a list of all people who voted in the page.
local function countVotesFullPage(fullPage)

	local votesText = pageExcerptFullPage(fullPage)

	local votes = {}

	-- The line is a match, as long as it starts with a "#" but not with "##", "#*" or "#:". So, the line is a vote, not a comment to a vote.
	-- matchLine (boolean) is true if the line is a match.
	-- Example of accepted line: # {{support}} --[[User:Example|Example]] ([[User talk:Example|talk]]) 03:16, 26 November 2015 (UTC)
	for line in mw.text.gsplit(votesText, "\n") do
		local matchLine = false
		if string.sub(line, 1, 1) == "#" then
			matchLine = true
			local tmp = string.sub(line, 1, 2);
			
			if tmp == "##" or tmp == "#*" or tmp == "#:" then
				matchLine = false
			end
		end

		if matchLine then

			local linkedUser=""

			-- Checks all links (in the format: [[link]]) in the current line.
			for link in string.gmatch(line, "%[%[.-%]%]") do

				-- The link is a match when it points to a user page or talk page.
				-- matchUserLink (boolean) is true if the link is a match.
				local matchUserLink = false
				
				if string.sub(link, 1, 7):lower() == "[[user:" then
					matchUserLink = true
				end
				
				if string.sub(link, 1, 12):lower() == "[[user talk:" then
					matchUserLink = true
				end

				-- Consider the line: # {{support}} per [[User:Example2]] --[[User:Example|Example]] ([[User talk:Example|talk]]) 01:00, 13 September 2015 (UTC)
				-- It has two links (a user page and a talk page). Only the last link is ultimately used as the parameter "linkedUser".
				-- Sometimes, a person has only the link to the user page or the talk page, this makes sure their votes are counted too.
				if matchUserLink then
					linkedUser = link
				end
			end

			if linkedUser ~= "" then
				-- linkedUser = [[User:Example#foo|Example]] (with brackets, link format, sometimes section links)
				-- unlinkedUser = Example (just the user name, unlike linkedUser)
				local cutNamespace = mw.text.split(linkedUser, ":", true)
				local cutAfterPipe = mw.text.split(cutNamespace[2], "|", true)
				local cutAfterHash = mw.text.split(cutAfterPipe[1], "#", true)
				local unlinkedUser = cutAfterHash[1]
				
				if votes[unlinkedUser] == nil then
					votes[unlinkedUser] = 1
				else
					votes[unlinkedUser] = votes[unlinkedUser] + 1;
				end

			end
		end
	end

	return votes

end

function export.createTable(frame)
	-- Concerning the page "Wiktionary:Votes/Active":
	-- pageObject is the page object,
	-- fullPage is the full contents of the page.
	local pageObject = mw.title.new("Wiktionary:Votes/Active")
	local fullPage = pageObject:getContent()

	-- startList is the start of the list of votes.
	-- endList is the end of the list of votes.
	-- For startList, we locate "{{votes/layout" in the code and add 14. The addition of 14 is because otherwise the "{{votes/layout" would be present in the final result.
	-- For endList, we locate "}}" in the code and subtract 1. The subtraction of 1 is because otherwise "}" would be present in the final result.
	local startList = string.find(fullPage, "{{votes/layout", 1, 1)
	startList = startList + 14
	local endList = string.find(fullPage, "}}", 1, 1)
	endList = endList - 1

	-- list is just the list of votes, as a single string.
	local list = string.sub(fullPage, startList, endList)
	list = mw.text.trim(list)

	-- parts = the list of votes, as an array separated by the "|" in the original text.
	local parts = mw.text.split(list, "|", true)

	-- firstVote -> the first vote to be shown; use 1 to show the first vote (frame.args[1] = 1st template argument)
	-- lastVote -> the upper limit, how many votes (vote columns) can be shown on the table at most. (frame.args[2] = 2nd template argument)
	local firstVote = tonumber(frame.args[1])
	local lastVote = tonumber(frame.args[2])

	-- voteOrder -> key = # of vote in the order, value = vote name
	-- activeVotes -> key = vote name, value = true by default, unless the vote didn't start or already ended
	-- voteContents -> key = vote name, value = the contents of each vote page
	-- voteStatusText -> key = vote name, value = the status text, if it exists, or "" (empty string)
	-- voteStartDate -> key = vote name, value = the end date, in the format "Dec 9"
	-- voteEndDate -> key = vote name, value = the end date, in the format "Dec 9"
	local voteOrder = {}
	local activeVotes = {}
	local voteContents = {}
	local voteStatusText = {}
	local voteStartDate = {}
	local voteEndDate = {}

	local i = firstVote
	while i <= lastVote and parts[i * 3] do
		local vote = parts[i * 3]
		table.insert(voteOrder, parts[i * 3])
		activeVotes[vote] = true
		voteContents[vote] = getFullPage("Wiktionary:Votes/" .. vote)
		voteStatusText[vote] = parts[i * 3 + 1] or ""
		voteStartDate[vote] = startDateFullPage(voteContents[vote])
		voteEndDate[vote] = parts[i * 3 - 1]
		i = i + 1
	end

	-- Parse all votes
	local voteTable = {}
	for _, vote in pairs(voteOrder) do
		voteTable[vote] = countVotesFullPage(voteContents[vote])
	end

	-- people = the name of each separate people that voted in at least one vote page
	-- numberOfPeople = number of people that voted in at least one vote page
	local people = {}
	local numberOfPeople = 0;

	for _, vote in pairs(voteOrder) do
		-- count = (key = user name; value = number of votes in the given vote page)
		-- only the key (user name) is used below.
		local count = voteTable[vote]

		-- We are building the list "people", with all people that voted in at least one vote page.
		-- alreadyListed = checks if the person is already listed in "people", to avoid repetition.
		-- When adding a new person in "people", the "numberOfPeople" is increased by 1.
		for countKey, countValue in pairs(count) do
			local alreadyListed = false
			for peopleKey, peopleValue in pairs(people) do
				if countKey == peopleValue then
					alreadyListed = true
				end
			end

			if alreadyListed == false then
				table.insert(people, countKey)
				numberOfPeople = numberOfPeople + 1
			end
		end
	end

	-- Sorting "people" alphabetically.
	table.sort(people)

	-- Starting the table. (variable "result")
	local result = "<table class='wikitable'>"

	result = result .. "<tr>"
	result = result .. "<th>#</th>"
	local i = 0;
	for k, v in pairs(voteOrder) do
		result = result .. "<th style=\"white-space: nowrap\">[vote " .. firstVote + i .. "]</th>"
		i = i + 1
	end
	result = result .. "</tr>"

	result = result .. "<tr>"
	result = result .. "<th>Ends</th>"
	for k, v in pairs(voteOrder) do
		result = result .. "<th style=\"white-space: nowrap\">" .. voteEndDate[v] .. "</th>"
	end
	result = result .. "</tr>"

	result = result .. "<tr>"
	result = result .. "<th>Title</th>"
	for k, v in pairs(voteOrder) do
		local voteTitleWithPrefix = v
		local splitVoteTitle = mw.text.split(voteTitleWithPrefix, "/", true)
		local voteTitleWithoutPrefix = splitVoteTitle[2]
		result = result .. "<th>[[Wiktionary:Votes/" .. voteTitleWithPrefix .. "|" .. voteTitleWithoutPrefix .. "]]</th>"
	end
	result = result .. "</tr>"

	for peopleKey, peopleValue in pairs(people) do
		result = result .. "<tr>"
		result = result .. "<th style=\"white-space: nowrap\">[[User:" .. peopleValue .. "|" .. peopleValue .. "]]</th>"

		for _, v in pairs(voteOrder) do

			local pageName = v
			local fullPage = voteContents[v]

			if peopleKey == 1 then
				if mw.ustring.find(fullPage, "{{premature}}") then
					result = result .. "<th style=\"text-align: center; background-color: lightgray; white-space: nowrap\" rowspan=\"" .. numberOfPeople .. "\">'''starts: " .. voteStartDate[v] .. "'''</td>"
					activeVotes[v] = false
				elseif mw.ustring.find(voteStatusText[v], "%a") then
					result = result .. "<th style=\"text-align: center; background-color: lightgray\" rowspan=\"" .. numberOfPeople .. "\">" .. voteStatusText[v] .. "'''</td>"
					activeVotes[v] = false
				end
			end

			if activeVotes[v] == true then

				local count = voteTable[v]
				local personVoted = false;

				for countKey, countValue in pairs(count) do
					if countKey == peopleValue then
						personVoted = true
					end
				end

				if personVoted == true then
					result = result .. "<td style=\"text-align:center\">✔</td>"
				else
					result = result .. "<td></td>"
				end
			end
		end

		result = result .. "</tr>"
	end

	result = result .. "</table>"

	return result
end

return export