Module:Track listing: Difference between revisions
Jump to navigation
Jump to search
np25>Est. 2021 |
Randombell (talk | contribs) Created page with "-- 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 = {..." Tag: Reverted |
||
Line 1: | Line 1: | ||
-- This module implements [[Template:Track listing]] | |||
local yesno = require('Module:Yesno') | local yesno = require('Module:Yesno') | ||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
Line 115: | Line 9: | ||
local Track = {} | local Track = {} | ||
Track.__index = Track | Track.__index = Track | ||
Track.fields = | Track.fields = { | ||
number = true, | |||
title = true, | |||
note = true, | |||
length = true, | |||
lyrics = true, | |||
music = true, | |||
writer = true, | |||
extra = true, | |||
} | |||
Track.cellMethods = { | Track.cellMethods = { | ||
Line 131: | Line 33: | ||
function Track.new(data) | function Track.new(data) | ||
local self = setmetatable({}, Track) | local self = setmetatable({}, Track) | ||
for field in pairs(Track.fields) do | for field in pairs(Track.fields) do | ||
self[field] = data[field] | self[field] = data[field] | ||
end | end | ||
self.number = assert(tonumber(self.number)) | self.number = assert(tonumber(self.number)) | ||
return self | return self | ||
end | end | ||
Line 159: | Line 59: | ||
function Track.makeSimpleCell(wikitext) | function Track.makeSimpleCell(wikitext) | ||
return mw.html.create('td') | return mw.html.create('td') | ||
:wikitext(wikitext or | :css('vertical-align', 'top') | ||
:wikitext(wikitext or ' ') | |||
end | end | ||
function Track:makeNumberCell() | function Track:makeNumberCell() | ||
return mw.html.create(' | return mw.html.create('td') | ||
: | :css('padding-right', '10px') | ||
: | :css('text-align', 'right') | ||
:wikitext( | :css('vertical-align', 'top') | ||
:wikitext(self.number .. '.') | |||
end | end | ||
function Track:makeTitleCell() | function Track:makeTitleCell() | ||
local titleCell = mw.html.create('td') | local titleCell = mw.html.create('td') | ||
titleCell: | titleCell | ||
self.title and string.format( | :css('vertical-align', 'top') | ||
:wikitext(self.title and string.format('"%s"', self.title) or 'Untitled') | |||
if self.note then | if self.note then | ||
titleCell:wikitext(string.format( | titleCell | ||
:wikitext(' ') | |||
:tag('span') | |||
:css('font-size', '85%') | |||
:wikitext(string.format('(%s)', self.note)) | |||
end | end | ||
return titleCell | return titleCell | ||
Line 198: | Line 104: | ||
function Track:makeLengthCell() | function Track:makeLengthCell() | ||
return mw.html.create('td') | return mw.html.create('td') | ||
: | :css('padding-right', '10px') | ||
:wikitext(self.length or | :css('text-align', 'right') | ||
:css('vertical-align', 'top') | |||
:wikitext(self.length or ' ') | |||
end | end | ||
function Track:exportRow( | function Track:exportRow(options) | ||
local columns = columns or {} | options = options or {} | ||
local columns = options.columns or {} | |||
local row = mw.html.create('tr') | local row = mw.html.create('tr') | ||
row:css('background-color', options.color or '#fff') | |||
for i, column in ipairs(columns) do | for i, column in ipairs(columns) do | ||
local method = Track.cellMethods[column] | local method = Track.cellMethods[column] | ||
Line 220: | Line 130: | ||
local TrackListing = {} | local TrackListing = {} | ||
TrackListing.__index = TrackListing | TrackListing.__index = TrackListing | ||
TrackListing.fields = | TrackListing.fields = { | ||
TrackListing.deprecatedFields = | all_writing = true, | ||
all_lyrics = true, | |||
all_music = true, | |||
collapsed = true, | |||
headline = true, | |||
extra_column = true, | |||
total_length = true, | |||
title_width = true, | |||
writing_width = true, | |||
lyrics_width = true, | |||
music_width = true, | |||
extra_width = true, | |||
category = true, | |||
} | |||
TrackListing.deprecatedFields = { | |||
writing_credits = true, | |||
lyrics_credits = true, | |||
music_credits = true, | |||
} | |||
function TrackListing.new(data) | function TrackListing.new(data) | ||
local self = setmetatable({}, TrackListing) | local self = setmetatable({}, TrackListing) | ||
-- Add properties | |||
for field in pairs(TrackListing.fields) do | |||
self[field] = data[field] | |||
end | |||
-- Check for deprecated arguments | -- Check for deprecated arguments | ||
for deprecatedField in pairs(TrackListing.deprecatedFields) do | for deprecatedField in pairs(TrackListing.deprecatedFields) do | ||
if data[deprecatedField] then | if data[deprecatedField] then | ||
self | self.hasDeprecatedArgs = true | ||
break | break | ||
end | end | ||
end | end | ||
-- Evaluate boolean properties | -- Evaluate boolean properties | ||
self.collapsed = yesno(self.collapsed, false) | |||
self.showCategories = yesno(self.category) ~= false | self.showCategories = yesno(self.category) ~= false | ||
self.category = nil | self.category = nil | ||
Line 291: | Line 215: | ||
return self | return self | ||
end | end | ||
function TrackListing:makeIntro() | function TrackListing:makeIntro() | ||
if self.all_writing then | if self.all_writing then | ||
return string.format( | return string.format( | ||
'All tracks written by %s.', | |||
self.all_writing | |||
) | |||
elseif self.all_lyrics and self.all_music then | elseif self.all_lyrics and self.all_music then | ||
return | return string.format( | ||
'All lyrics written by %s; all music composed by %s.', | |||
self.all_lyrics, | self.all_lyrics, | ||
self.all_music | |||
) | |||
elseif self.all_lyrics then | elseif self.all_lyrics then | ||
return string.format( | return string.format( | ||
'All lyrics written by %s.', | |||
self.all_lyrics | |||
) | |||
elseif self.all_music then | elseif self.all_music then | ||
return string.format( | return string.format( | ||
'All music composed by %s.', | |||
self.all_music | |||
) | |||
else | else | ||
return | return '' | ||
end | end | ||
end | end | ||
function TrackListing:renderTrackingCategories() | function TrackListing:renderTrackingCategories() | ||
local ret = '' | local ret = '' | ||
if self.showCategories | |||
and self.hasDeprecatedArgs | |||
ret = ret .. | and mw.title.getCurrentTitle().namespace == 0 | ||
then | |||
ret = ret .. '[[Category:Track listings with deprecated parameters]]' | |||
end | end | ||
return ret | return ret | ||
end | end | ||
function TrackListing:__tostring() | function TrackListing:__tostring() | ||
-- Find columns to output | -- Find columns to output | ||
local columns = {'number', 'title'} | local columns = {'number', 'title'} | ||
Line 433: | Line 272: | ||
end | end | ||
columns[#columns + 1] = 'length' | columns[#columns + 1] = 'length' | ||
-- Find column width | -- Find colspan and column width | ||
local nColumns = #columns | local nColumns = #columns | ||
local nOptionalColumns = nColumns - 3 | local nOptionalColumns = nColumns - 3 | ||
local titleColumnWidth | |||
local titleColumnWidth | |||
if nColumns >= 5 then | if nColumns >= 5 then | ||
titleColumnWidth = 40 | titleColumnWidth = 40 | ||
elseif nColumns >= 4 then | elseif nColumns >= 4 then | ||
titleColumnWidth = 60 | titleColumnWidth = 60 | ||
else | |||
titleColumnWidth = 100 | |||
end | end | ||
local optionalColumnWidth = (100 - titleColumnWidth) / nOptionalColumns | |||
local optionalColumnWidth = | |||
titleColumnWidth = titleColumnWidth .. '%' | titleColumnWidth = titleColumnWidth .. '%' | ||
optionalColumnWidth = optionalColumnWidth .. '%' | |||
---- Title | |||
-- 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') | |||
-- Header row | |||
if self.headline or self.collapsed then | |||
tableRoot:tag('tr'):tag('th') | |||
:addClass('tlheader mbox-text') | |||
:attr('colspan', nColumns) | |||
:css('text-align', 'left') | |||
:css('background-color', '#fff') | |||
:wikitext(self.headline or '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') | |||
:tag('abbr') | |||
:attr('title', 'Number') | |||
:wikitext('No.') | |||
---- Title | |||
headerRow:tag('th') | headerRow:tag('th') | ||
:addClass('tlheader') | |||
:attr('scope', 'col') | :attr('scope', 'col') | ||
:css('width', self.title_width or titleColumnWidth) | :css('width', self.title_width or titleColumnWidth) | ||
:wikitext( | :css('text-align', 'left') | ||
:css('background-color', '#eee') | |||
:wikitext('Title') | |||
---- Optional headers: writer, lyrics, music, and extra | ---- Optional headers: writer, lyrics, music, and extra | ||
Line 458: | Line 345: | ||
if self.optionalColumns[field] then | if self.optionalColumns[field] then | ||
headerRow:tag('th') | headerRow:tag('th') | ||
:addClass('tlheader') | |||
:attr('scope', 'col') | :attr('scope', 'col') | ||
:css('width', width or optionalColumnWidth) | :css('width', width or optionalColumnWidth) | ||
:css('text-align', 'left') | |||
:css('background-color', '#eee') | |||
:wikitext(headerText) | :wikitext(headerText) | ||
end | end | ||
end | end | ||
addOptionalHeader('writer', | addOptionalHeader('writer', 'Writer(s)', self.writing_width) | ||
addOptionalHeader('lyrics', | addOptionalHeader('lyrics', 'Lyrics', self.lyrics_width) | ||
addOptionalHeader('music', | addOptionalHeader('music', 'Music', self.music_width) | ||
addOptionalHeader( | addOptionalHeader( | ||
'extra', | 'extra', | ||
self.extra_column or | self.extra_column or '{{{extra_column}}}', | ||
self.extra_width | self.extra_width | ||
) | ) | ||
Line 474: | Line 364: | ||
---- Track length | ---- Track length | ||
headerRow:tag('th') | headerRow:tag('th') | ||
:addClass(' | :addClass('tlheader') | ||
:attr('scope', 'col') | :attr('scope', 'col') | ||
:wikitext( | :css('width', '4em') | ||
:css('padding-right', '10px') | |||
:css('text-align', 'right') | |||
:css('background-color', '#eee') | |||
:wikitext('Length') | |||
-- Tracks | -- Tracks | ||
for i, track in ipairs(self.tracks) do | for i, track in ipairs(self.tracks) do | ||
tableRoot:node(track:exportRow(columns)) | tableRoot:node(track:exportRow({ | ||
columns = columns, | |||
color = i % 2 == 0 and '#f7f7f7' or '#fff' | |||
})) | |||
end | end | ||
Line 487: | Line 384: | ||
tableRoot | tableRoot | ||
:tag('tr') | :tag('tr') | ||
:tag('td') | |||
:tag(' | |||
:attr('colspan', nColumns - 1) | :attr('colspan', nColumns - 1) | ||
: | :css('padding', 0) | ||
:tag('span') | :tag('span') | ||
:wikitext( | :css('width', '7.5em') | ||
:css('float', 'right') | |||
:css('padding-left', '10px') | |||
:css('background-color', '#eee') | |||
:css('margin-right', '2px') | |||
:wikitext("'''Total length:'''") | |||
:done() | :done() | ||
:done() | :done() | ||
:tag('td') | :tag('td') | ||
:wikitext(self.total_length) | :css('padding', '0 10px 0 0') | ||
:css('text-align', 'right') | |||
:css('background-color', '#eee') | |||
:wikitext(string.format("'''%s'''", self.total_length)) | |||
end | end | ||
-- Tracking categories | |||
-- | |||
root:wikitext(self:renderTrackingCategories()) | root:wikitext(self:renderTrackingCategories()) | ||
return | return tostring(root) | ||
end | end | ||
Revision as of 11:42, 17 July 2025
Warning | This Lua module is used on approximately 117,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. |
CSS | This module uses TemplateStyles: |
![]() | This module depends on the following other modules: |
File:Logo wikibot.svg | 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')
if self.note then
titleCell
:wikitext(' ')
:tag('span')
:css('font-size', '85%')
:wikitext(string.format('(%s)', self.note))
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,
title_width = true,
writing_width = true,
lyrics_width = true,
music_width = true,
extra_width = true,
category = 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
-- Check for deprecated arguments
for deprecatedField in pairs(TrackListing.deprecatedFields) do
if data[deprecatedField] then
self.hasDeprecatedArgs = true
break
end
end
-- Evaluate boolean properties
self.collapsed = yesno(self.collapsed, false)
self.showCategories = yesno(self.category) ~= false
self.category = nil
-- 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:renderTrackingCategories()
local ret = ''
if self.showCategories
and self.hasDeprecatedArgs
and mw.title.getCurrentTitle().namespace == 0
then
ret = ret .. '[[Category:Track listings with deprecated parameters]]'
end
return ret
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')
-- Header row
if self.headline or self.collapsed then
tableRoot:tag('tr'):tag('th')
:addClass('tlheader mbox-text')
:attr('colspan', nColumns)
:css('text-align', 'left')
:css('background-color', '#fff')
:wikitext(self.headline or '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')
:tag('abbr')
:attr('title', 'Number')
:wikitext('No.')
---- Title
headerRow:tag('th')
:addClass('tlheader')
:attr('scope', 'col')
:css('width', self.title_width or titleColumnWidth)
:css('text-align', 'left')
:css('background-color', '#eee')
:wikitext('Title')
---- Optional headers: writer, lyrics, music, and extra
local function addOptionalHeader(field, headerText, width)
if self.optionalColumns[field] then
headerRow:tag('th')
:addClass('tlheader')
:attr('scope', 'col')
:css('width', width or optionalColumnWidth)
:css('text-align', 'left')
:css('background-color', '#eee')
:wikitext(headerText)
end
end
addOptionalHeader('writer', 'Writer(s)', self.writing_width)
addOptionalHeader('lyrics', 'Lyrics', self.lyrics_width)
addOptionalHeader('music', 'Music', self.music_width)
addOptionalHeader(
'extra',
self.extra_column or '{{{extra_column}}}',
self.extra_width
)
---- 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
-- Tracking categories
root:wikitext(self:renderTrackingCategories())
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