Module:Class

From Liquipedia Commons Wiki
Module documentation[view] [edit] [history] [purge]

Enables equals treatment of LUA and wikicode users for modules. Using this module means you will no longer need to have two different entry points for each function, both wikicode and LUA can call the same function.

Usage[edit]

Usage of this module is simple. See the following example:

local Class = require('Module:Class')                <!--- Add the import
local Cool = Class.new()                             <!--- Create a class

function Cool.doCoolStuff(a, b)                      <!--- Note the colon symbol!
  return a + b
end

return Cool.export()                            <!--- Export the table

Now, doCoolStuff will be callable from both LUA and wikicode with the same arguments.

To repeat, you can now do Cool:doCoolStuff(2, 3) while also being able to do {{#invoke:SomeModule|doCoolStuff|2|3}} and both will return 5.

Named arguments[edit]

When named arguments are provided, things change slightly. Named arguments are provided as a lua table, and are always the first argument. So, example:

local Class = require('Module:Class')                <!--- Add the import
local Cool = Class.new()                             <!--- Create a class

function Cool.doCoolStuff(args, a, b)                <!--- Named args are now the first argument!
  return a + b + args.c
end

return Cool.export()                            <!--- Export the table

You can now do Cool:doCoolStuff(2, 3, {c = 5}) while also being able to do {{#invoke:SomeModule|doCoolStuff|2|3|c=5}} and both will return 10.

Advanced[edit]

Module:Class supports advanced usage scenarios via export's options parameter. These options are passed straight to Module:Arguments, which allows you to specify such things as frame inheritance, trimming functionality, and more. For details on this, see Wikipedia's documentation.

API[edit]

Programmatic name: Class

export(class: table, options: table) → class

Adjusts the table given so that its public functions (the ones not prefixed using an underscore) are usable from both LUA and wikicode and returns the adjusted table. Note: you can not create underscored versions of your public versions, please use a different name instead (with underscore if desired). So no copy() and _copy() in the same Module. The options arguments allows for providing arguments to Module:Arguments's getArgs.


new(base: class, init: function) → class

Creates a new class, by setting the proper metadata. If base is given, that will be used as base class. When init is supplied with a function, it can be used as a constructor. If only init is given, the function will still work.


See all our documentation here.


---
-- @Liquipedia
-- wiki=commons
-- page=Module:Class
--
-- Please see https://github.com/Liquipedia/Lua-Modules to contribute
--

---
-- @author Vogan for Liquipedia
--

local Arguments = require('Module:Arguments')

local Class = {}

Class.PRIVATE_FUNCTION_SPECIFIER = '_'

---@class BaseClass
---@operator call:self
---@field is_a fun(self, BaseClass):boolean

function Class.new(base, init)
	local instance = {}

	if not init and type(base) == 'function' then
		init = base
		base = nil
	elseif type(base) == 'table' then
		for index, value in pairs(base) do
			instance[index] = value
		end
		instance._base = base
	end

	instance.__index = instance

	local metatable = {}

	metatable.__call = function(class_tbl, ...)
		local object = {}
		setmetatable(object, instance)

		instance.init(object, ...)

		return object
	end

	instance.init = function(object, ...)
		if base then
			base.init(object, ...)
		end
		if init then
			init(object, ...)
		end
	end

	instance.export = function(options)
		return Class.export(instance, options)
	end

	instance.is_a = function(self, class)
		local m = getmetatable(self)
		while m do
			if m == class then
				return true
			end
			m = m._base
		end
		return false
	end
	setmetatable(instance, metatable)
	return instance
end

---@generic T:table
---@param class T
---@param options ?table
---@return T
function Class.export(class, options)
	for name, f in pairs(class) do
		-- We only want to export functions, and only functions which are public (no underscore)
		if (
			type(f) == 'function' and
				(not string.find(name, Class.PRIVATE_FUNCTION_SPECIFIER))
		) then
			class[name] = Class._wrapFunction(class[name], options)
		end
	end
	return class
end

local Table = {}

-- Duplicate Table.isNotEmpty() here to avoid circular dependencies with Table
function Table.isNotEmpty(tbl)
	-- luacheck: push ignore (Loop can be executed at most once)
	for _ in pairs(tbl) do
		return true
	end
	-- luacheck: pop
	return false
end

---
-- Wrap the given function with an argument parses so that both wikicode and lua
-- arguments are accepted
--
function Class._wrapFunction(f, options)
	options = options or {}
	local alwaysRewriteArgs = options.trim
		or options.removeBlanks
		or options.valueFunc ~= nil

	return function(...)
		-- We cannot call getArgs with a spread operator when these are just lua
		-- args, so we need to wrap it
		local input = {...}

		local frame = input[1]
		local shouldRewriteArgs = alwaysRewriteArgs
			or (
				#input == 1
					and type(frame) == 'table'
					and type(frame.args) == 'table'
			)

		if shouldRewriteArgs then
			local namedArgs, indexedArgs = Class._frameToArgs(frame, options)
			if namedArgs then
				return f(namedArgs, unpack(indexedArgs))
			else
				return f(unpack(indexedArgs))
			end
		else
			return f(...)
		end
	end
end

--[[
Translates a frame object into arguments expected by a lua function.
]]
function Class._frameToArgs(frame, options)
	local args = Arguments.getArgs(frame, options)

	-- getArgs adds a metatable to the table. This breaks unpack. So we remove it.
	-- We also add all named params to a special table, since unpack removes them.
	local indexedArgs = {}
	local namedArgs = {}
	for key, value in pairs(args) do
		if type(key) == 'number' then
			indexedArgs[key] = value
		else
			namedArgs[key] = value
		end
	end

	return (Table.isNotEmpty(namedArgs) and namedArgs or nil), indexedArgs
end

return Class