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('text-align', 'left')
: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
-- Count the number of optional columns.
self.nOptionalColumns = 0
for k in pairs(self.optionalColumns) do
self.nOptionalColumns = self.nOptionalColumns + 1
end
return self
end
function TrackListing:makeIntro()
local ret = ''
if self.all_writing then
ret = ret .. string.format(
'All songs written and composed by %s. ',
self.all_writing
)
else
if self.all_lyrics then
ret = ret .. 'All lyrics written by ' .. self.all_lyrics
if self.all_music then
ret = ret .. ','
else
ret = ret .. '.'
end
ret = ret .. ' '
end
if self.all_music then
if self.all_lyrics then
ret = ret .. 'All'
else
ret = ret .. 'all'
end
ret = ret .. ' music composed by ' .. self.all_music .. '.'
end
end
return ret
end
function TrackListing:__tostring()
local root = mw.html.create()
-- 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 number of columns
local nColumns = #columns
-- Intro
root:node(self:makeIntro())
-- Start 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')
local titleColumnWidth, optionalColumnWidth
if nColumns >= 6 then
titleColumnWidth = '40%'
optionalColumnWidth = '20%'
elseif nColumns >= 5 then
titleColumnWidth = '40%'
optionalColumnWidth = '30%'
elseif nColumns >= 4 then
titleColumnWidth = '60%'
optionalColumnWidth = '40%'
else
-- If there are three
titleColumnWidth = '100%'
end
--- 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')
-- Writer
if self.optionalColumns.writer then
headerRow
:tag('th')
:addClass('tlheader')
:attr('scope', 'col')
:css('width', optionalColumnWidth)
:css('text-align', 'left')
:css('background-color', '#eee')
:wikitext('Writer(s)')
end
for i, track in ipairs(self.tracks) do
root:node(track:exportRow({columns = columns}))
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