Diferencia entre revisiones de «Track listing»
De La Venciclopedia
(Per talk page request) |
m (1 revisión importada) |
||
(No se muestran 2 ediciones intermedias de 2 usuarios) | |||
Línea 1: | Línea 1: | ||
local yesno = require('Module:Yesno') | local yesno = require('Module:Yesno') | ||
local checkType = require('libraryUtil').checkType | local checkType = require('libraryUtil').checkType | ||
local cfg = mw.loadData('Module:Track listing/configuration') | |||
local | |||
-------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ||
Línea 64: | Línea 59: | ||
if hours and hours:sub(1, 1) == '0' then | if hours and hours:sub(1, 1) == '0' then | ||
-- Disallow times like "0:12:34" | -- Disallow times like "0:12:34" | ||
self:addWarning(string.format( | self:addWarning( | ||
string.format(cfg.leading_0_in_hours, mw.text.nowiki(length)), | |||
cfg.input_error_category | |||
) | |||
return nil | return nil | ||
end | end | ||
Línea 77: | Línea 72: | ||
-- Special case to disallow lengths like "01:23". This check has to | -- Special case to disallow lengths like "01:23". This check has to | ||
-- be here so that lengths like "1:01:23" are still allowed. | -- be here so that lengths like "1:01:23" are still allowed. | ||
self:addWarning(string.format( | self:addWarning( | ||
string.format(cfg.leading_0_in_minutes, mw.text.nowiki(length)), | |||
cfg.input_error_category | |||
) | |||
return nil | return nil | ||
end | end | ||
Línea 87: | Línea 82: | ||
-- Add a warning and return if we did not find a match. | -- Add a warning and return if we did not find a match. | ||
if not seconds then | if not seconds then | ||
self:addWarning(string.format( | self:addWarning( | ||
string.format(cfg.not_a_time, mw.text.nowiki(length)), | |||
cfg.input_error_category | |||
) | |||
return nil | return nil | ||
end | end | ||
Línea 96: | Línea 91: | ||
-- Check that the minutes are less than 60 if we have an hours field. | -- Check that the minutes are less than 60 if we have an hours field. | ||
if hours and tonumber(minutes) >= 60 then | if hours and tonumber(minutes) >= 60 then | ||
self:addWarning(string.format( | self:addWarning( | ||
string.format(cfg.more_than_60_minutes, mw.text.nowiki(length)), | |||
cfg.input_error_category | |||
) | |||
return nil | return nil | ||
end | end | ||
Línea 105: | Línea 100: | ||
-- Check that the seconds are less than 60 | -- Check that the seconds are less than 60 | ||
if tonumber(seconds) >= 60 then | if tonumber(seconds) >= 60 then | ||
self:addWarning(string.format( | self:addWarning( | ||
string.format(cfg.more_than_60_seconds, mw.text.nowiki(length)), | |||
cfg.input_error_category | |||
) | |||
end | end | ||
Línea 122: | Línea 117: | ||
addMixin(Track, Validation) | addMixin(Track, Validation) | ||
Track.fields = | Track.fields = cfg.track_field_names | ||
Track.cellMethods = { | Track.cellMethods = { | ||
Línea 173: | Línea 159: | ||
function Track.makeSimpleCell(wikitext) | function Track.makeSimpleCell(wikitext) | ||
return mw.html.create('td') | return mw.html.create('td') | ||
:wikitext(wikitext or cfg.blank_cell) | |||
:wikitext(wikitext or | |||
end | end | ||
function Track:makeNumberCell() | function Track:makeNumberCell() | ||
return mw.html.create(' | return mw.html.create('th') | ||
: | :attr('id', string.format(cfg.track_id, self.number)) | ||
:attr('scope', 'row') | |||
: | :wikitext(string.format(cfg.number_terminated, self.number)) | ||
: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:wikitext( | ||
self.title and string.format(cfg.track_title, self.title) or cfg.untitled | |||
) | |||
if self.note then | if self.note then | ||
titleCell | titleCell:wikitext(string.format(cfg.note, self.note)) | ||
end | end | ||
return titleCell | return titleCell | ||
Línea 217: | Línea 198: | ||
function Track:makeLengthCell() | function Track:makeLengthCell() | ||
return mw.html.create('td') | return mw.html.create('td') | ||
: | :addClass('tracklist-length') | ||
:wikitext(self.length or cfg.blank_cell) | |||
:wikitext(self.length or | |||
end | end | ||
function Track:exportRow( | function Track:exportRow(columns) | ||
local columns = columns or {} | |||
local columns = | |||
local row = mw.html.create('tr') | local row = mw.html.create('tr') | ||
for i, column in ipairs(columns) do | for i, column in ipairs(columns) do | ||
local method = Track.cellMethods[column] | local method = Track.cellMethods[column] | ||
Línea 244: | Línea 221: | ||
TrackListing.__index = TrackListing | TrackListing.__index = TrackListing | ||
addMixin(TrackListing, Validation) | addMixin(TrackListing, Validation) | ||
TrackListing.fields = cfg.track_listing_field_names | |||
TrackListing.fields = | TrackListing.deprecatedFields = cfg.deprecated_track_listing_field_names | ||
TrackListing.deprecatedFields = | |||
function TrackListing.new(data) | function TrackListing.new(data) | ||
Línea 273: | Línea 231: | ||
for deprecatedField in pairs(TrackListing.deprecatedFields) do | for deprecatedField in pairs(TrackListing.deprecatedFields) do | ||
if data[deprecatedField] then | if data[deprecatedField] then | ||
self:addCategory( | self:addCategory(cfg.deprecated_parameter_category) | ||
break | break | ||
end | end | ||
Línea 337: | Línea 295: | ||
function TrackListing:makeIntro() | function TrackListing:makeIntro() | ||
if self.all_writing then | if self.all_writing then | ||
return string.format( | return string.format(cfg.tracks_written, self.all_writing) | ||
elseif self.all_lyrics and self.all_music then | elseif self.all_lyrics and self.all_music then | ||
return | return mw.message.newRawMessage( | ||
cfg.lyrics_written_music_composed, | |||
self.all_lyrics, | self.all_lyrics, | ||
self.all_music | self.all_music | ||
) | ):plain() | ||
elseif self.all_lyrics then | elseif self.all_lyrics then | ||
return string.format( | return string.format(cfg.lyrics_written, self.all_lyrics) | ||
elseif self.all_music then | elseif self.all_music then | ||
return string.format( | return string.format(cfg.music_composed, self.all_music) | ||
else | else | ||
return | return nil | ||
end | end | ||
end | end | ||
Línea 387: | Línea 336: | ||
function TrackListing:renderWarnings() | function TrackListing:renderWarnings() | ||
if not | if not cfg.show_warnings then | ||
return '' | return '' | ||
end | end | ||
Línea 394: | Línea 343: | ||
local function addWarning(msg) | local function addWarning(msg) | ||
table.insert(ret, string.format( | table.insert(ret, string.format(cfg.track_listing_error, msg)) | ||
end | end | ||
Línea 414: | Línea 360: | ||
function TrackListing:__tostring() | 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 | -- Find columns to output | ||
local columns = {'number', 'title'} | local columns = {'number', 'title'} | ||
Línea 430: | Línea 416: | ||
end | end | ||
columns[#columns + 1] = 'length' | columns[#columns + 1] = 'length' | ||
-- Find | -- Find column width | ||
local nColumns = #columns | local nColumns = #columns | ||
local nOptionalColumns = nColumns - 3 | local nOptionalColumns = nColumns - 3 | ||
local titleColumnWidth | |||
local titleColumnWidth = 100 | |||
if nColumns >= 5 then | if nColumns >= 5 then | ||
titleColumnWidth = 40 | titleColumnWidth = 40 | ||
elseif nColumns >= 4 then | elseif nColumns >= 4 then | ||
titleColumnWidth = 60 | titleColumnWidth = 60 | ||
end | end | ||
local optionalColumnWidth = (100 - titleColumnWidth) / nOptionalColumns | |||
local optionalColumnWidth = ((100 - titleColumnWidth) / nOptionalColumns) .. '%' | |||
titleColumnWidth = titleColumnWidth .. '%' | titleColumnWidth = titleColumnWidth .. '%' | ||
---- Title column | |||
---- Title | |||
headerRow:tag('th') | headerRow:tag('th') | ||
:attr('scope', 'col') | :attr('scope', 'col') | ||
:css('width', self.title_width or titleColumnWidth) | :css('width', self.title_width or titleColumnWidth) | ||
:wikitext(cfg.title) | |||
:wikitext( | |||
---- Optional headers: writer, lyrics, music, and extra | ---- Optional headers: writer, lyrics, music, and extra | ||
Línea 505: | Línea 441: | ||
if self.optionalColumns[field] then | if self.optionalColumns[field] then | ||
headerRow:tag('th') | headerRow:tag('th') | ||
:attr('scope', 'col') | :attr('scope', 'col') | ||
:css('width', width or optionalColumnWidth) | :css('width', width or optionalColumnWidth) | ||
:wikitext(headerText) | :wikitext(headerText) | ||
end | end | ||
end | end | ||
addOptionalHeader('writer', | addOptionalHeader('writer', cfg.writer, self.writing_width) | ||
addOptionalHeader('lyrics', | addOptionalHeader('lyrics', cfg.lyrics, self.lyrics_width) | ||
addOptionalHeader('music', | addOptionalHeader('music', cfg.music, self.music_width) | ||
addOptionalHeader( | addOptionalHeader( | ||
'extra', | 'extra', | ||
self.extra_column or | self.extra_column or cfg.extra, | ||
self.extra_width | self.extra_width | ||
) | ) | ||
Línea 524: | Línea 457: | ||
---- Track length | ---- Track length | ||
headerRow:tag('th') | headerRow:tag('th') | ||
:addClass(' | :addClass('tracklist-length-header') | ||
:attr('scope', 'col') | :attr('scope', 'col') | ||
:wikitext(cfg.length) | |||
:wikitext( | |||
-- Tracks | -- Tracks | ||
for i, track in ipairs(self.tracks) do | for i, track in ipairs(self.tracks) do | ||
tableRoot:node(track:exportRow( | tableRoot:node(track:exportRow(columns)) | ||
end | end | ||
Línea 544: | Línea 470: | ||
tableRoot | tableRoot | ||
:tag('tr') | :tag('tr') | ||
:tag(' | :addClass('tracklist-total-length') | ||
:tag('th') | |||
:attr('colspan', nColumns - 1) | :attr('colspan', nColumns - 1) | ||
: | :attr('scope', 'row') | ||
:tag('span') | :tag('span') | ||
: | :wikitext(cfg.total_length) | ||
:done() | :done() | ||
:done() | :done() | ||
:tag('td') | :tag('td') | ||
:wikitext(self.total_length) | |||
:wikitext( | |||
end | end | ||
root:node(tableRoot) | |||
-- Warnings and tracking categories | -- Warnings and tracking categories | ||
root:wikitext(self:renderWarnings()) | root:wikitext(self:renderWarnings()) | ||
root:wikitext(self:renderTrackingCategories()) | root:wikitext(self:renderTrackingCategories()) | ||
return tostring(root) | return mw.getCurrentFrame():extensionTag{ | ||
name = 'templatestyles', args = { src = 'Module:Track listing/styles.css' } | |||
} .. tostring(root) | |||
end | end | ||
Revisión actual - 14:36 29 dic 2023
This Lua module is used on approximately 86 000 pages and changes may be widely noticed. Test changes in the module's /sandbox or /testcases subpages, or in your own module sandbox. 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.
If you intend to change this module in any significant way, move or nominate it for deletion, please inform the bot operators. Thank you. The relevant bots are: User:cewbot/log/20201008/configuration. |
This module implements {{track listing}}. Please see the template page for documentation.
local yesno = require('Module:Yesno')
local checkType = require('libraryUtil').checkType
local cfg = mw.loadData('Module:Track listing/configuration')
--------------------------------------------------------------------------------
-- Helper functions
--------------------------------------------------------------------------------
-- Add a mixin to a class.
local function addMixin(class, mixin)
for k, v in pairs(mixin) do
if k ~= 'init' then
class[k] = v
end
end
end
--------------------------------------------------------------------------------
-- Validation mixin
--------------------------------------------------------------------------------
local Validation = {}
function Validation.init(self)
self.warnings = {}
self.categories = {}
end
function Validation:addWarning(msg, category)
table.insert(self.warnings, msg)
table.insert(self.categories, category)
end
function Validation:addCategory(category)
table.insert(self.categories, category)
end
function Validation:getWarnings()
return self.warnings
end
function Validation:getCategories()
return self.categories
end
-- Validate a track length. If a track length is invalid, a warning is added.
-- A type error is raised if the length is not of type string or nil.
function Validation:validateLength(length)
checkType('validateLength', 1, length, 'string', true)
if length == nil then
-- Do nothing if no length specified
return nil
end
local hours, minutes, seconds
-- Try to match times like "1:23:45".
hours, minutes, seconds = length:match('^(%d+):(%d%d):(%d%d)$')
if hours and hours:sub(1, 1) == '0' then
-- Disallow times like "0:12:34"
self:addWarning(
string.format(cfg.leading_0_in_hours, mw.text.nowiki(length)),
cfg.input_error_category
)
return nil
end
if not seconds then
-- The previous attempt didn't match. Try to match times like "1:23".
minutes, seconds = length:match('^(%d?%d):(%d%d)$')
if minutes and minutes:find('^0%d$') then
-- Special case to disallow lengths like "01:23". This check has to
-- be here so that lengths like "1:01:23" are still allowed.
self:addWarning(
string.format(cfg.leading_0_in_minutes, mw.text.nowiki(length)),
cfg.input_error_category
)
return nil
end
end
-- Add a warning and return if we did not find a match.
if not seconds then
self:addWarning(
string.format(cfg.not_a_time, mw.text.nowiki(length)),
cfg.input_error_category
)
return nil
end
-- Check that the minutes are less than 60 if we have an hours field.
if hours and tonumber(minutes) >= 60 then
self:addWarning(
string.format(cfg.more_than_60_minutes, mw.text.nowiki(length)),
cfg.input_error_category
)
return nil
end
-- Check that the seconds are less than 60
if tonumber(seconds) >= 60 then
self:addWarning(
string.format(cfg.more_than_60_seconds, mw.text.nowiki(length)),
cfg.input_error_category
)
end
return nil
end
--------------------------------------------------------------------------------
-- Track class
--------------------------------------------------------------------------------
local Track = {}
Track.__index = Track
addMixin(Track, Validation)
Track.fields = cfg.track_field_names
Track.cellMethods = {
number = 'makeNumberCell',
title = 'makeTitleCell',
writer = 'makeWriterCell',
lyrics = 'makeLyricsCell',
music = 'makeMusicCell',
extra = 'makeExtraCell',
length = 'makeLengthCell',
}
function Track.new(data)
local self = setmetatable({}, Track)
Validation.init(self)
for field in pairs(Track.fields) do
self[field] = data[field]
end
self.number = assert(tonumber(self.number))
self:validateLength(self.length)
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')
:wikitext(wikitext or cfg.blank_cell)
end
function Track:makeNumberCell()
return mw.html.create('th')
:attr('id', string.format(cfg.track_id, self.number))
:attr('scope', 'row')
:wikitext(string.format(cfg.number_terminated, self.number))
end
function Track:makeTitleCell()
local titleCell = mw.html.create('td')
titleCell:wikitext(
self.title and string.format(cfg.track_title, self.title) or cfg.untitled
)
if self.note then
titleCell:wikitext(string.format(cfg.note, 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')
:addClass('tracklist-length')
:wikitext(self.length or cfg.blank_cell)
end
function Track:exportRow(columns)
local columns = columns or {}
local row = mw.html.create('tr')
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
addMixin(TrackListing, Validation)
TrackListing.fields = cfg.track_listing_field_names
TrackListing.deprecatedFields = cfg.deprecated_track_listing_field_names
function TrackListing.new(data)
local self = setmetatable({}, TrackListing)
Validation.init(self)
-- Check for deprecated arguments
for deprecatedField in pairs(TrackListing.deprecatedFields) do
if data[deprecatedField] then
self:addCategory(cfg.deprecated_parameter_category)
break
end
end
-- Validate total length
if data.total_length then
self:validateLength(data.total_length)
end
-- Add properties
for field in pairs(TrackListing.fields) do
self[field] = data[field]
end
-- Evaluate boolean properties
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(cfg.tracks_written, self.all_writing)
elseif self.all_lyrics and self.all_music then
return mw.message.newRawMessage(
cfg.lyrics_written_music_composed,
self.all_lyrics,
self.all_music
):plain()
elseif self.all_lyrics then
return string.format(cfg.lyrics_written, self.all_lyrics)
elseif self.all_music then
return string.format(cfg.music_composed, self.all_music)
else
return nil
end
end
function TrackListing:renderTrackingCategories()
if not self.showCategories or mw.title.getCurrentTitle().namespace ~= 0 then
return ''
end
local ret = ''
local function addCategory(cat)
ret = ret .. string.format('[[Category:%s]]', cat)
end
for i, category in ipairs(self:getCategories()) do
addCategory(category)
end
for i, track in ipairs(self.tracks) do
for j, category in ipairs(track:getCategories()) do
addCategory(category)
end
end
return ret
end
function TrackListing:renderWarnings()
if not cfg.show_warnings then
return ''
end
local ret = {}
local function addWarning(msg)
table.insert(ret, string.format(cfg.track_listing_error, msg))
end
for i, warning in ipairs(self:getWarnings()) do
addWarning(warning)
end
for i, track in ipairs(self.tracks) do
for j, warning in ipairs(track:getWarnings()) do
addWarning(warning)
end
end
return table.concat(ret, '<br>')
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(frame)
local args = require('Module:Arguments').getArgs(frame, {
wrappers = 'Template:Track listing'
})
return p._main(args)
end
return p