Jump to content
Toggle menu
Toggle preferences menu
Toggle personal menu
Not logged in
Your IP address will be publicly visible if you make any edits.

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

-- Module:TourIndex

local p = {}

local cargo = mw.ext.cargo

-- Manual items inserted immediately BEFORE the specified show.
-- Add only genuinely editorial/special separators here.
local before = {
	["Live:20000815"] = {
		{ type = "break" },
	},

	["Live:20010928"] = {
		{ type = "break" },
	},

	["Live:20170725"] = {
		{ type = "ripchester" },
	},

	["Live:20250131"] = {
		{ type = "break" },
		{ type = "header", text = "From Zero World Tour: Mexico" },
	},

	["Live:20250210"] = {
		{ type = "break" },
		{ type = "header", text = "From Zero World Tour: Asia" },
	},

	["Live:20250412"] = {
		{ type = "break" },
		{ type = "header", text = "From Zero World Tour: North America (Leg 1)" },
	},

	["Live:20250612"] = {
		{ type = "break" },
		{ type = "header", text = "From Zero World Tour: Europe" },
	},

	["Live:20250729"] = {
		{ type = "break" },
		{ type = "header", text = "From Zero World Tour: North America (Leg 2)" },
	},

	["Live:20251025"] = {
		{ type = "break" },
		{ type = "header", text = "From Zero World Tour: South America" },
	},

	["Live:20260118"] = {
		{ type = "break" },
		{ type = "header", text = "From Zero World Tour: Middle East + India" },
	},

	["Live:20260303"] = {
		{ type = "break" },
		{ type = "header", text = "From Zero World Tour: Australia and New Zealand" },
	},

	["Live:20260529"] = {
		{ type = "break" },
		{ type = "header", text = "From Zero World Tour: Europe" },
	},
}


local function escape(value)
	return tostring(value or "")
end


local function getDateParts(date)
	if not date or date == "" then
		return nil, nil, nil
	end

	local year, month, day =
		tostring(date):match("^(%d%d%d%d)%-(%d%d)%-(%d%d)")

	if year then
		return tonumber(year), tonumber(month), tonumber(day)
	end

	-- Fallback in case Cargo returns YYYYMMDD.
	year, month, day =
		tostring(date):match("^(%d%d%d%d)(%d%d)(%d%d)$")

	if year then
		return tonumber(year), tonumber(month), tonumber(day)
	end

	return nil, nil, nil
end


local function getDisplayDate(row)
	local page = tostring(row.Link or "")
	local raw = page:gsub("^Live:", "")

	local yearSet =
		row.Year ~= nil and tostring(row.Year) ~= ""

	local monthSet =
		row.Month ~= nil and tostring(row.Month) ~= ""

	local daySet =
		row.Day ~= nil and tostring(row.Day) ~= ""

	if yearSet and monthSet and daySet then
		local year, month, day = getDateParts(row.Date)

		if year and month and day then
			return string.format(
				"%04d.%02d.%02d",
				year,
				month,
				day
			)
		end
	end

	-- An incomplete source date is displayed from the page name.
	if #raw >= 8 then
		return raw:sub(1, 4) .. "." ..
			raw:sub(5, 6) .. "." ..
			raw:sub(7, 8)
	end

	return raw
end

local function renderYear(year)
	return table.concat({
		'|-\n',
		'| colspan="5" class="subheader" |\n',
		'==', tostring(year), '==\n',
	})
end

local function renderMonth()
	return table.concat({
		'|-\n',
		'| colspan="5" |  \n',
	})
end

local function renderHeader(text)
	return table.concat({
		'|-\n',
		'| colspan="5" class="subheader" |\n',
		'===[[', tostring(text), ']]===\n',
	})
end

local function renderBreak()
	return table.concat({
		'|-\n',
		'|<hr>||<hr>||<hr>||<hr>\n',
	})
end

local function renderRIPChester()
	return table.concat({
		'|-\n',
		'| colspan="5" style="text-align:center;" | ',
		"'''''On July 20th, the world was shocked and heartbroken ",
		"to learn of the passing of Chester Bennington.'''''\n",
	})
end

local function renderMarker(marker)
	if marker.type == "break" then
		return renderBreak()
	elseif marker.type == "ripchester" then
		return renderRIPChester()
	elseif marker.type == "header" then
		return renderHeader(marker.text)
	elseif marker.type == "month" then
		return renderMonth()
	elseif marker.type == "year" then
		return renderYear(marker.text)
	end

	return ""
end

local function hasStructuralMarker(markers)
	if not markers then
		return false
	end

	for _, marker in ipairs(markers) do
		if marker.type == "break"
			or marker.type == "header"
			or marker.type == "ripchester"
		then
			return true
		end
	end

	return false
end

local function getStyle(row)
	if row.ShowType == "cancelled" then
		return 'style="color: #D24501;"'
	end

	if row.Artist == "Linkin Park" then
		local today = os.date("!%Y-%m-%d")

		if row.Date == today then
			return 'style="color: #00cc00;"'
		elseif row.Date and row.Date > today then
			return 'style="color: #2dbaba;"'
		end
	end

	return ""
end


local function renderRow(row, frame)
	local link = escape(row.Link)
	local city = escape(row.City)
	local venue = escape(row.Venue)
	local country = escape(row.Country)
	local event = escape(row.Event)

	local style = getStyle(row)
	local displayDate = getDisplayDate(row)

	local flag = ""
	if country ~= "" then
		flag = frame:expandTemplate {
			title = "flag",
			args = { country }
		}
	end

	local function styledCell(value)
    	if style ~= "" then
    		return style .. " | " .. value
    	end

    	return value
    end

	local output = {}

	output[#output + 1] = "|-\n"
	output[#output + 1] =
		"|[[" .. link .. "|" .. displayDate .. "]]"

	output[#output + 1] = "||" .. styledCell(city)
	output[#output + 1] = "||" .. styledCell(venue)
	output[#output + 1] = "||" .. flag
	output[#output + 1] = "||" .. styledCell(event)
	output[#output + 1] = "\n"

	return table.concat(output)
end

local preLinkinParkArtists = {
	["The Pricks"] = true,
	["Relative Degree"] = true,
	["Xero"] = true,
	["Xero 818"] = true,
	["Hybrid Theory"] = true,
}

function p.main(frame)
	local fields = table.concat({
		"ShowPage=Link",
		"City",
		"Venue",
		"Country",
		"Event",
		"Date",
		"Year",
		"Month",
		"Day",
		"Tour",
		"ShowType",
		"Artist",
	}, ",")

	local args = {
		where = table.concat({
			'Artist IN ("Linkin Park","Hybrid Theory","Xero","Xero 818","The Pricks","Relative Degree")',
			'ShowType IN ("upcoming","current","concert","private","TV","festival","radio","benefit","album","awards","sports","label","LPU","summit","fan rehearsal","studio","acoustic","cancelled")',
		}, " AND "),
		orderBy = "Date ASC, ShowPage ASC",
		limit = 5000,
	}

	local rows = cargo.query(
		"Shows",
		fields,
		args
	)

	local output = {}

	local previousYear = nil
	local previousMonth = nil
	local previousTour = nil
	local previousArtist = nil
	local previousRowTour = nil

	-- The historical section starts with one special heading rather
	-- than individual calendar-year headings.
	local preLinkinPark = true
	local preLinkinParkHeadingWritten = false

	for _, row in ipairs(rows) do
		local link = tostring(row.Link or "")
        local yearNumber = tonumber(row.Year)
        -- Use the normalized Cargo Date for the numeric month.
        -- The original Month field may contain names such as "November".
        local _, monthNumber = getDateParts(row.Date)

        local year = yearNumber and tostring(yearNumber) or ""
        local month = monthNumber and tostring(monthNumber) or ""

        local artist = tostring(row.Artist or "")
        local tour = tostring(row.Tour or "")

		local markers = before[link]

		------------------------------------------------------------
		-- PRE-LINKIN PARK SECTION
		------------------------------------------------------------

		if preLinkinPark then
			if not preLinkinParkHeadingWritten then
				output[#output + 1] =
					renderYear("pre-Linkin Park")

				preLinkinParkHeadingWritten = true
			end

			-- Your original index starts the ordinary yearly
			-- structure with Live:20000525.
			if link == "Live:20000525" then
				preLinkinPark = false

				output[#output + 1] =
					renderYear("2000")

				previousYear = "2000"
				previousMonth = month
				previousTour = nil
				previousArtist = nil
				previousRowTour = nil
			end
		end

		------------------------------------------------------------
		-- NORMAL YEAR CHANGES
		------------------------------------------------------------

		if not preLinkinPark
			and link ~= "Live:20000525"
			and year ~= ""
			and year ~= previousYear
		then
			output[#output + 1] =
				renderYear(year)

			previousYear = year
			previousMonth = month
			previousTour = nil
			previousArtist = nil
			previousRowTour = nil
		end

		------------------------------------------------------------
		-- ARTIST HEADERS FOR EARLY BANDS
		------------------------------------------------------------

		if preLinkinPark
			and preLinkinParkArtists[artist]
			and artist ~= previousArtist
		then
			output[#output + 1] =
				renderHeader(artist)

			previousArtist = artist
		end

        ------------------------------------------------------------
        -- TOUR / MONTH STATE
        ------------------------------------------------------------

        local tourChanged =
        	tour ~= ""
        	and tour ~= previousTour
        	
        local startsTourBlock =
        	tour ~= ""
        	and tour ~= previousRowTour

        local monthChanged =
        	not preLinkinPark
        	and month ~= ""
        	and previousMonth ~= nil
        	and previousMonth ~= ""
        	and month ~= previousMonth

        ------------------------------------------------------------
        -- MONTH BREAK
        --
        -- Add between months within the existing flow, including
        -- within a tour and among non-tour events.
        --
        -- Do not add immediately before a new tour header.
        ------------------------------------------------------------

        if monthChanged
        	and not startsTourBlock
        	and not hasStructuralMarker(markers)
        then
        	output[#output + 1] =
        		renderMonth()
        end

		------------------------------------------------------------
		-- MANUAL BREAKS / SPECIAL CONTENT
		------------------------------------------------------------

		if markers then
			for _, marker in ipairs(markers) do
				output[#output + 1] =
					renderMarker(marker)
			end
		end

		------------------------------------------------------------
		-- TOUR HEADER
		------------------------------------------------------------

		if not preLinkinPark and tourChanged then
			output[#output + 1] =
				renderHeader(tour)
		end

		------------------------------------------------------------
		-- SHOW
		------------------------------------------------------------

		output[#output + 1] =
			renderRow(row, frame)

		------------------------------------------------------------
		-- REMEMBER CURRENT VALUES
		------------------------------------------------------------

		if year ~= "" then
			previousYear = year
		end

		if month ~= "" then
			previousMonth = month
		end

		if tour ~= "" then
        	previousTour = tour
        end

		if artist ~= "" then
			previousArtist = artist
		end
		
		previousRowTour = tour
	end

	return table.concat(output)
end


return p