Module:SMW data tables

From Path of Exile Wiki
Jump to: navigation, search


Overview

Module for implementing templates for use in Semantic Mediawiki data input handling (i.e. setting semantic properties and subobjects) on a page and optionally displaying the input.

List of currently implemented templates

Generic

Specific

Tests

YesY All tests passed.

Name Expected Actual
YesY test_generic_stat_table

--
-- Module for Semantic Mediawiki data tables
--
-- The goal is to verify input and put it on a page as well as streamlining things.
-- Note that very complex functions should go into their own module, i.e. like the item stuff.
--

local getArgs = require('Module:Arguments').getArgs
local util = require('Module:Util')
local game = require('Module:Game')

local doInfoCard

local p = {}
local g_frame, g_args

-- ----------------------------------------------------------------------------
-- Utility / Helper functions
-- ----------------------------------------------------------------------------

local h = {}

-- Validate single value properties and set them

h.validate = {}

function h.validate.not_nil (args)
    return function (arg)
        if g_args[arg] == nil then
            error(string.format('%s must not be nil', arg))
        end
    end
end

function h.validate.number (args)
    return function (arg)
        g_args[arg] = util.cast.number(g_args[arg], args)
        return g_args[arg]
    end
end


function h.handle_mapped_property_args (map)
    local properties = {}
    
    for _, data in ipairs(map) do
        if data.func ~= nil then
            data.func(data.name)
        end
        
        if data.property ~= nil then
            properties[data.property] = g_args[data.name]
        end
    end
    
    g_frame:callParserFunction('#set:', properties)
end

function h.timezone(s)
	if s ~= nil and s:sub(-1,-1) == 'Z' then  
		return 'UTC'
	else
		return ''
	end
end

-- ----------------------------------------------------------------------------
-- Section generic templates
-- ----------------------------------------------------------------------------

--
-- Template: SMW generic stat table
--
function p.generic_stat_table(frame)
    -- Args
    g_args = getArgs(frame, {
        parentFirst = true
    })
    g_frame = util.misc.get_frame(frame)
    
    if not util.smw.safeguard{} then
        return util.html.error{msg='Do not call this template/module from userspace'}
    end
    
    --
    local i = util.cast.number(g_args.start_index, {default=1})
    local end_index = tonumber(g_args.end_index)
    if end_index ~= nil then
        end_index = end_index + 1
    end
    repeat 
        local id = {
            id = 'stat' .. i .. '_id',
            min = 'stat' .. i .. '_min',
            max = 'stat' .. i .. '_max',
            value = 'stat' .. i .. '_value',
            text = 'stat' .. i .. '_text',
        }
        
        local value = {
            id = g_args[id.id],
            min = g_args[id.min],
            max = g_args[id.max],
            value = g_args[id.value],
            text = g_args[id.text],
        }
        
        if value.id ~= nil then
            if value.value ~= nil and (value.min ~= nil or value.max ~= nil) then
                error(string.format('Only one of %s or (%s, %s) should be set', id.value, id.min, id.max)) 
            elseif value.value == nil and (value.min == nil or value.max == nil) then
                error(string.format('Both %s and %s must be set', id.min, id.max))
            end
            
            local properties = {}
            
            properties['Is stat number'] = i
            properties['Has stat id'] = value.id
            
            if value.value ~= nil then
                properties['Has stat value'] = util.cast.number(value.value)
            else
                properties['Has minimum stat value'] = util.cast.number(value.min)
                properties['Has maximum stat value'] = util.cast.number(value.max)
            end
            
            if value.text ~= nil then
                properties['Has stat text'] = value.text
            end
            
            g_frame:callParserFunction('#subobject:', properties)
        end
        i = i + 1
    until (end_index and i == end_index) or (not end_index and value.id == nil)
    -- if not called from a template, add some args
    if util.misc.is_frame(frame) then
        return util.misc.add_category({'Pages defining semantic stat data'})
    end
end

-- ----------------------------------------------------------------------------
-- Section specific templates
-- ----------------------------------------------------------------------------

--
-- Template: SMW 
--

--
-- Template:Area
--

-- a=p.area{name = "Lioneye's Watch", type = "Act", act = "1", icon = "town", mlvl1 = "15",  mlvl2 = "40", mlvl3 = "56", flavourtext = "Cold, dank and reeks of infection.<br />That's how Hope fares in Wraeclast."}
-- b=p.area{name = "Abandoned Dam", type = "corrupted", icon = "town", mlvl1 = "35",  mlvl2 = "56", mlvl3 = "68"}
-- c=p.area{name = "The Phrecia Outskirts", type = "Descent", difficulty = "Normal", mlvl1 = "2"}
-- d=p.area{name = "The Necromantic Crypt", type = "Descent: Champions", difficulty = "Cruel", mlvl2 = "14"} -- Note: Was mlvl1 on the page, need to be changed.
-- e=p.area{name = "Haunted Mansion", type = "Mission", tileset = 'Sceptre'}
-- f=p.area{name = "Enlightened Hideout", type = "Hideout", tileset = 'Library'}
-- g=p.area{name = "The Sewer Waterway", type = "removed", act = "3", icon = "normal", mlvl1 = "32",  mlvl2 = "51", mlvl3 = "63", flavourtext = "Nothing reeks like civilisation."}

function p.area (frame)
    -- Get args
    g_args = getArgs(frame, {
        parentFirst = true
    })
    g_frame = util.misc.get_frame(frame)
	
	if not doInfoCard then
		doInfoCard = require('Module:Infocard')._main
	end
	
	local image
	if g_args['image'] == nil then 
		image = g_args['name'] .. ' area screenshot.jpg' 
	else 
		image = g_args['image']
	end
	image = string.format('<div class="image"> [[File:%s|alt=|250px]]</div>', image)
	
	local area_table = {
		{'act'					, 'Act ' .. tostring(g_args['act']) .. ' area'}, 
		{'corrupted'			, 'Corrupted area'},
		{'map'					, 'Map area'},
		{'descent'				, 'Descent  area'},
		{'descent: champions'	, 'Descent: Champions area'},
		{'mission'				, 'Mission area'},
		{'hideout'				, 'Hideout area'},
		{'removed'				, 'Removed area'},
	}
	
	for i=1,#area_table do
		if area_table[i][1] == g_args['type']:lower() then
			g_args['area_type'] = area_table[i][2]
		end
	end

	local map = {
        {
            name = 'name',
            property = 'Has name',
            func = h.validate.not_nil{},
        },
        {
            name = 'area_type',
            property = 'Is area',
            func = h.validate.not_nil{},
        },
    }
	
	local icon_table = {
		normal = {
			display = '[[File:No waypoint area icon.png|link=|No Waypoint]]'
		},
		waypoint = {
			display = '[[File:Waypoint area icon.png|link=|Waypoint]]'
		},
		town = {
			display = '[[File:Town area icon.png|link=|Town Hub]]'
		},
	}
	
	icon_data = icon_table[tostring(g_args['icon']):lower()]
	local icon
	if icon_data ~= nil then
		icon = icon_data['display']
	end
	
	-- Add the monster level of the area and which difficulty it is in.
	local block = {'[[Monster level]]: ', } --Buggy when a level isn't specified.
	for i, v in ipairs(game.constants.difficulties) do 
		if g_args['mlvl' .. i] ~= nil then 
			block[#block+1] = util.html.abbr(g_args['mlvl' .. i], string.format('%s on %s difficulty', tostring(g_args['mlvl' .. i]), game.constants.difficulties[i]['full']))
			
			if g_args['mlvl' .. i+1] ~= nil then
				block[#block+1] = '<span class="autocomment"> / </span>'
			end
			
			map[#map+1] = {
				name = 'mlvl' .. i, 
				property = string.format('Has monster level on %s difficulty', game.constants.difficulties[i]['full']), 
				func = h.validate.not_nil{}
			}
		end
	end
	
	-- Check if tileset is specified, if so add category.
	if g_args['tileset'] ~= nil then
		map[#map+1] = {
            name = 'tileset',
            property = 'Has tileset',
            func = h.validate.not_nil{},
        }
		local cat_tileset = g_args['tileset'] .. 'tileset'
	end
	
	-- Validate args & set properties
	h.handle_mapped_property_args(map)
	
	-- Output Infocard
	local tplargs = {
		['class'] = 'area',
		['header'] = g_args['name'],
		['headerright'] = icon,
		['subheader'] = g_args['area_type'],
		[1] = table.concat(block, ''),
		[2] = image,
		[3] = g_args['flavourtext'],
		[4] = g_args['tileset'],
    }

    -- Categories
    local cats = {
		g_args['area_type'] .. 's',
		cat_tileset,
    }
    
    -- Done
    
    return doInfoCard(tplargs) .. util.misc.add_category(cats)
end


--
-- Template: Ascendancy Class
--

-- =p.ascendancy_class{name='Slayer', base_class='Duelist', class_id=1}
function p.ascendancy_class (frame)
    -- Get args
    g_args = getArgs(frame, {
        parentFirst = true
    })
    g_frame = util.misc.get_frame(frame)
	
	if not doInfoCard then
		doInfoCard = require('Module:Infocard')._main
	end
    
    local map = {
        {
            name = 'name',
            property = 'Is ascendancy class',
            func = h.validate.not_nil{},
        },
        {
            name = 'base_class',
            property = 'Has base class',
            func = h.validate.not_nil{},
        },
        {
            name = 'class_id',
            property = 'Is ascendancy class id',
            func = h.validate.number{},
        },
        {
            name = 'flavour_text',
            property = 'Has flavour text',
        },
    }
    
    -- Validate args & set properties
    h.handle_mapped_property_args(map)

    -- Output Infocard
    
    local tplargs = {
        ['header'] = g_args['name'],
        ['subheader'] = string.format('[[%s]]', g_args['base_class']),
        [1] = string.format('[[File:%s ascendancy class.png|266px]]', g_args['name']),
        [2] = g_args.flavour_text
    }
    
    -- cats
    
    local cats = {
        'Ascendancy Classes', 
        g_args['base_class'] .. ' Ascendancy Classes',
    }
    
    -- Done
    
    return doInfoCard(tplargs) .. util.misc.add_category(cats)
end


--
-- Template: Event
--

-- a=p.event{name='Ascendancy', type = 'expansion', release_version = '2.2.0', release_date = '2016.03.04'}
-- b=p.event{name='Winterheart race season', type='race', release_version = '2.3.0', release_date='2016.01.29', end_date='2016-08-29T22:00:00Z', number_of_events='155', rewards="[[Asphyxia's Wrath]] <br> [[Sapphire Ring]] <br> [[The Whispering Ice]] <br> [[Dyadian Dawn]] <br> [[Call of the Brotherhood]] <br> [[Winterheart]]", prize="[[Demigod's Dominance]]", links='[http://www.pathofexile.com/seasons Schedule and overview]'  }
-- c=p.event{name='PvP season 2', type='pvp', release_date='2015.02.15', end_date='2015.03.16', rewards="[[Wanderlust]] <br> [[Coral Ring]] <br> [[Geofri's Baptism]] <br> [[Kikazaru]] <br> [[Meginord's Girdle]] <br> [[Atziri's Foible]]", prize='[[Talisman of the Victor]]', links='[http://www.pathofexile.com/seasons/pvp/season/EUPvPSeason2 EU PvP Ladder] <br> [http://www.pathofexile.com/seasons/pvp/season/USPvPSeason2 US PvP Ladder]'  }
-- d=p.event{name='Perandus league', type='challenge ', release_version = '2.2.0', release_date='2016.03.04', end_date='2016.05.30', standard = 'True', hardcore = 'True'}

function p.event (frame)
    -- Get args
    g_args = getArgs(frame, {
        parentFirst = true
    })
    g_frame = util.misc.get_frame(frame)
	
	if not doInfoCard then
		doInfoCard = require('Module:Infocard')._main
	end
	
	local input = {
		header = {
			{
				name = 'name',
				property = 'Has name',
				func = h.validate.not_nil{},
			},
		},
		subheader = {
			challenge = {
				name = 'Challenge league',
				short_name = function ()
					return g_args['name']:lower():gsub('league', ''):gsub('^%l', string.upper)
				end,
				property = 'Is event',
				func = h.validate.not_nil{},
			},
			expansion = {
				name = 'Expansion',
				short_name = function ()
					return g_args['name']
				end,
				property = 'Is event',
				func = h.validate.not_nil{},
			},
			pvp = {
				name = 'PvP season',
				short_name = function ()
					return g_args['name']:lower():gsub('pvp season', ''):gsub('^%l', string.upper)
				end,
				property = 'Is event',
				func = h.validate.not_nil{},
			},
			race = {
				name = 'Race season',
				short_name = function ()
					return g_args['name']:lower():gsub('race season', ''):gsub('^%l', string.upper)
				end,
				property = 'Is event',
				func = h.validate.not_nil{},
			},
		},
		block = {
			{ 
				name = 'release_version',
				block = string.format("'''Release version:''' [[Version %s|%s]] <br>", tostring(g_args['release_version']), tostring(g_args['release_version'])),
				property = 'Has release version',
				func = h.validate.not_nil{},
			},
			{ 
				name = 'release_date',
				block = string.format("'''Release date:''' ''%s %s'' <br>", tostring(g_frame:callParserFunction(string.format("#ask:[[Has name::%s]]", g_args['name']), "?Has release date=", "mainlabel=-")), h.timezone(g_args['release_date'])),
				property = 'Has release date',
				func = h.validate.not_nil{},
			},
			{ 
				name = 'end_date',
				block = string.format("'''End date:''' ''%s %s'' <br>", tostring(g_frame:callParserFunction(string.format("#ask:[[Has name::%s]]", g_args['name']), "?Has end date=", "mainlabel=-")), h.timezone(g_args['end_date'])),
				property = 'Has end date',
				func = h.validate.not_nil{},
			},

			{ 
				name = 'number_of_events',
				block = string.format("'''Number of events:''' %s <br>", tostring(g_args['number_of_events'])),
				property = 'Has number of events',
				func = h.validate.number{},
			},	
			{ 
				name = 'rewards',
				block = string.format([['''Rewards:''' <div style="text-align: right;">%s</div>]], tostring(g_args['rewards'])),
				-- property = 'Has rewards',
				-- func = h.validate.not_nil{},
			},
			{ 
				name = 'prize',
				block = string.format("'''Prize:''' %s <br>", tostring(g_args['prize'])),
				-- property = 'Has prize',
				-- func = h.validate.not_nil{},
			},
			{ 
				name = 'links',
				block = string.format("'''Links:''' %s", tostring(g_args['links'])),
				-- property = 'Has links',
				-- func = h.validate.not_nil{},
			},
		},
	}
	
	local map = {}
	
	-- Add properties for the the header.
	for i,v in ipairs(input['header']) do
		map[#map+1] = input['header'][i]
	end
	
	-- Change type to a proper name, add a shorter name (navboxes, etc.) and the ordinal number (Sorting and counter for seasons and such.) 
	local type_data = input['subheader'][g_args['type']]
	if type_data ~= nil then
		g_args['type'] = type_data['name']
		-- we know this is a function, so it can be called
		g_args['short_name'] = type_data['short_name']()
		g_args['ordinal_number'] = g_frame:callParserFunction(string.format("#ask:[[Is event::%s]] [[Has release date::<%s]]", tostring(g_args['type']), tostring(g_args['release_date'])), "format=count")	
		
		-- Set properties.
		map[#map+1] = {name = 'type', property = type_data['property'], func = type_data['func'],}
		map[#map+1] = {name = 'short_name', property = 'Has short name', func = h.validate.not_nil{},}
		map[#map+1] = {name = 'ordinal_number', property = 'Has ordinal number', func = h.validate.not_nil{},}
	end
	
	-- Add a image to the infocard. 
	local image
	if g_args['image'] == nil then 
		image = g_args['name'] .. ' logo.png' 
	else 
		image = g_args['image']
	end
	image = string.format('<div class="image"> [[File:%s|alt=|250px]]</div>', image)
		
	-- Add the block text. 
	local block_data = input['block']
	local block = {'<div style="text-align: left;">'} -- Intro
	for i,_ in ipairs(block_data) do
		if g_args[input['block'][i]['name']] ~= nil then
			block[#block+1] = block_data[i]['block']
			
			if block_data[i]['property'] ~= nil then
				map[#map+1] = {name = block_data[i]['name'], property = block_data[i]['property'], func = block_data[i]['func']}
			end
		end
	end
	block[#block+1] = '</div>' -- Outro
	
	-- TODO: change input from 'True' to true later for the leagues.
	-- str2bool = {
		-- TRUE 	= true,
		-- FALSE 	= false,
		-- }
	-- for _,v in ipairs({'standard', 'hardcore'}) do
		-- map[#map+1]	= {name = v, property = string.format('Is %s event', v), func = h.validate.not_nil{},}
		-- if type(str2bool[g_args[v]:upper()]) == boolean then
			-- g_args[v] = str2bool[g_args[v]:upper()]
		-- else
			-- g_args[v] = false
		-- end
	-- end
	
	if g_args['standard'] == 'True' and g_args['hardcore'] == 'False' then
		g_args['event_mode'] 	= true
		map[#map+1] 			= {name = 'event_mode', property = 'Is standard event', func = h.validate.not_nil{},} -- set properties.
	elseif g_args['standard'] == 'False' and g_args['hardcore'] == 'True' then
		g_args['event_mode'] 	= true
		map[#map+1] 			= {name = 'event_mode', property = 'Is hardcore event', func = h.validate.not_nil{},} -- set properties.
	elseif g_args['standard'] == 'True' and g_args['hardcore'] == 'True' then
		g_args['event_mode'] 	= true
		map[#map+1] 			= {name = 'event_mode', property = 'Is standard and hardcore event', func = h.validate.not_nil{},} -- set properties.
	end
	
	-- Validate args & set properties
	h.handle_mapped_property_args(map)
	
	-- Output Infocard
	local tplargs = {
		['class'] = 'event',
		['header'] = g_args['name'],
		['subheader'] = type_data['name'],
		[1] = image,
		[2] = table.concat(block, ''),
    }
    
    -- Categories
    local cats = {
		g_args['type'] .. 's',
    }
    
    -- Done
    return doInfoCard(tplargs) .. util.misc.add_category(cats)
end

-- ----------------------------------------------------------------------------

return p