Module:Track listing

From The New Prairie Universe Wiki
Jump to navigation Jump to search

-- 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 .. 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