Jump to content
 







Main menu
   


Navigation  



Main page
Contents
Current events
Random article
About Wikipedia
Contact us
Donate
 




Contribute  



Help
Learn to edit
Community portal
Recent changes
Upload file
 








Search  

































Create account

Log in
 









Create account
 Log in
 




Pages for logged out editors learn more  



Contributions
Talk
 

















Module:Track listing






Afrikaans
Anarâškielâ
Asturianu
Basa Bali

 / Bân-lâm-gú

Cebuano
Deutsch
Eesti
Español
فارسی


Hausa
ि
Bahasa Indonesia
Íslenska
עברית


Kurdî
مازِرونی
Bahasa Melayu
 / Mìng-dĕ̤ng-nḡ



Norsk bokmål
Norsk nynorsk
ି
Oʻzbekcha / ўзбекча

Русский
Scots
Slovenščina
کوردی
Српски / srpski
Suomi

Тоҷикӣ
Українська
اردو
Tiếng Vit


 

Edit links
 









Module
Talk
 

















Read
View source
View history
 








Tools
   


Actions  



Read
View source
View history
 




General  



What links here
Related changes
Upload file
Special pages
Permanent link
Page information
Get shortened URL
Download QR code
Wikidata item
 




Print/export  



Download as PDF
Printable version
 
















Appearance
   

 





Permanently protected module

From Wikipedia, the free encyclopedia
 


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

Retrieved from "https://en.wikipedia.org/w/index.php?title=Module:Track_listing&oldid=1134855366"

Categories: 
Templates using TemplateStyles
Pages monitored by Wikipedia bots
Hidden category: 
Wikipedia template-protected modules
 



This page was last edited on 21 January 2023, at 01:48 (UTC).

Text is available under the Creative Commons Attribution-ShareAlike License 4.0; additional terms may apply. By using this site, you agree to the Terms of Use and Privacy Policy. Wikipedia® is a registered trademark of the Wikimedia Foundation, Inc., a non-profit organization.



Privacy policy

About Wikipedia

Disclaimers

Contact Wikipedia

Code of Conduct

Developers

Statistics

Cookie statement

Mobile view



Wikimedia Foundation
Powered by MediaWiki