Jump to content

Module:Icon

From The Petit Planet Wiki

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

--- A library that other modules can use to create icon images with automatic prefix/suffix settings for items, characters, and weapons.
--  (Other icon types must have their types/suffixes specified manually.)
--
--  '''Note:''' this module is (currently) NOT related to [[Template:Icon]].
--
--  @script Icon

local TemplateData = require('Module:TemplateData')

local p = {}

local FOOD_PREFIXES = {
	['Suspicious '] = 1,
	['Delicious '] = 1,
}

-- icon arg defaults for template data
-- uses list format to ensure that the order is the same when added to another list of args using `p.addIconArgs`
local ICON_ARGS = {
	{name='name',
		label='Name',
		description='The name of the {labelPrefix}image.',
		example={'Furnishing Blueprint', 'Fruit Paste Bait', 'Anemo'}
	},
	{name='link', displayType='wiki-page-name', defaultFrom='name',
		label='Link',
		description='The page that the {labelPrefix}image links to.'
	},
	{name='extension', alias='ext', default='png',
		label='File Extension',
		description='The file extension for the {labelPrefix}image.',
		example={'png', 'jpg', 'jpeg', 'gif'},
	},
	{name='type', displayDefault='determined based on {labelPrefix}Name; "Item" if unknown',
		label='File Prefix',
		description='The file prefix to prepend to {labelPrefix}Name.',
		example={'Item', 'Weapon', 'Character', 'Icon'}
	},
	{name='suffix', displayDefault='determined based on {labelPrefix}Name/{labelPrefix}Type',
		label='File Suffix',
		description='The file suffix to append to {labelPrefix}Name.',
		example={'Icon', 'Thumb', '2nd', 'White'}
	},
	{name='size', default='20',
		label='Size',
		description='The height and width of the {labelPrefix}image in pixels. A single number will be used for both height and width; to specify them separately, use \'wxh\' format.',
		example={'20', '20x10', '20x', 'x20'}
	},
	{name='alt', defaultFrom='name', displayDefault='determined based on {labelPrefix}Name, and updated based on {labelPrefix}Character Outfit and {labelPrefix}Weapon Ascension Phase',
		label='Alt Text',
		description='The alt text of the {labelPrefix}image.',
	},
	{name='outfit', alias='o',
		label='Character Outfit',
		description='Name of the Outfit equipped on the character in the {labelPrefix}image. (Ignored if not a character.)',
		example='Sea Breeze Dandelion'
	},
	{name='ascension', alias='a', type='number', default=0,
		label='Weapon Ascension Phase',
		description='Ascension Phase of the weapon in the {labelPrefix}image. (Weapon image changes for ascension 2 or higher. Ignored if not a weapon.)',
		example={'1', '2'}
	},
}

--- Returns a table version of the input.
--  @param v A table or an item that belongs in a table.
--  @return {table}
local function ensureTable(v)
	if type(v) == 'table' then return v end
	return {v}
end

--- A special constant that can be used as a value in the `overrides` parameter for `addIconArgs`
--  to disable the given base config or TemplateData key.
p.EXCLUDE = {};

--- Copy entries from b into a, excluding values that are `p.EXCLUDE`.
--  @param {table} a Table to insert into.
--  @param {table} b Table to copy from.
local function copyTable(a, b)
	for k, v in pairs(b) do 
		if v == p.EXCLUDE then
			v = nil
		end
		a[k] = v
	end
end

--- A function that other modules can use to append Icon arguments to their template data.
--  @param {TemplateData#ArgumentConfigList} base The template data to append to.
--         If any configs have a `name` that matches an Icon argument's (with `namePrefix`)
--         and a property `placeholderFor` set to `'Module:Icon'`, then the
--         corresponding Icon argument will replace it instead of being appended.
--         (This allows argument order to be changed for [[Module:TemplateData]]'s
--         documentation generation.)
--  @param {string} namePrefix A string to prepend to all parameter names.
--  @param {string} labelPrefix A string to prepend to all parameter labels
--    (the names displayed in documentation).
--  @param {TemplateData#ArgumentConfigMap} overrides A table of overrides to configure the template data.
--    Keys should be parameter names, and values are tables of TemplateData
--    configuration objects to merge with the default configuration.
--    These configuration objects can use @{Icon.EXCLUDE} as values to disable
--    the corresponding default configuration key (e.g., to disable a default value
--    or alias without adding a new one).
function p.addIconArgs(baseConfigs, namePrefix, labelPrefix, overrides)
	-- preprocessing: save position of each placeholder config
	local argLocations = {}
	for i, config in ipairs(baseConfigs) do
		if config.placeholderFor == 'Module:Icon' then
			argLocations[config.name] = i
		end
	end
	
	-- main loop: prepend namePrefix and labelPrefix where needed
	-- and add configs to baseConfigs
	for _, config in ipairs(ICON_ARGS) do
		local override = overrides[config.name]
		if override ~= false and override ~= p.EXCLUDE then
			local c = {}
			copyTable(c, config)
			c.name = namePrefix .. c.name
			c.label = labelPrefix .. c.label
			if c.defaultFrom then
				c.defaultFrom = namePrefix .. c.defaultFrom
			end
			if c.alias then
				local aliases = {}
				for _, a in ipairs(ensureTable(c.alias)) do
					table.insert(aliases, namePrefix .. a)
				end
				c.alias = aliases
			end
			
			if override then
				copyTable(c, override)
			end
			
			if c.description then
				c.description = c.description:gsub('{labelPrefix}', labelPrefix)
			end
			if c.displayDefault then
				c.displayDefault = c.displayDefault:gsub('{labelPrefix}', labelPrefix)
			end
			
			local placeholderIndex = argLocations[c.name]
			if placeholderIndex then
				baseConfigs[placeholderIndex] = c
			else
				table.insert(baseConfigs, c)
			end
		end
	end
end

local function extendTable(base, extra)
	out = {}
	setmetatable(out, {
		__index = function(t, key)
			-- be defensive: base or extra may be nil
			local val
			if base ~= nil then
				val = base[key]
			end
			if val ~= nil then
				return val
			end
			if extra ~= nil then
				return extra[key]
			end
			return nil
		end
	})
	return out
end

local neighbors = mw.loadData('Module:Card/neighbors')
local ALL_DATA = {-- list of {type, data} in the order that untyped items should get checked
	{'Neighbor', neighbors},
	{'Creature', mw.loadData('Module:Card/creatures')},
	{'Dish', mw.loadData('Module:Card/dishes')},
	{'Plant', mw.loadData('Module:Card/plants')},
	{'Furniture', mw.loadData('Module:Card/furnitures')},
	{'Outfit', mw.loadData('Module:Card/outfits')},
	{'Item', extendTable(characters, mw.loadData('Module:Card/items'))} -- allow characters to be items
}

--- A basic image type with filename prefix/suffix support.
--  Extends @{TemplateData#TableWithDefaults} with image-related properties,
--  allowing default property values to be customized after creation.
--
--  Image objects are created by @{Icon.createIcon}.
--  Use @{Image:buildString} to convert to wikitext.
--  @type Image
local IMAGE_ARGS = {
	size = {default=''},
	name = {default='', alias=1},
	prefix = {default=''},
	prefixSeparator = {default=' '},
	suffix = {default=''},
	suffixSeparator = {default=' '},
	extension = {default='png', alias='ext'},
	link = {defaultFrom='name'},
	alt = {defaultFrom='name'},
}

local function buildImageFullPrefix(img)
	local prefix = img.prefix
	if prefix ~= '' then
		return prefix .. img.prefixSeparator
	end
	return ''
end
local function buildImageFullSuffix(img)
	local suffix = img.suffix
	if suffix ~= '' then
		return img.suffixSeparator .. suffix
	end
	return ''
end
local function buildImageFilename(img)
	if img.name == '' then return '' end
	return 'File:' .. buildImageFullPrefix(img) .. img.name .. buildImageFullSuffix(img) .. '.' .. img.extension
end
local function buildImageSizeString(img)
	local size = img.size
	if size == nil or size == '' then
		return ''
	end
	size = tostring(size)
	if size:find('x', 1, true) then
		return size .. 'px'
	end
	return size .. 'x' .. size .. 'px'
end
--[[
Returns wikitext that will display this image.
@return {string} Wikitext
@function Image:buildString
--]]
local function buildImageString(img)
	local filename = buildImageFilename(img)
	if filename == '' then return '' end
	return '[[' .. filename .. '|' .. buildImageSizeString(img) .. '|link=' .. img.link .. '|alt=' .. img.alt .. ']]'
end

--[[
Creates an `Image` object with the given properties.
@param {table} args A table of `Image` properties that to assign to the new `Image`.
@see @{Image} for property documentation
@return {Image} The new `Image` object.
@constructor
--]]
local function createImage(args)
	local out
	if type(args) == 'table' then
		out = TemplateData.processArgs(args, IMAGE_ARGS, {preserveDefaults=true})
	else
		out = {name = args}
	end
	-- add buildString function to image directly, not to image.nonDefaults
	
	rawset(out, 'buildString', buildImageString)
	return out
end

--- The name of the image. Used to determine filename.
--  @property {string} Image.name

--- Maximum image size using wiki image syntax.
--  Unlike regular wiki image syntax, a single number specifies both height and width.
--  @property {string} Image.size

--- Image file prefix.
--  @property {string} Image.prefix

--- Appended to `prefix` if `prefix` is not empty. Defaults to a single space.
--  @property[opt] {string} Image.prefixSeparator

--- Image file suffix.
--  @property {string} Image.suffix

--- Prepended to `suffix` if `suffix` is not empty. Defaults to a single space.
--  @property {string} Image.suffixSeparator

--- Image file extension. (Alias: `ext`.)
--  @property {string} Image.extension

--- Image link. Also sets title (hover tooltip). Defaults to `name`.
--  @property {string} Image.link

--- Image alt text. Defaults to `name`.
--  @property {string} Image.alt


--- A helper function for other modules to get icon args with a prefix from the given table.
--  @param {table} args A table containing icon args (and probably other args).
--  @param {string} prefix Prefix for keys to use when looking up entries from `args`.
--    If not specified, no prefix is used.
--  @return {table} A table containing only the extracted args, with the prefix removed from its keys.
function p.extractIconArgs(args, prefix)
	prefix = prefix or ''
	local out = TemplateData.createObjectWithDefaults()
	for _, config in pairs(ICON_ARGS) do
		out.defaults[config.name] = args.defaults[prefix .. config.name]
		out.nonDefaults[config.name] = args.nonDefaults[prefix .. config.name]
	end
	return out
end

--- A helper function that removes one of the given prefixes from the given string.
--  @param {string} str The string from which to strip a prefix
--  @param {table} prefixes A table whose keys are the possible prefixes to strip from `str`
--  @return {string} The string with the prefix removed.
--  @return[opt] {string} The prefix that was removed, or `nil` if no prefix was found.
function p.stripPrefixes(str, prefixes)
	for prefix, _ in pairs(prefixes) do
		if string.sub(str, 1, string.len(prefix)) == prefix then
			return string.sub(str, string.len(prefix) + 1), prefix
		end
	end
	return str
end

--[[
    The main entry point for other modules.
    Creates and returns an Image object with the given arguments.
    Infers `args.type` if `args.name` matches a known item/character/weapon.
    @param {table} args Table containing the arguments:
    @param[opt] {string} args.name The name of the image.
                         Used to determine file name and set the default link and alt text.
                         If not specified, the output `Image` will produce empty wikitext.
    @param[opt] {string} args.link Image link. Also sets title (hover tooltip).
                         Defaults to `name`.
    @param[opt] {string} args.extension (alias: `ext`) Image file extension.
                         Defaults to 'png'.
    @param[opt] {string} args.type Image type. Used to determine file prefix and default file suffix.
                         If not specified, type will be inferred if `args.name`
                         is a known item; otherwise, defaults to 'Item'.
    @param[opt] {string} args.suffix File suffix override.
    @param[opt] {string} args.size Maximum image size using wiki image syntax.
                         Unlike regular wiki image syntax, specifying a single number will
                         set both height and width.
    @param[opt] {string} args.alt Image alt text. Defaults to `args.name`, but
                         also changes with `args.outfit` or `args.ascension`.
    @param[opt] {string} args.outfit Character outfit. Only applies to character images.
    @param[opt] {number} args.ascension Weapon ascension level. Only applies to weapon images.
    @param[opt] {string} prefix Prefix to use when looking up arguments in `args`.
    @return {Image} The image for given arguments.
      Use `Image:buildString()` to get the wikitext for the image.
    @return {string} The image type.
    @return {TableWithDefaults} Associated data for given item (e.g., rarity, display name).
--]]
function p.createIcon(args, prefix)
	if prefix then
		args = p.extractIconArgs(args, prefix)
	end
	args = TemplateData.processArgs(args, ICON_ARGS, {preserveDefaults=true})
	local baseName, prefix = p.stripPrefixes(args.name or '', FOOD_PREFIXES)
	
	-- determine type by checking for data
	local image_type, data
	for i, v in ipairs(ALL_DATA) do
		image_type = v[1]
		if args.type == nil or args.type == image_type then
			data = v[2][baseName]
			if data then break end
		end
	end
	image_type = args.type or image_type -- in case args.type isn't in ALL_DATA (e.g., Enemy, Wildlife)
	
	local image = createImage(args)
	image.suffix = 'Icon'
	image.defaults.suffix = 'Icon'
	
    return image, image_type, data
end

return p