Module:Track listing
Appearance
![]() | This Lua module is used on approximately 116,000 pages. To avoid major disruption and server load, any changes should be tested in the module's /sandbox or /testcases subpages, or in your own module sandbox. The tested changes can be added to this page in a single edit. Consider discussing changes on the talk page before implementing them. |
![]() | This module uses TemplateStyles: |
![]() | This module depends on the following other modules: |
![]() | This module is used by one or more bots in their standard operation. Any breaking changes to this module, including moving it or nominating it for deletion, must be communicated in advance to the bot operator(s). The relevant bots are: User:cewbot/log/20201008/configuration. |
This module implements {{track listing}}. Please see the template page for documentation.
-- This module implements [[Template:Track listing]]
local yesno = require('Module:Yesno')
--------------------------------------------------------------------------------
-- Track class
--------------------------------------------------------------------------------
local Track = {}
Track.__index = Track
Track.fields = {
number = true,
title = true,
note = true,
length = true,
lyrics = true,
music = true,
writer = true,
extra = true,
}
Track.cellMethods = {
number = 'makeNumberCell',
title = 'makeTitleCell',
writer = 'makeWriterCell',
lyrics = 'makeLyricsCell',
music = 'makeMusicCell',
extra = 'makeExtraCell',
length = 'makeLengthCell',
}
function Track.new(data)
local self = setmetatable({}, Track)
for field in pairs(Track.fields) do
self[field] = data[field]
end
self.number = assert(tonumber(self.number))
return self
end
function Track:getLyricsCredit()
return self.lyrics
end
function Track:getMusicCredit()
return self.music
end
function Track:getWriterCredit()
return self.writer
end
function Track:getExtraField()
return self.extra
end
-- Note: called with single dot syntax
function Track.makeSimpleCell(wikitext)
return mw.html.create('td')
:css('vertical-align', 'top')
:wikitext(wikitext or ' ')
end
function Track:makeNumberCell()
return mw.html.create('td')
:css('padding-right', '10px')
:css('text-align', 'right')
:css('vertical-align', 'top')
:wikitext(self.number .. '.')
end
function Track:makeTitleCell()
local titleCell = mw.html.create('td')
titleCell
:css('vertical-align', 'top')
:wikitext(self.title and string.format('"%s"', self.title) or 'Untitled')
:wikitext(' ')
if self.note then
titleCell:tag('span')
:css('font-size', '85%')
:wikitext(string.format('(%s)', self.note))
else
titleCell:wikitext(' ')
end
return titleCell
end
function Track:makeWriterCell()
return Track.makeSimpleCell(self.writer)
end
function Track:makeLyricsCell()
return Track.makeSimpleCell(self.lyrics)
end
function Track:makeMusicCell()
return Track.makeSimpleCell(self.music)
end
function Track:makeExtraCell()
return Track.makeSimpleCell(self.extra)
end
function Track:makeLengthCell()
return mw.html.create('td')
:css('padding-right', '10px')
:css('text-align', 'right')
:css('vertical-align', 'top')
:wikitext(self.length or ' ')
end
function Track:exportRow(options)
options = options or {}
local columns = options.columns or {}
local row = mw.html.create('tr')
row:css('background-color', options.color or '#fff')
for i, column in ipairs(columns) do
local method = Track.cellMethods[column]
if method then
row:node(self[method](self))
end
end
return row
end
--------------------------------------------------------------------------------
-- TrackListing class
--------------------------------------------------------------------------------
local TrackListing = {}
TrackListing.__index = TrackListing
TrackListing.fields = {
all_writing = true,
all_lyrics = true,
all_music = true,
collapsed = true,
headline = true,
extra_column = true,
total_length = true,
}
TrackListing.deprecatedFields = {
writing_credits = true,
lyrics_credits = true,
music_credits = true,
}
function TrackListing.new(data)
local self = setmetatable({}, TrackListing)
-- Add properties
for field in pairs(TrackListing.fields) do
self[field] = data[field]
end
-- Evaluate boolean properties
self.collapsed = yesno(self.collapsed, false)
-- Make track objects
self.tracks = {}
for i, trackData in ipairs(data.tracks or {}) do
table.insert(self.tracks, Track.new(trackData))
end
-- Find which of the optional columns we have.
-- We could just check every column for every track object, but that would
-- be no fun^H^H^H^H^H^H inefficient, so we use four different strategies
-- to try and check only as many columns and track objects as necessary.
do
local optionalColumns = {}
local columnMethods = {
lyrics = 'getLyricsCredit',
music = 'getMusicCredit',
writer = 'getWriterCredit',
extra = 'getExtraField',
}
local doneWriterCheck = false
for i, trackObj in ipairs(self.tracks) do
for column, method in pairs(columnMethods) do
if trackObj[method](trackObj) then
optionalColumns[column] = true
columnMethods[column] = nil
end
end
if not doneWriterCheck and optionalColumns.writer then
doneWriterCheck = true
optionalColumns.lyrics = nil
optionalColumns.music = nil
columnMethods.lyrics = nil
columnMethods.music = nil
end
if not next(columnMethods) then
break
end
end
self.optionalColumns = optionalColumns
end
return self
end
function TrackListing:makeIntro()
if self.all_writing then
return string.format(
'All tracks written by %s. ',
self.all_writing
)
elseif self.all_lyrics and self.all_music then
return string.format(
'All lyrics written by %s; all music composed by %s. ',
self.all_lyrics,
self.all_music
)
elseif self.all_lyrics then
return string.format(
'All lyrics written by %s. ',
self.all_lyrics
)
elseif self.all_music then
return string.format(
'All music composed by %s. ',
self.all_music
)
else
return ''
end
end
function TrackListing:__tostring()
-- 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 colspan and column width
local nColumns = #columns
local nOptionalColumns = nColumns - 3
local titleColumnWidth
if nColumns >= 5 then
titleColumnWidth = 40
elseif nColumns >= 4 then
titleColumnWidth = 60
else
titleColumnWidth = 100
end
local optionalColumnWidth = (100 - titleColumnWidth) / nOptionalColumns
titleColumnWidth = titleColumnWidth .. '%'
optionalColumnWidth = optionalColumnWidth .. '%'
-- Root of the output
local root = mw.html.create()
-- Intro
root:node(self:makeIntro())
-- Start of track listing table
local tableRoot = root:tag('table')
tableRoot
:addClass('tracklist')
:addClass(self.collapsed and 'collapsible collapsed' or nil)
:css('display', 'block')
:css('border-spacing', '0px')
:css('border-collapse', 'collapse')
:css('border', self.collapsed and '#aaa 1px solid' or nil)
:css('padding', self.collapsed and '3px' or '4px')
-- Headline
if self.headline then
tableRoot:tag('tr'):tag('th')
:addClass('tlheader mbox-text')
:attr('colspan', nColumns)
:css('text-align', 'left')
:css('background-color', '#fff')
:wikitext(self.headline)
end
-- Header row for collapsed track listings
if self.collapsed then
tableRoot:tag('tr'):tag('th')
:addClass('tlheader mbox-text')
:attr('colspan', nColumns)
:css('text-align', 'left')
:css('background-color', '#fff')
:wikitext('Track listing')
end
-- Headers
local headerRow = tableRoot:tag('tr')
---- Track number
headerRow
:tag('th')
:addClass('tlheader')
:attr('scope', 'col')
:css('width', '2em')
:css('padding-left', '10px')
:css('padding-right', '10px')
:css('text-align', 'right')
:css('background-color', '#eee')
:wikitext('No.')
---- Title
headerRow:tag('th')
:addClass('tlheader')
:attr('scope', 'col')
:css('width', titleColumnWidth)
:css('text-align', 'left')
:css('background-color', '#eee')
:wikitext('Title')
---- Optional headers: writer, lyrics, music, and extra
local function addOptionalHeader(field, headerText)
if self.optionalColumns[field] then
headerRow:tag('th')
:addClass('tlheader')
:attr('scope', 'col')
:css('width', optionalColumnWidth)
:css('text-align', 'left')
:css('background-color', '#eee')
:wikitext(headerText)
end
end
addOptionalHeader('writer', 'Writer(s)')
addOptionalHeader('lyrics', 'Lyrics')
addOptionalHeader('music', 'Music')
addOptionalHeader('extra', self.extra_column or '{{{extra_column}}}')
---- Track length
headerRow:tag('th')
:addClass('tlheader')
:attr('scope', 'col')
:css('width', '4em')
:css('padding-right', '10px')
:css('text-align', 'right')
:css('background-color', '#eee')
:wikitext('Length')
-- Tracks
for i, track in ipairs(self.tracks) do
tableRoot:node(track:exportRow({
columns = columns,
color = i % 2 == 0 and '#f7f7f7' or '#fff'
}))
end
-- Total length
if self.total_length then
tableRoot
:tag('tr')
:tag('td')
:attr('colspan', nColumns - 1)
:css('padding', 0)
:tag('span')
:css('width', '7.5em')
:css('float', 'right')
:css('padding-left', '10px')
:css('background-color', '#eee')
:css('margin-right', '2px')
:wikitext("'''Total length:'''")
:done()
:done()
:tag('td')
:css('padding', '0 10px 0 0')
:css('text-align', 'right')
:css('background-color', '#eee')
:wikitext(string.format("'''%s'''", self.total_length))
end
return 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(frame)
local args = require('Module:Arguments').getArgs(frame, {
wrappers = 'Template:Track listing'
})
return p._main(args)
end
return p