Module:DropList

Revision as of 14:04, 15 February 2016 by がか (talk | contribs)

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

local format = require('Module:StringInterpolation').format
local getArgs = require('Module:GetArgs')
local Ship = require('Module:Ship')
local Formatting = require('Module:Formatting')

-- * Rarity definitions.
-- sync with http://kancolle.wikia.com/wiki/Template:DropList/doc

local rare_ships = {
	'Agano', 'Akashi', 'Akitsu Maru', 'Akitsushima', 'Akizuki', 'Amagi', 'Amatsukaze', 'Arashi', 'Asagumo', 'Asashimo',
	'Bismarck', 'Graf Zeppelin', 'Hagikaze', 'Harusame', 'Hatsukaze', 'Hatsuzuki', 'Hayashimo', 'Hayasui', 'I-401', 'Isokaze',
	'Kashima', 'Katsuragi', 'Kawakaze', 'Kazagumo', 'Kiyoshimo', 'Libeccio', 'Littorio', 'Maikaze', 'Maruyu', 'Mikuma',
	'Mizuho', 'Musashi', 'Noshiro', 'Nowaki', 'Okinami', 'Ooyodo', 'Prinz Eugen', 'Roma', 'Sakawa', 'Taigei',
	'Taihou', 'Takanami', 'Tanikaze', 'Teruzuki', 'Tokitsukaze', 'U-511', 'Umikaze', 'Unryuu', 'Uzuki', 'Yahagi',
	'Yamato', 'Z1', 'Z3', 'Zara',
}

local ignored_ships = {
	'Akatsuki', 'Akebono', 'Aoba', 'Arare', 'Arashio', 'Asashio', 'Ashigara', 'Ayanami', 'Chitose', 'Chiyoda',
	'Choukai', 'Fubuki', 'Fumizuki', 'Furutaka', 'Haguro', 'Hatsuharu', 'Hatsushimo', 'Hatsuyuki', 'Hibiki', 'Houshou',
	'Ikazuchi', 'Inazuma', 'Isonami', 'Isuzu', 'Jintsuu', 'Kagerou', 'Kako', 'Kasumi', 'Kikuzuki', 'Kisaragi',
	'Kuma', 'Kuroshio', 'Maya', 'Michishio', 'Mikazuki', 'Miyuki', 'Mochizuki', 'Murakumo', 'Murasame', 'Mutsuki',
	'Myoukou', 'Nachi', 'Nagara', 'Nagatsuki', 'Naka', 'Natori', 'Nenohi', 'Oboro', 'Ooshio', 'Ryuujou',
	'Samidare', 'Satsuki', 'Sazanami', 'Shigure', 'Shikinami', 'Shiranui', 'Shiratsuyu', 'Shirayuki', 'Suzukaze', 'Tama',
	'Tatsuta', 'Tenryuu', 'Ushio', 'Wakaba', 'Yura', 'Yuudachi',
}

-- * Definitions for parser/formatter.

local args_grammar = {
	node           = '^%s*(%a)%s*$',
	comma_list     = '[^,]+',
	ship_and_nodes = '^%s*(.-)%s*:%s*(.-)%s*$',
	just_node      = '^%s*(%a)%s*$',
	node_and_diff  = '^%s*(%a)%s*/%s*(%S-)%s*$',
	-- TODO:
	-- * Add battle ranks:
	-- node_diff_rank = '^%s*(%a)%s*/%s*(%S-)%s*/%s*(%a)%s*$',
	-- * Hide difficulty:
	-- node_rank = '^%s*(%a)%s*//%s*(%a)%s*$',
	-- * Tooltips with extra info?
}

local diff_colors = {
	['Easy']   = '5a5',
	['Medium'] = 'da6',
	['Hard']   = 'd33',
	['?']      = '0ff'
}

local diff_names = {
	['Easy']   = 'Easy+',
	['Medium'] = 'Medium+',
	['Hard']   = 'Hard+',
	['?']      = '?'
}

-- * parser/formatter.

function find(tbl, v_, k_)
	for _, v in pairs(tbl) do
		if k_ and v[k_] == v_ or not k_ and v == v_ then
			return true
		end
	end
	return false
end

function parseArgs(args)

	local tbl = { nodes = {}, rows = {}, debug = '' }

	function log(message, value)
		tbl.debug = tbl.debug .. string.format('%s: %s\n', message, value)
	end

	if not args.nodes then
		log('info', 'empty table')
		return tbl
	end

	local boss_nodes = {}
	for boss_node_ in string.gmatch(args.boss or "", args_grammar.comma_list) do
		local boss_node = boss_node_:match(args_grammar.node)
		if boss_node then
			local boss_node = string.upper(boss_node)
			if find(boss_nodes, boss_node) then
				log('boss node duplicate', boss_node)
			else
				table.insert(boss_nodes, boss_node)
			end
		else
			log('boss node syntax error', boss_node_)
		end
	end
	if #boss_nodes == 0 then
		log('warning', 'no boss node(s) specified')
	end

	for node_ in string.gmatch(args.nodes, args_grammar.comma_list) do
		local node = node_:match(args_grammar.node)
		if node then
			local node = string.upper(node)
			if find(tbl.nodes, node, 'name') then
				log('node duplicate', node)
			else
				local is_boss = find(boss_nodes, node)
				table.insert(tbl.nodes, { name = node, boss = is_boss })
			end
		else
			log('node syntax error', node_)
		end
	end
	for _, boss_node in pairs(boss_nodes) do
		if not find(tbl.nodes, boss_node, 'name') then
			log('boss node ignored', boss_node)
		end
	end

	-- ship args
	for arg_name, ship_and_nodes in pairs(args) do
		if tonumber(arg_name) then
			local ship, nodes = ship_and_nodes:match(args_grammar.ship_and_nodes)
			if ship and nodes then
				local ship_table = Ship:get_table(ship, '')
				if ship_table and ship_table._type then
					if find(tbl.rows, ship, 'ship') then
						log('ship duplicate', ship)
					elseif find(ignored_ships, ship) then
						log('ship ignored', ship)
					else
						table.insert(tbl.rows, {
							ship = ship,
							rare = find(rare_ships, ship),
							type = Formatting:format_ship_code(ship_table._type) or '?',
							nodes = {}
						})
						local row = tbl.rows[#tbl.rows]
						for _, node in pairs(tbl.nodes) do
							row.nodes[node.name] = nil
						end
						for node_and_diff in string.gmatch(nodes, args_grammar.comma_list) do
							local node, diff = node_and_diff:match(args_grammar.node_and_diff)
							if not node or not diff then
								node = node_and_diff:match(args_grammar.just_node)
								diff = nil
							end
							if node then
								local node = string.upper(node)
								if row.nodes[node] then
									log('ship node duplicate', string.format('%s for %s', node, ship))
								elseif not find(tbl.nodes, node, 'name') then
									log('node ignored', string.format('%s for %s', node, ship))
								else
									row.nodes[node] = {
										color = diff and diff_colors[diff] or diff_colors['?'],
										diff = diff and diff_names[diff] or '?'
									}
								end
							else
								log('ship node syntax error', string.format('%s for %s', node_and_diff, ship))
							end
						end
					end
				else
					log('ship ignored', ship)
				end
			else
				log('ship syntax error', ship_and_nodes)
			end
		end
	end

	return tbl

end

local table_format = {
	header           = '{| class="article-table sortable" align="center" width="100%" style="text-align:center;"\n!Type\n!Ship <sup>[[Template:DropList/doc|?]]</sup>\n',
	header_node      = '!${node}\n',
	header_boss_node = '!style="background-color:pink;color:red;"|\'\'\'${node}\'\'\'\n',
	row              = '|- class="drop-list-non-rare-ship" style="display:none;"\n',
	rare_row         = '|-\n',
	type_cell        = '|${type}\n',
	-- TODO: japanese tooltips
	ship_cell        = '|[[${ship}]]\n',
	rare_ship_cell   = '|[[${ship}|<span style="color:red;">${ship}</span>]]\n',
	node_cell        = '|style="background-color:#${color};"|${diff}\n',
	empty_cell       = '|\n',
	footer           = '|}\n',
	debugger         = [[{| style="width:100%;" align="center" cellspacing="0" class="article-table mw-collapsible mw-collapsed"
!Script warnings
|-
|<pre>${debug}</pre>
|}]]
}

function showTable(tbl)

	local res = table_format.header

	function add(str)
		res = res .. str
	end

	function add_row(row)
		add(row.rare and table_format.rare_row or table_format.row)
		add(format{table_format.type_cell, type = row.type})
		add(format{
			row.rare and table_format.rare_ship_cell or table_format.ship_cell,
			ship = row.ship
		})
		for _, node in pairs(tbl.nodes) do
			local node = row.nodes[node.name]
			add(node and format{
				table_format.node_cell, color = node.color, diff = node.diff
			} or table_format.empty_cell)
		end
	end

	-- header
	for _, node in pairs(tbl.nodes) do
		add(format{
			node.boss and table_format.header_boss_node or table_format.header_node,
			node = node.name
		})
	end

	-- rows
	for _, row in pairs(tbl.rows) do
		-- TODO: Sort by type/name
		if row.rare then
			add_row(row)
		end
	end
	for key, row in pairs(tbl.rows) do
		-- TODO: Sort by type/name
		if not row.rare then
			add_row(row)
		end
	end

	add(table_format.footer)

	if tbl.debug ~= '' then
		add(format{table_format.debugger, debug = tbl.debug})
	end

	return res

end

local DropList = {}

function DropList.show(frame, args_)
	local args = args_ or getArgs{frame = frame:getParent()}
	return showTable(parseArgs(args))
end

-- DropList.test = DropList.show(nil, {nodes = "A,B,C", boss="A,B"})

return DropList