A large portion of relevant modules/templates have now been switched to cargo. Various usages of SMW throughout the wiki need to be replaced by the new functions, in particular item tables. If some pages do not show up but contain no errors, please null-edit them. To see how you can help with the port check out Path_of_Exile_Wiki:To-do_list/SMW_migration (and leave a comment on the talk page if you have questions).

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