Module:Infobox song

From SPCodex, The Smashing Pumpkins wiki

Documentation for this module may be created at Module:Infobox song/doc

local p = {}
local cargo = mw.ext.cargo
local moduleInfobox = require('Module:Infobox')
local moduleDelink = require('Module:Delink')

local types = {
	['single'] = {
		color = 'khaki',
		link = '[[:Category:Singles|Single]]',
		category = 'Singles'
	},
	['song'] = {
		color = '#E6E8FA',
		link = '[[:Category:Songs|Song]]',
		category = 'Songs'
	},
	['promo'] = {
		color = '#E6E8FA',
		link = '[[:Category:Promotional singles|Promotional single]]',
		category = 'Promotional singles'
	},
	['cover'] = {
		color = '#E6E8FA',
		link = '[[:Category:Cover songs|Cover song]]',
		category = 'Cover songs'
	},
	['instrumental'] = {
		color = '#E6E8FA',
		link = '[[:Category:Instrumental songs|Instrumental song]]',
		category = 'Instrumental songs'
	}
}

function p.link( frame )
	local given = frame.args[1]
	if given == '' or given == nil then
		given = 'song'
	end
	local given = mw.text.split(given, "%s*,%s*")
	return types[given[1]].link
end

function p.color( frame )
	local given = frame.args[1]
	if given == '' or given == nil then
		given = 'song'
	end
	given = mw.text.split(given, "%s*,%s*")
	return types[given[1]].color
end

function p._categories( artist, album, release_date, args )
	if mw.title.getCurrentTitle().namespace ~= 0 then
		return ''
	end
	local songType = args.type
	if args.type == '' or args.type == nil then
		songType = 'song'
	end
	songType = mw.text.split(songType, "%s*,%s*")

	local cats = '[[Category:Songs]]'
	local is_cover = false

	-- [[Category:Singles]], [[Category:Cover songs]], [[Category:Billy Corgan cover songs]], etc.
	for _, v in ipairs(songType) do
		local data = types[v]
		if v == 'cover' then
			is_cover = true
		end
		cats = cats .. '[[Category:' .. data.category .. ']]'
		if artist ~= nil and args.nonstandard_artist == nil and args.side_project == nil and (is_cover and v == 'cover') == false then
			cats = cats .. '[[Category:' .. artist .. ' ' .. data.category:lower() .. ']]'
		end
	end

	-- [[Category:Album songs]]
	cats = cats .. '[[Category:' .. album ..  ' songs]]'

	-- [[Category:Artist side projects]], [[Category:Arist songs]]
	if args.side_project ~= nil then
		cats = cats .. "[[Category:" .. args.side_project .. " side projects]]"
	elseif artist ~= nil and args.nonstandard_artist == nil and is_cover == false then
		cats = cats .. "[[Category:" .. artist .. " songs]]"
	end

	-- [[Category:Music videos]], [[Category:Artist music videos]]
	if args.video ~= '' and args.video ~= nil then
		cats = cats .. '[[Category:Music videos]]'
		if args.side_project == '' or args.side_project == nil then
			cats = cats .. '[[Category:' .. artist .. ' music videos]]'
		end
	end

	-- [[Category:YYYY singles]], [[Category:YYYY songs]]
	if args.nonstandard_artist == nil and args.released ~= nil then
		local cat_type = (args.type == 'single') and 'single' or 'song'
		-- Categorize each release
		local matches = string.gmatch( args.released, "%d%d%d%d" )
		for year in matches do
			cats = cats .. '[[Category:' .. year .. ' ' .. string.lower(cat_type) .. 's]]'
		end

		-- Categorize the first release as 'single'/'cover'/etc., if applicable
		local first_year = string.match( args.released, "%d%d%d%d" )
		if first_year and cat_type ~= 'song' then
			cats = cats .. '[[Category:' .. first_year .. ' ' .. 'songs]]'
		end
	end

	-- [[Category:Songs written by artist]]
	if args.writer ~= nil and args.writer ~= '' then
		local writersToCategorize = {
			['Billy Corgan'] = true,
			['James Iha'] = true,
			["D'arcy Wretzky"] = true,
			['Jimmy Chamberlin'] = true,
			['Kerry Brown'] = true,
			['Jeff Schroeder'] = true,
			['Courtney Love'] = true,
			['Frank Catalano'] = true
		}
		local writers = mw.text.split(args.writer, "%s*,%s*")
		for _i, writer in ipairs(writers) do
			if writersToCategorize[writer] then
				cats = cats .. '[[Category:Songs written by ' .. writer .. ']]'
			end
		end
	else
		cats = cats .. '[[Category:Songs with unknown writers]]'
	end

	-- [[Songs produced by artist]]
	if args.producer ~= nil and args.producer ~= '' then
		local producersToCategorize = {
			['Billy Corgan'] = true,
			['James Iha'] = true,
			["D'arcy Wretzky"] = true,
			['Jimmy Chamberlin'] = true,
			['Butch Vig'] = true,
			['Flood'] = true,
			['Alan Moulder'] = true,
			['Kerry Brown'] = true,
			['Bjorn Thorsrud'] = true,
			['Nellee Hooper'] = true,
			['Terry Date'] = true,
			['Jeff Schroeder'] = true,
			['Howard Willing'] = true,
			['Rick Rubin'] = true,
			['Roy Thomas Baker'] = true,
			['Brad Wood'] = true,
			['Dale Griffin'] = true,
			['Ted de Bono' ] = true
		}
		local producers = mw.text.split(args.producer, "%s*,%s*")
		for _i, producer in ipairs(producers) do
			if producersToCategorize[producer] then
				cats = cats .. '[[Category:Songs produced by ' .. producer .. ']]'
			end
		end
	end

	return cats
end

function p._track_listing(song, album)
	local results = cargo.query(
		'track_listings',
		'track, song, song_page_title',
		{
			where = 'work = "' .. album .. '" AND main_listing = 1',
			orderBy = 'track_listings._ID ASC'
		}
	)

	local root = mw.html.create()
	local olRoot = root:tag('ol')
	local has_match = false

	for r = 1, #results do
		local result = results[r]
		local page = mw.title.new(result['song_page_title'])

		local wikitext = result['song']
		if page.exists then
			wikitext = '[[' .. result['song_page_title'] .. '|' .. result['song'] .. ']]'
		end

		olRoot:tag('li')
			:attr('value', result['track'])
			:wikitext(wikitext)

		if result['song'] == song then
			has_match = true
		end
	end
	
	if has_match then
		return tostring(root)
	end
	
	return ''

	-- local tableRoot = root:tag('table')

	-- for r = 1, #results do
	-- 	local row = tableRoot:tag('tr')
	-- 	local result = results[r]
	-- 	row:tag('td')
	-- 		:css('padding-right', '5px')
	-- 		:wikitext(result['track'] .. '.')
	-- 	row:tag('td'):wikitext('[[' .. result['song_page_title'] .. '|' .. result['song'] .. ']]')
	-- end
end

function p.track_listing(frame)
	local song = frame.args[1]
	local album = frame.args[2]
	return p._track_listing(song, album)
end

function p._studio_sessions(song)
	local cargo = mw.ext.cargo
	local results = cargo.query(
		'studio_sessions, recordings, songs',
		'studio_sessions._pageName=page',
		{
			where = 'songs._pageName = "' .. song .. '"',
			join = 'recordings.song=songs.name,studio_sessions._pageName=recordings._pageName',
			groupBy = 'studio_sessions._pageName',
			orderBy = 'studio_sessions.date_from ASC'
		}
	)

	if 0 == #results then
		return ''
	elseif 1 == #results then
		return '[[' .. results[1]['page'] .. ']]'
	end

	local root = mw.html.create()
	local ulRoot = root:tag('ul')
		:addClass('studio-sessions')

	local sessions = {}
	for r = 1, #results do
		local page = results[r]['page']

		if page ~= nil and page ~= '' then
			ulRoot:tag('li')
				:wikitext('[[' .. page .. ']]')
		end
	end

	return tostring(root)
	-- return table.concat(sessions, ' • ')
end

function p.studio_sessions(frame)
	local album = frame.args[1]
	return p._studio_sessions(album)
end

function p._validateOrFetchReleaseDate(artist, song, release_date)
	local Date = require('Module:Date')._Date
	release_date = Date(release_date)
	if release_date ~= nil then
		return release_date:text('ymd')
	end

	-- Try to find the earliest US date, or earliest date overall if there isn't a US date.
	local cargo = mw.ext.cargo
	local results = cargo.query(
		"songs, albums, releases",
		"songs._pageName, songs.name, releases.country, releases.release_date, albums.released",
		{
			where = "songs.artist = \"" .. artist .. "\" AND songs.name = \"" .. song .. '"',
			join = "songs._pageName = releases._pageName, songs.main_album = albums.name",
		}
	)

	if next(results) == nil or (#results == 1 and results[1]['releases.release_date'] == nil and results[1]['albums.released'] == nil) then
		return ''
	end

	local currentDate = Date('currentdate')
	-- earliestDate is the earliest release date, either the single or the album
	local earliestDate = currentDate
	-- earliestDateUS is only for single release dates, not albums
	local earliestDateUS = currentDate

	for r = 1, #results do
		local row = results[r]
		local release_table_date = Date(row['releases.release_date']) or currentDate
		local album_table_date = Date(row['albums.released']) or currentDate
		local isUS = row['releases.country'] == 'US' or row['releases.country'] == 'Worldwide'

		if release_table_date < album_table_date then
			-- single release date came first
			if isUS and release_table_date < earliestDateUS then
				earliestDateUS = release_table_date
			end
			if release_table_date < earliestDate then
				earliestDate = release_table_date
			end
		else
			-- album release date came first
			if isUS and release_table_date < earliestDateUS then
				earliestDateUS = release_table_date
			end
			if album_table_date < earliestDateUS then
				earliestDate = album_table_date
			end
		end
	end

	if earliestDateUS < currentDate then
		return earliestDateUS:text('ymd')
	else
		return earliestDate:text('ymd')
	end
end

function p.validateOrFetchReleaseDate(frame)
	local artist = frame.args[1]
	local song = frame.args[2]
	local release_date = frame.args[3]
	local normalized_release_date = p._validateOrFetchReleaseDate(artist, song, release_date)
	if normalized_release_date ~= '' and normalized_release_date ~= nil then
		frame:callParserFunction('#vardefine', 'release_date', normalized_release_date)
	end
end

function p.infobox(frame)
	local args = frame:getParent().args
	local song = args.name or tostring(mw.title.getCurrentTitle())
	local artist = moduleDelink._delink({mw.getContentLanguage():ucfirst(args.artist or '')})
	local album = moduleDelink._delink({args.album or ''})
	local release_date = p._validateOrFetchReleaseDate(artist, song, args.released_cargo or args.released) or ''
	local categories = p._categories(artist, album, release_date, args)

	-- Infobox parameters
	frame.args.above = '"' .. song .. '"'
	if (args.nonstandard_artist == '' or args.nonstandard_artist == nil) and release_date ~= '' then
		frame.args.data10 = args.released
	end

	-- Writer
	if args.writer ~= nil and args.writer ~= '' then
		for writer in string.gmatch(args.writer, "([^,]+)") do
			writer = moduleDelink._delink({writer})
			local page = mw.title.new(writer)
			if page.exists then
				args.writer = mw.ustring.gsub( args.writer, writer, '[[' .. writer .. ']]' )
			end
		end
	end
	frame.args.data40 = args.writer

	-- Producer
	if args.producer ~= nil and args.producer ~= '' then
		local producers = {}
		for _i, producer in ipairs(mw.text.split(args.producer, '%s*,%s*')) do
			producer = moduleDelink._delink({producer})
			local page = mw.title.new(producer)
			if page and page.exists then
				table.insert(producers, '[[' .. producer .. ']]')
			else
				table.insert(producers, producer)
			end
		end
		frame.args.data43 = table.concat(producers, ', ')
	end

	-- Studio sessions
	if args.recorded == nil or args.recorded == '' then
		frame.args.data12 = p._studio_sessions(song)
	else
		frame.args.data12 = args.recorded .. (args.recorded_ref or '')
		frame.args.data13 = require('Module:Link')._studio(args.studio or '')
	end

	-- Track listings
	local track_listing = p._track_listing(song, album)
	if track_listing ~= '' then
		frame.args.below = (args.misc or '') .. frame:expandTemplate{
			title = 'hidden',
			args = {
				class = 'nomobile',
				headerstyle = 'background:' .. frame:expandTemplate{
					title = 'Infobox song/color',
					args = { args.type }
				},
				'[[' .. album .. ']]',
				track_listing
			}
		}
	end

	-- Cargo storage
	local cargo_data = {
		'_table=songs',
		name = song,
		type = args.type,
		artist = artist,
		original_artist = moduleDelink._delink({args.original_artist}),
		main_album = album,
		song_release_date = release_date,
		writer = moduleDelink._delink({args.writer}),
		video = args.video ~= '' and args.video ~= nil,
		spotify = args.spotify,
		discogs = args.discogs,
		musicbrainz = args.musicbrainz,
		cover = args.cover
	}
	frame:callParserFunction('#cargo_store', cargo_data)

	-- Cargo storage for EPs (which get stored as an album)
	if args.is_ep ~= nil and args.is_ep ~= '' then
		frame:expandTemplate{
			title = 'Infobox song/Album store',
			args = {
				name = song,
				cover = args.cover,
				type = 'ep',
				artist = artist,
				producer = args.producer,
				engineer = args.engineer,
				released = release_date,
				discogs = args.discogs,
				musicbrainz = args.musicbrainz
			}
		}
	end

	return moduleInfobox.infobox(frame.args) .. categories
end

return p