<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://www.thegoonshow.co.uk/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3ATrack_listing</id>
	<title>Module:Track listing - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://www.thegoonshow.co.uk/wiki/index.php?action=history&amp;feed=atom&amp;title=Module%3ATrack_listing"/>
	<link rel="alternate" type="text/html" href="https://www.thegoonshow.co.uk/wiki/index.php?title=Module:Track_listing&amp;action=history"/>
	<updated>2026-05-13T21:36:34Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.38.2</generator>
	<entry>
		<id>https://www.thegoonshow.co.uk/wiki/index.php?title=Module:Track_listing&amp;diff=33738&amp;oldid=prev</id>
		<title>Kurt: 1 revision imported</title>
		<link rel="alternate" type="text/html" href="https://www.thegoonshow.co.uk/wiki/index.php?title=Module:Track_listing&amp;diff=33738&amp;oldid=prev"/>
		<updated>2023-01-28T18:15:35Z</updated>

		<summary type="html">&lt;p&gt;1 revision imported&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 13:15, 28 January 2023&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;en&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Kurt</name></author>
	</entry>
	<entry>
		<id>https://www.thegoonshow.co.uk/wiki/index.php?title=Module:Track_listing&amp;diff=33737&amp;oldid=prev</id>
		<title>en&gt;Alex 21: Add optional parameter for table's overall width</title>
		<link rel="alternate" type="text/html" href="https://www.thegoonshow.co.uk/wiki/index.php?title=Module:Track_listing&amp;diff=33737&amp;oldid=prev"/>
		<updated>2023-01-21T01:48:46Z</updated>

		<summary type="html">&lt;p&gt;Add optional parameter for table&amp;#039;s overall width&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;col class=&quot;diff-marker&quot; /&gt;
				&lt;col class=&quot;diff-content&quot; /&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;2&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 20:48, 20 January 2023&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot; id=&quot;mw-diff-left-l375&quot;&gt;Line 375:&lt;/td&gt;
&lt;td colspan=&quot;2&quot; class=&quot;diff-lineno&quot;&gt;Line 375:&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;	tableRoot&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;	tableRoot&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;		:addClass('tracklist')&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;		:addClass('tracklist')&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;	&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;	-- Overall table width&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;	if self.width then&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;		tableRoot&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;			:css('width', self.width)&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-side-deleted&quot;&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot; data-marker=&quot;+&quot;&gt;&lt;/td&gt;&lt;td style=&quot;color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #a3d3ff; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;&lt;ins style=&quot;font-weight: bold; text-decoration: none;&quot;&gt;	end&lt;/ins&gt;&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;	&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;	&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;	-- Header row&lt;/div&gt;&lt;/td&gt;&lt;td class=&quot;diff-marker&quot;&gt;&lt;/td&gt;&lt;td style=&quot;background-color: #f8f9fa; color: #202122; font-size: 88%; border-style: solid; border-width: 1px 1px 1px 4px; border-radius: 0.33em; border-color: #eaecf0; vertical-align: top; white-space: pre-wrap;&quot;&gt;&lt;div&gt;	-- Header row&lt;/div&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</summary>
		<author><name>en&gt;Alex 21</name></author>
	</entry>
	<entry>
		<id>https://www.thegoonshow.co.uk/wiki/index.php?title=Module:Track_listing&amp;diff=25376&amp;oldid=prev</id>
		<title>Kurt: 1 revision imported</title>
		<link rel="alternate" type="text/html" href="https://www.thegoonshow.co.uk/wiki/index.php?title=Module:Track_listing&amp;diff=25376&amp;oldid=prev"/>
		<updated>2022-11-07T00:31:11Z</updated>

		<summary type="html">&lt;p&gt;1 revision imported&lt;/p&gt;
&lt;table style=&quot;background-color: #fff; color: #202122;&quot; data-mw=&quot;interface&quot;&gt;
				&lt;tr class=&quot;diff-title&quot; lang=&quot;en&quot;&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;← Older revision&lt;/td&gt;
				&lt;td colspan=&quot;1&quot; style=&quot;background-color: #fff; color: #202122; text-align: center;&quot;&gt;Revision as of 19:31, 6 November 2022&lt;/td&gt;
				&lt;/tr&gt;&lt;tr&gt;&lt;td colspan=&quot;2&quot; class=&quot;diff-notice&quot; lang=&quot;en&quot;&gt;&lt;div class=&quot;mw-diff-empty&quot;&gt;(No difference)&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</summary>
		<author><name>Kurt</name></author>
	</entry>
	<entry>
		<id>https://www.thegoonshow.co.uk/wiki/index.php?title=Module:Track_listing&amp;diff=25375&amp;oldid=prev</id>
		<title>en&gt;Jonesey95: rm non-working code related to the collapsed parameter; deprecated parameter checking can be implemented in the template easily</title>
		<link rel="alternate" type="text/html" href="https://www.thegoonshow.co.uk/wiki/index.php?title=Module:Track_listing&amp;diff=25375&amp;oldid=prev"/>
		<updated>2022-06-17T17:19:15Z</updated>

		<summary type="html">&lt;p&gt;rm non-working code related to the collapsed parameter; deprecated parameter checking can be implemented in the template easily&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;local yesno = require('Module:Yesno')&lt;br /&gt;
local checkType = require('libraryUtil').checkType&lt;br /&gt;
local cfg = mw.loadData('Module:Track listing/configuration')&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Helper functions&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
-- Add a mixin to a class.&lt;br /&gt;
local function addMixin(class, mixin)&lt;br /&gt;
	for k, v in pairs(mixin) do&lt;br /&gt;
		if k ~= 'init' then&lt;br /&gt;
			class[k] = v&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Validation mixin&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local Validation = {}&lt;br /&gt;
&lt;br /&gt;
function Validation.init(self)&lt;br /&gt;
	self.warnings = {}&lt;br /&gt;
	self.categories = {}&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Validation:addWarning(msg, category)&lt;br /&gt;
	table.insert(self.warnings, msg)&lt;br /&gt;
	table.insert(self.categories, category)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Validation:addCategory(category)&lt;br /&gt;
	table.insert(self.categories, category)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Validation:getWarnings()&lt;br /&gt;
	return self.warnings&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Validation:getCategories()&lt;br /&gt;
	return self.categories&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Validate a track length. If a track length is invalid, a warning is added.&lt;br /&gt;
-- A type error is raised if the length is not of type string or nil.&lt;br /&gt;
function Validation:validateLength(length)&lt;br /&gt;
	checkType('validateLength', 1, length, 'string', true)&lt;br /&gt;
	if length == nil then&lt;br /&gt;
		-- Do nothing if no length specified&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local hours, minutes, seconds&lt;br /&gt;
&lt;br /&gt;
	-- Try to match times like &amp;quot;1:23:45&amp;quot;.&lt;br /&gt;
	hours, minutes, seconds = length:match('^(%d+):(%d%d):(%d%d)$')&lt;br /&gt;
	if hours and hours:sub(1, 1) == '0' then&lt;br /&gt;
		-- Disallow times like &amp;quot;0:12:34&amp;quot;&lt;br /&gt;
		self:addWarning(&lt;br /&gt;
			string.format(cfg.leading_0_in_hours, mw.text.nowiki(length)),&lt;br /&gt;
			cfg.input_error_category&lt;br /&gt;
		)&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	if not seconds then&lt;br /&gt;
		-- The previous attempt didn't match. Try to match times like &amp;quot;1:23&amp;quot;.&lt;br /&gt;
		minutes, seconds = length:match('^(%d?%d):(%d%d)$')&lt;br /&gt;
		if minutes and minutes:find('^0%d$') then&lt;br /&gt;
			-- Special case to disallow lengths like &amp;quot;01:23&amp;quot;. This check has to&lt;br /&gt;
			-- be here so that lengths like &amp;quot;1:01:23&amp;quot; are still allowed.&lt;br /&gt;
			self:addWarning(&lt;br /&gt;
				string.format(cfg.leading_0_in_minutes, mw.text.nowiki(length)),&lt;br /&gt;
				cfg.input_error_category&lt;br /&gt;
			)&lt;br /&gt;
			return nil&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Add a warning and return if we did not find a match.&lt;br /&gt;
	if not seconds then&lt;br /&gt;
		self:addWarning(&lt;br /&gt;
			string.format(cfg.not_a_time, mw.text.nowiki(length)),&lt;br /&gt;
			cfg.input_error_category&lt;br /&gt;
		)&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Check that the minutes are less than 60 if we have an hours field.&lt;br /&gt;
	if hours and tonumber(minutes) &amp;gt;= 60 then&lt;br /&gt;
		self:addWarning(&lt;br /&gt;
			string.format(cfg.more_than_60_minutes, mw.text.nowiki(length)),&lt;br /&gt;
			cfg.input_error_category&lt;br /&gt;
		)&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Check that the seconds are less than 60&lt;br /&gt;
	if tonumber(seconds) &amp;gt;= 60 then&lt;br /&gt;
		self:addWarning(&lt;br /&gt;
			string.format(cfg.more_than_60_seconds, mw.text.nowiki(length)),&lt;br /&gt;
			cfg.input_error_category&lt;br /&gt;
		)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return nil&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Track class&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local Track = {}&lt;br /&gt;
Track.__index = Track&lt;br /&gt;
addMixin(Track, Validation)&lt;br /&gt;
&lt;br /&gt;
Track.fields = cfg.track_field_names&lt;br /&gt;
&lt;br /&gt;
Track.cellMethods = {&lt;br /&gt;
	number = 'makeNumberCell',&lt;br /&gt;
	title = 'makeTitleCell',&lt;br /&gt;
	writer = 'makeWriterCell',&lt;br /&gt;
	lyrics = 'makeLyricsCell',&lt;br /&gt;
	music = 'makeMusicCell',&lt;br /&gt;
	extra = 'makeExtraCell',&lt;br /&gt;
	length = 'makeLengthCell',&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
function Track.new(data)&lt;br /&gt;
	local self = setmetatable({}, Track)&lt;br /&gt;
	Validation.init(self)&lt;br /&gt;
	for field in pairs(Track.fields) do&lt;br /&gt;
		self[field] = data[field]&lt;br /&gt;
	end&lt;br /&gt;
	self.number = assert(tonumber(self.number))&lt;br /&gt;
	self:validateLength(self.length)&lt;br /&gt;
	return self&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:getLyricsCredit()&lt;br /&gt;
	return self.lyrics&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:getMusicCredit()&lt;br /&gt;
	return self.music&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:getWriterCredit()&lt;br /&gt;
	return self.writer&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:getExtraField()&lt;br /&gt;
	return self.extra&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
-- Note: called with single dot syntax&lt;br /&gt;
function Track.makeSimpleCell(wikitext)&lt;br /&gt;
	return mw.html.create('td')&lt;br /&gt;
		:wikitext(wikitext or cfg.blank_cell)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:makeNumberCell()&lt;br /&gt;
	return mw.html.create('th')&lt;br /&gt;
		:attr('id', string.format(cfg.track_id, self.number))&lt;br /&gt;
		:attr('scope', 'row')&lt;br /&gt;
		:wikitext(string.format(cfg.number_terminated, self.number))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:makeTitleCell()&lt;br /&gt;
	local titleCell = mw.html.create('td')&lt;br /&gt;
	titleCell:wikitext(&lt;br /&gt;
		self.title and string.format(cfg.track_title, self.title) or cfg.untitled&lt;br /&gt;
	)&lt;br /&gt;
	if self.note then&lt;br /&gt;
		titleCell:wikitext(string.format(cfg.note, self.note))&lt;br /&gt;
	end&lt;br /&gt;
	return titleCell&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:makeWriterCell()&lt;br /&gt;
	return Track.makeSimpleCell(self.writer)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:makeLyricsCell()&lt;br /&gt;
	return Track.makeSimpleCell(self.lyrics)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:makeMusicCell()&lt;br /&gt;
	return Track.makeSimpleCell(self.music)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:makeExtraCell()&lt;br /&gt;
	return Track.makeSimpleCell(self.extra)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:makeLengthCell()&lt;br /&gt;
	return mw.html.create('td')&lt;br /&gt;
		:addClass('tracklist-length')&lt;br /&gt;
		:wikitext(self.length or cfg.blank_cell)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function Track:exportRow(columns)&lt;br /&gt;
	local columns = columns or {}&lt;br /&gt;
	local row = mw.html.create('tr')&lt;br /&gt;
	for i, column in ipairs(columns) do&lt;br /&gt;
		local method = Track.cellMethods[column]&lt;br /&gt;
		if method then&lt;br /&gt;
			row:node(self[method](self))&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	return row&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- TrackListing class&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local TrackListing = {}&lt;br /&gt;
TrackListing.__index = TrackListing&lt;br /&gt;
addMixin(TrackListing, Validation)&lt;br /&gt;
TrackListing.fields = cfg.track_listing_field_names&lt;br /&gt;
TrackListing.deprecatedFields = cfg.deprecated_track_listing_field_names&lt;br /&gt;
&lt;br /&gt;
function TrackListing.new(data)&lt;br /&gt;
	local self = setmetatable({}, TrackListing)&lt;br /&gt;
	Validation.init(self)&lt;br /&gt;
&lt;br /&gt;
	-- Check for deprecated arguments&lt;br /&gt;
	for deprecatedField in pairs(TrackListing.deprecatedFields) do&lt;br /&gt;
		if data[deprecatedField] then&lt;br /&gt;
			self:addCategory(cfg.deprecated_parameter_category)&lt;br /&gt;
			break&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Validate total length&lt;br /&gt;
	if data.total_length then&lt;br /&gt;
		self:validateLength(data.total_length)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Add properties&lt;br /&gt;
	for field in pairs(TrackListing.fields) do&lt;br /&gt;
		self[field] = data[field]&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Evaluate boolean properties&lt;br /&gt;
	self.showCategories = yesno(self.category) ~= false&lt;br /&gt;
	self.category = nil&lt;br /&gt;
&lt;br /&gt;
	-- Make track objects&lt;br /&gt;
	self.tracks = {}&lt;br /&gt;
	for i, trackData in ipairs(data.tracks or {}) do&lt;br /&gt;
		table.insert(self.tracks, Track.new(trackData))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Find which of the optional columns we have.&lt;br /&gt;
	-- We could just check every column for every track object, but that would&lt;br /&gt;
	-- be no fun^H^H^H^H^H^H inefficient, so we use four different strategies&lt;br /&gt;
	-- to try and check only as many columns and track objects as necessary.&lt;br /&gt;
	do&lt;br /&gt;
		local optionalColumns = {}&lt;br /&gt;
		local columnMethods = {&lt;br /&gt;
			lyrics = 'getLyricsCredit',&lt;br /&gt;
			music = 'getMusicCredit',&lt;br /&gt;
			writer = 'getWriterCredit',&lt;br /&gt;
			extra = 'getExtraField',&lt;br /&gt;
		}&lt;br /&gt;
		local doneWriterCheck = false&lt;br /&gt;
		for i, trackObj in ipairs(self.tracks) do&lt;br /&gt;
			for column, method in pairs(columnMethods) do&lt;br /&gt;
				if trackObj[method](trackObj) then&lt;br /&gt;
					optionalColumns[column] = true&lt;br /&gt;
					columnMethods[column] = nil&lt;br /&gt;
				end&lt;br /&gt;
			end&lt;br /&gt;
			if not doneWriterCheck and optionalColumns.writer then&lt;br /&gt;
				doneWriterCheck = true&lt;br /&gt;
				optionalColumns.lyrics = nil&lt;br /&gt;
				optionalColumns.music = nil&lt;br /&gt;
				columnMethods.lyrics = nil&lt;br /&gt;
				columnMethods.music = nil&lt;br /&gt;
			end&lt;br /&gt;
			if not next(columnMethods) then&lt;br /&gt;
				break&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
		self.optionalColumns = optionalColumns&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return self&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TrackListing:makeIntro()&lt;br /&gt;
	if self.all_writing then&lt;br /&gt;
		return string.format(cfg.tracks_written, self.all_writing)&lt;br /&gt;
	elseif self.all_lyrics and self.all_music then&lt;br /&gt;
		return mw.message.newRawMessage(&lt;br /&gt;
			cfg.lyrics_written_music_composed,&lt;br /&gt;
			self.all_lyrics,&lt;br /&gt;
			self.all_music&lt;br /&gt;
		):plain()&lt;br /&gt;
	elseif self.all_lyrics then&lt;br /&gt;
		return string.format(cfg.lyrics_written, self.all_lyrics)&lt;br /&gt;
	elseif self.all_music then&lt;br /&gt;
		return string.format(cfg.music_composed, self.all_music)&lt;br /&gt;
	else&lt;br /&gt;
		return nil&lt;br /&gt;
	end&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TrackListing:renderTrackingCategories()&lt;br /&gt;
	if not self.showCategories or mw.title.getCurrentTitle().namespace ~= 0 then&lt;br /&gt;
		return ''&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local ret = ''&lt;br /&gt;
&lt;br /&gt;
	local function addCategory(cat)&lt;br /&gt;
		ret = ret .. string.format('[[Category:%s]]', cat)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	for i, category in ipairs(self:getCategories()) do&lt;br /&gt;
		addCategory(category)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	for i, track in ipairs(self.tracks) do&lt;br /&gt;
		for j, category in ipairs(track:getCategories()) do&lt;br /&gt;
			addCategory(category)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return ret&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TrackListing:renderWarnings()&lt;br /&gt;
	if not cfg.show_warnings then&lt;br /&gt;
		return ''&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	local ret = {}&lt;br /&gt;
&lt;br /&gt;
	local function addWarning(msg)&lt;br /&gt;
		table.insert(ret, string.format(cfg.track_listing_error, msg))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	for i, warning in ipairs(self:getWarnings()) do&lt;br /&gt;
		addWarning(warning)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	for i, track in ipairs(self.tracks) do&lt;br /&gt;
		for j, warning in ipairs(track:getWarnings()) do&lt;br /&gt;
			addWarning(warning)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	return table.concat(ret, '&amp;lt;br&amp;gt;')&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function TrackListing:__tostring()&lt;br /&gt;
	-- Root of the output&lt;br /&gt;
	local root = mw.html.create('div')&lt;br /&gt;
		:addClass('track-listing')&lt;br /&gt;
	&lt;br /&gt;
	local intro = self:makeIntro()&lt;br /&gt;
	if intro then&lt;br /&gt;
		root:tag('p')&lt;br /&gt;
			:wikitext(intro)&lt;br /&gt;
			:done()&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	-- Start of track listing table&lt;br /&gt;
	local tableRoot = mw.html.create('table')&lt;br /&gt;
	tableRoot&lt;br /&gt;
		:addClass('tracklist')&lt;br /&gt;
	&lt;br /&gt;
	-- Header row&lt;br /&gt;
	if self.headline then&lt;br /&gt;
		tableRoot:tag('caption')&lt;br /&gt;
			:wikitext(self.headline or cfg.track_listing)&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Headers&lt;br /&gt;
	local headerRow = tableRoot:tag('tr')&lt;br /&gt;
&lt;br /&gt;
	---- Track number&lt;br /&gt;
	headerRow&lt;br /&gt;
		:tag('th')&lt;br /&gt;
			:addClass('tracklist-number-header')&lt;br /&gt;
			:attr('scope', 'col')&lt;br /&gt;
			:tag('abbr')&lt;br /&gt;
				:attr('title', cfg.number)&lt;br /&gt;
				:wikitext(cfg.number_abbr)&lt;br /&gt;
&lt;br /&gt;
	-- Find columns to output&lt;br /&gt;
	local columns = {'number', 'title'}&lt;br /&gt;
	if self.optionalColumns.writer then&lt;br /&gt;
		columns[#columns + 1] = 'writer'&lt;br /&gt;
	else&lt;br /&gt;
		if self.optionalColumns.lyrics then&lt;br /&gt;
			columns[#columns + 1] = 'lyrics'&lt;br /&gt;
		end&lt;br /&gt;
		if self.optionalColumns.music then&lt;br /&gt;
			columns[#columns + 1] = 'music'&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	if self.optionalColumns.extra then&lt;br /&gt;
		columns[#columns + 1] = 'extra'&lt;br /&gt;
	end&lt;br /&gt;
	columns[#columns + 1] = 'length'&lt;br /&gt;
	&lt;br /&gt;
	-- Find column width&lt;br /&gt;
	local nColumns = #columns&lt;br /&gt;
	local nOptionalColumns = nColumns - 3&lt;br /&gt;
	&lt;br /&gt;
	local titleColumnWidth = 100&lt;br /&gt;
	if nColumns &amp;gt;= 5 then&lt;br /&gt;
		titleColumnWidth = 40&lt;br /&gt;
	elseif nColumns &amp;gt;= 4 then&lt;br /&gt;
		titleColumnWidth = 60&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	local optionalColumnWidth = ((100 - titleColumnWidth) / nOptionalColumns) .. '%'&lt;br /&gt;
	titleColumnWidth = titleColumnWidth .. '%'&lt;br /&gt;
	&lt;br /&gt;
	---- Title column&lt;br /&gt;
	headerRow:tag('th')&lt;br /&gt;
		:attr('scope', 'col')&lt;br /&gt;
		:css('width', self.title_width or titleColumnWidth)&lt;br /&gt;
		:wikitext(cfg.title)&lt;br /&gt;
&lt;br /&gt;
	---- Optional headers: writer, lyrics, music, and extra&lt;br /&gt;
	local function addOptionalHeader(field, headerText, width)&lt;br /&gt;
		if self.optionalColumns[field] then&lt;br /&gt;
			headerRow:tag('th')&lt;br /&gt;
				:attr('scope', 'col')&lt;br /&gt;
				:css('width', width or optionalColumnWidth)&lt;br /&gt;
				:wikitext(headerText)&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	addOptionalHeader('writer', cfg.writer, self.writing_width)&lt;br /&gt;
	addOptionalHeader('lyrics', cfg.lyrics, self.lyrics_width)&lt;br /&gt;
	addOptionalHeader('music', cfg.music, self.music_width)&lt;br /&gt;
	addOptionalHeader(&lt;br /&gt;
		'extra',&lt;br /&gt;
		self.extra_column or cfg.extra,&lt;br /&gt;
		self.extra_width&lt;br /&gt;
	)&lt;br /&gt;
&lt;br /&gt;
	---- Track length&lt;br /&gt;
	headerRow:tag('th')&lt;br /&gt;
		:addClass('tracklist-length-header')&lt;br /&gt;
		:attr('scope', 'col')&lt;br /&gt;
		:wikitext(cfg.length)&lt;br /&gt;
&lt;br /&gt;
	-- Tracks&lt;br /&gt;
	for i, track in ipairs(self.tracks) do&lt;br /&gt;
		tableRoot:node(track:exportRow(columns))&lt;br /&gt;
	end&lt;br /&gt;
&lt;br /&gt;
	-- Total length&lt;br /&gt;
	if self.total_length then&lt;br /&gt;
		tableRoot&lt;br /&gt;
			:tag('tr')&lt;br /&gt;
				:addClass('tracklist-total-length')&lt;br /&gt;
				:tag('th')&lt;br /&gt;
					:attr('colspan', nColumns - 1)&lt;br /&gt;
					:attr('scope', 'row')&lt;br /&gt;
					:tag('span')&lt;br /&gt;
						:wikitext(cfg.total_length)&lt;br /&gt;
						:done()&lt;br /&gt;
					:done()&lt;br /&gt;
				:tag('td')&lt;br /&gt;
					:wikitext(self.total_length)&lt;br /&gt;
	end&lt;br /&gt;
	&lt;br /&gt;
	root:node(tableRoot)&lt;br /&gt;
	-- Warnings and tracking categories&lt;br /&gt;
	root:wikitext(self:renderWarnings())&lt;br /&gt;
	root:wikitext(self:renderTrackingCategories())&lt;br /&gt;
	&lt;br /&gt;
	return mw.getCurrentFrame():extensionTag{&lt;br /&gt;
		name = 'templatestyles', args = { src = 'Module:Track listing/styles.css' }&lt;br /&gt;
	} .. tostring(root)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
-- Exports&lt;br /&gt;
--------------------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
local p = {}&lt;br /&gt;
&lt;br /&gt;
function p._main(args)&lt;br /&gt;
	-- Process numerical args so that we can iterate through them.&lt;br /&gt;
	local data, tracks = {}, {}&lt;br /&gt;
	for k, v in pairs(args) do&lt;br /&gt;
		if type(k) == 'string' then&lt;br /&gt;
			local prefix, num = k:match('^(%D.-)(%d+)$')&lt;br /&gt;
			if prefix and Track.fields[prefix] and (num == '0' or num:sub(1, 1) ~= '0') then&lt;br /&gt;
				-- Allow numbers like 0, 1, 2 ..., but not 00, 01, 02...,&lt;br /&gt;
				-- 000, 001, 002... etc.&lt;br /&gt;
				num = tonumber(num)&lt;br /&gt;
				tracks[num] = tracks[num] or {}&lt;br /&gt;
				tracks[num][prefix] = v&lt;br /&gt;
			else&lt;br /&gt;
				data[k] = v&lt;br /&gt;
			end&lt;br /&gt;
		end&lt;br /&gt;
	end&lt;br /&gt;
	data.tracks = (function (t)&lt;br /&gt;
		-- Compress sparse array&lt;br /&gt;
		local ret = {}&lt;br /&gt;
		for num, trackData in pairs(t) do&lt;br /&gt;
			trackData.number = num&lt;br /&gt;
			table.insert(ret, trackData) &lt;br /&gt;
		end&lt;br /&gt;
		table.sort(ret, function (t1, t2)&lt;br /&gt;
			return t1.number &amp;lt; t2.number&lt;br /&gt;
		end)&lt;br /&gt;
		return ret&lt;br /&gt;
	end)(tracks)&lt;br /&gt;
&lt;br /&gt;
	return tostring(TrackListing.new(data))&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
function p.main(frame)&lt;br /&gt;
	local args = require('Module:Arguments').getArgs(frame, {&lt;br /&gt;
		wrappers = 'Template:Track listing'&lt;br /&gt;
	})&lt;br /&gt;
	return p._main(args)&lt;br /&gt;
end&lt;br /&gt;
&lt;br /&gt;
return p&lt;/div&gt;</summary>
		<author><name>en&gt;Jonesey95</name></author>
	</entry>
</feed>