Module:Setlist

From Weezerpedia

local cfg = mw.loadData('Module:Setlist/configuration')

--------------------------------------------------------------------------------
-- Song class
--------------------------------------------------------------------------------

local Song = {}
Song.__index = Song
Song.fields = cfg.song_field_names

Song.cellMethods = {
	number = 'makeNumberCell',
	song = 'makeSongCell',
	placeholder = 'makePlaceholderCell',
}

function Song.new(data)
	local self = setmetatable({}, Song)
	for k, v in pairs(data) do
		self[k] = v
	end
	self.number = assert(tonumber(self.number), 'Song number must be numeric')
	return self
end

function Song.makeSimpleCell(wikitext)
	return mw.html.create('td')
		:wikitext(wikitext or cfg.blank_cell)
end

function Song:makeNumberCell(ordered)
	local display = ordered and '•' or string.format(cfg.number_terminated, self.number)
	return mw.html.create('th')
		:attr('id', string.format(cfg.song_id, self.number))
		:attr('scope', 'row')
		:wikitext(display)
end

function Song:makeSongCell()
	local songCell = mw.html.create('td')
	local songText = self.song and string.format(cfg.song_title, self.song) or cfg.untitled
	songCell:wikitext(songText)
	if self.note then
		songCell:wikitext(string.format(cfg.note, self.note))
	end
	return songCell
end

function Song:makePlaceholderCell()
	return mw.html.create('td')
		:addClass('setlist-placeholder')
		:wikitext(cfg.blank_cell)
end

function Song:exportRow(columns, ordered)
	columns = columns or {}
	local row = mw.html.create('tr')
	for _, column in ipairs(columns) do
		local method = Song.cellMethods[column]
		if method and self[method] then
			if column == 'number' then
				row:node(self[method](self, ordered))
			else
				row:node(self[method](self))
			end
		end
	end
	return row
end

--------------------------------------------------------------------------------
-- Setlist class
--------------------------------------------------------------------------------

local Setlist = {}
Setlist.__index = Setlist
Setlist.fields = cfg.setlist_field_names

function Setlist.new(data)
	local self = setmetatable({}, Setlist)

	for field in pairs(Setlist.fields) do
		self[field] = data[field]
	end

	-- Handle the 'ordered' parameter: only accept "no" (case-insensitive)
	if type(data.ordered) == 'string' and data.ordered:lower() == 'no' then
		self.ordered = true
	else
		self.ordered = false
	end

	self.songs = {}
	self.encoreSongs = {}
	self.encoreTwoSongs = {}

	for _, songData in ipairs(data.songs or {}) do
		local songObj = Song.new(songData)
		if songData.intermission then
			table.insert(self.songs, songObj)
		elseif songData.encoretwo then
			table.insert(self.encoreTwoSongs, songObj)
		elseif songData.encore then
			table.insert(self.encoreSongs, songObj)
		else
			table.insert(self.songs, songObj)
		end
	end

	self.customHeaders = data.customHeaders or {}

	return self
end

function Setlist:addEncoreSection(songs, label, tableRoot, columns, songColumnWidth)
	if #songs == 0 then return end
	local header = tableRoot:tag('tr'):addClass('setlist-subheader')
	header:tag('th')
		:addClass('setlist-number-header')
		:attr('scope', 'col')
		:wikitext(cfg.blank_cell)
	header:tag('th')
		:attr('scope', 'col')
		:css('width', songColumnWidth)
		:wikitext("''" .. label .. "''")
	header:tag('th')
		:addClass('setlist-placeholder-header')
		:attr('scope', 'col')
		:wikitext(cfg.placeholder)
	for _, song in ipairs(songs) do
		tableRoot:node(song:exportRow(columns, self.ordered))
	end
end

function Setlist:__tostring()
	local root = mw.html.create('div'):addClass('setlist')
	local tableRoot = mw.html.create('table'):addClass('setlist')

	if self.width then
		tableRoot:css('width', self.width)
	end

	if self.headline then
		tableRoot:tag('caption'):wikitext(self.headline)
	end

	local headerRow = tableRoot:tag('tr')
	headerRow:tag('th')
		:addClass('setlist-number-header')
		:attr('scope', 'col')
		:wikitext(cfg.number_abbr)

	local columns = {'number', 'song', 'placeholder'}
	local songColumnWidth = self.song_width or '95%'

	headerRow:tag('th')
		:attr('scope', 'col')
		:css('width', songColumnWidth)
		:wikitext(cfg.song)

	headerRow:tag('th')
		:addClass('setlist-placeholder-header')
		:attr('scope', 'col')
		:wikitext(cfg.placeholder)

	for _, song in ipairs(self.songs) do
		local sectionTitle = self.customHeaders[song.number]
		if sectionTitle then
			local sectionRow = tableRoot:tag('tr'):addClass('setlist-subheader')
			sectionRow:tag('th')
				:addClass('setlist-number-header')
				:attr('scope', 'col')
				:wikitext(cfg.blank_cell)
			sectionRow:tag('th')
				:attr('scope', 'col')
				:css('width', songColumnWidth)
				:wikitext("''" .. sectionTitle .. "''")
			sectionRow:tag('th')
				:addClass('setlist-placeholder-header')
				:attr('scope', 'col')
				:wikitext(cfg.placeholder)
		end

		if song.intermission then
			local row = tableRoot:tag('tr'):addClass('setlist-subheader'):addClass('setlist-intermission')
			row:tag('th')
				:addClass('setlist-number-header')
				:attr('scope', 'col')
				:wikitext(cfg.blank_cell)
			row:tag('th')
				:attr('scope', 'col')
				:css('width', songColumnWidth)
				:wikitext("''Intermission''" .. (song.intermission_text and (" <small>(" .. song.intermission_text .. ")</small>") or ""))
			row:tag('th')
				:addClass('setlist-placeholder-header')
				:attr('scope', 'col')
				:wikitext(cfg.placeholder)
		else
			tableRoot:node(song:exportRow(columns, self.ordered))
		end
	end

	local encoreLabel = self.encore_header or cfg.encore_header or 'Encore'
	local encoreTwoLabel = self.encore_two_header or (encoreLabel .. ' 2')

	self:addEncoreSection(self.encoreSongs, encoreLabel, tableRoot, columns, songColumnWidth)
	self:addEncoreSection(self.encoreTwoSongs, encoreTwoLabel, tableRoot, columns, songColumnWidth)

	if self.source then
		local sourceRow = tableRoot:tag('tr'):addClass('setlist-source')
		sourceRow:tag('th')
			:attr('colspan', 2)
			:attr('scope', 'row')
			:tag('span')
				:wikitext(cfg.source or 'Source:')
		sourceRow:tag('td'):wikitext(self.source)
	end

	root:node(tableRoot)

	return mw.getCurrentFrame():extensionTag{
		name = 'templatestyles',
		args = { src = cfg.style_src or 'Module:Setlist/styles.css' }
	} .. tostring(root)
end

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

local p = {}

function p._main(args)
	local data, songs, customHeaders = {}, {}, {}
	local maxNum = 0

	for k, v in pairs(args) do
		if type(k) == 'string' then
			local num = tonumber(k:match('(%d+)$'))
			if num and v and v ~= '' then
				if num > maxNum then maxNum = num end
				songs[num] = songs[num] or {}

				if k:match('^song%d+$') then
					songs[num].song = v
				elseif k:match('^note%d+$') then
					songs[num].note = v
				elseif k:match('^encore%d+$') then
					songs[num].song = v
					songs[num].encore = true
				elseif k:match('^encore_note%d+$') then
					songs[num].note = v
					songs[num].encore = true
				elseif k:match('^encoretwo%d+$') then
					songs[num].song = v
					songs[num].encoretwo = true
				elseif k:match('^encoretwo_note%d+$') then
					songs[num].note = v
					songs[num].encoretwo = true
				elseif k:match('^section%d+$') then
					customHeaders[num] = v
				elseif k:match('^intermission%d+$') then
					local key = num - 0.5
					songs[key] = {intermission = true, intermission_text = v, number = key}
				else
					data[k] = v
				end
			else
				data[k] = v
			end
		end
	end

	data.songs = (function(t)
		local ret = {}
		for num, songData in pairs(t) do
			songData.number = num
			table.insert(ret, songData)
		end
		table.sort(ret, function(a, b) return a.number < b.number end)
		return ret
	end)(songs)

	data.customHeaders = customHeaders

	return tostring(Setlist.new(data))
end

function p.main(frame)
	local args = require('Module:Arguments').getArgs(frame, {
		wrappers = 'Template:Setlist 2'
	})
	return p._main(args)
end

return p