addWarning(warning)
end
end  
return table.concat(ret, '
')
end  
function TrackListing:__tostring
-- Root of the output
local root = mw.html.create('div')
:addClass('track-listing')  
local intro = self:makeIntro
if intro then
root:tag('p')
:wikitext(intro)
:done
end  
-- Start of track listing table
local tableRoot = mw.html.create('table')
tableRoot
:addClass('tracklist')  
-- Overall table width
if self.width then
tableRoot
:css('width', self.width)
end  
-- Header row
if self.headline then
tableRoot:tag('caption')
:wikitext(self.headline or cfg.track_listing)
end  
-- Headers
local headerRow = tableRoot:tag('tr')  
---- Track number
headerRow
:tag('th')
:addClass('tracklist-number-header')
:attr('scope', 'col')
:tag('abbr')
:attr('title', cfg.number)
:wikitext(cfg.number_abbr)  
-- Find columns to output
local columns = {'number', 'title'}
if self.optionalColumns.writer then
columns[#columns + 1] = 'writer'
else
if self.optionalColumns.lyrics then
columns[#columns + 1] = 'lyrics'
end
if self.optionalColumns.music then
columns[#columns + 1] = 'music'
end
end
if self.optionalColumns.extra then
columns[#columns + 1] = 'extra'
end
columns[#columns + 1] = 'length'  
-- Find column width
local nColumns = #columns
local nOptionalColumns = nColumns - 3  
local titleColumnWidth = 100
if nColumns >= 5 then
titleColumnWidth = 40
elseif nColumns >= 4 then
titleColumnWidth = 60
end  
local optionalColumnWidth = ((100 - titleColumnWidth) / nOptionalColumns) .. '%'
titleColumnWidth = titleColumnWidth .. '%'  
---- Title column
headerRow:tag('th')
:attr('scope', 'col')
:css('width', self.title_width or titleColumnWidth)
:wikitext(cfg.title)  
---- Optional headers: writer, lyrics, music, and extra
local function addOptionalHeader(field, headerText, width)
if self.optionalColumns[field] then
headerRow:tag('th')
:attr('scope', 'col')
:css('width', width or optionalColumnWidth)
:wikitext(headerText)
end
end
addOptionalHeader('writer', cfg.writer, self.writing_width)
addOptionalHeader('lyrics', cfg.lyrics, self.lyrics_width)
addOptionalHeader('music', cfg.music, self.music_width)
addOptionalHeader(
'extra',
self.extra_column or cfg.extra,
self.extra_width  
---- Track length
headerRow:tag('th')
:addClass('tracklist-length-header')
:attr('scope', 'col')
:wikitext(cfg.length)  
-- Tracks
for i, track in ipairs(self.tracks) do
tableRoot:node(track:exportRow(columns))
end  
-- Total length
if self.total_length then
tableRoot
:tag('tr')
:addClass('tracklist-total-length')
:tag('th')
:attr('colspan', nColumns - 1)
:attr('scope', 'row')
:tag('span')
:wikitext(cfg.total_length)
:done
:done
:tag('td')
:wikitext(self.total_length)
end  
root:node(tableRoot)
-- Warnings and tracking categories
root:wikitext(self:renderWarnings)
root:wikitext(self:renderTrackingCategories)  
return mw.getCurrentFrame:extensionTag{
name = 'templatestyles', args = { src = 'Module:Track listing/styles.css' }
} .. tostring(root)
end  
---
-- Exports
---  
local p = {}  
function p._main(args)
-- Process numerical args so that we can iterate through them.
local data, tracks = {}, {}
for k, v in pairs(args) do
if type(k) == 'string' then
local prefix, num = k:match('^(%D.-)(%d+)$')
if prefix and Track.fields[prefix] and (num == '0' or num:sub(1, 1) ~= '0') then
-- Allow numbers like 0, 1, 2 ..., but not 00, 01, 02...,
-- 000, 001, 002... etc.
num = tonumber(num)
tracks[num] = tracks[num] or {}
tracks[num][prefix] = v
else
data[k] = v
end
end
end
data.tracks = (function (t)
-- Compress sparse array
local ret = {}
for num, trackData in pairs(t) do
trackData.number = num
table.insert(ret, trackData)
end
table.sort(ret, function (t1, t2)
return t1.number < t2.number
end)
return ret
end)(tracks)  
return tostring(TrackListing.new(data))
end  
function p.main
local args = require('Module:Arguments').getArgs(, {
wrappers = 'Template:Track listing'
})
return p._main(args)
end  
return p