Module:Arkitexure

From ARK Wiki
Jump to navigation Jump to search
Template-info.png Documentation

Example

Lua error in Module:Arkitexure/test at line 9: attempt to call field 'createInfobox' (a nil value).


local Utility = require( 'Module:Utility' )
local ItemList = require( 'Module:ItemList' ).create


-- #region Class abstraction
local function Class( constructor, parent )
    local methods
    if parent ~= nil then
        methods = Utility.deepcopy( parent.methods )
    else
        methods = {}
    end
    if constructor == nil and parent ~= nil then
        constructor = parent.constructor
    end
	local class = {
        constructor = constructor,
        parent = parent,
        methods = methods,
        new = function ( self, ... )
            local inst = { mt = {
                class = self,
                __index = self.methods,
                __tostring = self.methods.toString
            } }
            setmetatable( inst, inst.mt )
            if self.constructor ~= nil then
                self.constructor( inst, unpack( {...} ) )
            end
            return inst
        end
	}
    setmetatable( class, { __call = class.new } )
    return class
end
-- #endregion


-- Helper function to prepend or append an element
local function pushItem(t, i, first)
	if first == false then
		table.insert(t, 1, i)
	else
		table.insert(t, i)
	end
    return i
end


-- Helper function to convert objects in array to strings and concatenate them
local function concatStringLikeTable(t)
	local tmp = {}
	for _, v in ipairs(t) do
		tmp[#tmp+1] = tostring(v)
	end
	return table.concat(tmp)
end


-- #region Component
local Component = Class( function ( self, parent )
    self.parent = parent
end )
    function Component.methods.addCategory( self, name, sortName )
        self.parent:addCategory( name, sortName )
        return self
    end

    function Component.methods.fillCargoField( self, name, value )
        self.parent:fillCargoField( name, value )
        return self
    end
    Component.methods.cargo = Component.methods.fillCargoField

    function Component.methods.done( self )
        return self.parent
    end
-- #endregion


-- #region CargoFieldInfo
local CargoFieldInfo = Class( function ( self, name )
    self[1] = name
end )
    function CargoFieldInfo.methods.adapt( self, value )
        return tostring( value )
    end

CargoFieldInfo.String = CargoFieldInfo
CargoFieldInfo.Bool = CargoFieldInfo
CargoFieldInfo.Number = CargoFieldInfo
CargoFieldInfo.List = CargoFieldInfo
-- #endregion


-- #region ServiceWiring
local ServiceWiring = Class( function ( self )
    self.categories = {}
    self.enableCargo = false
    self.enableCategories = mw.ext.VariablesLua.var('NoAutoCategories') ~= '1'
    self.cargoData = {}
    self.defaultCargoTable = nil
end )
    function ServiceWiring.methods.addCategory( self, name, sortName )
        table.insert(self.categories, string.format('[[Category:%s|%s]]', name, sortName or name))
        return self
    end

    function ServiceWiring.methods.setCargoEnabled( self, value )
        self.enableCargo = value
        return self
    end

    function ServiceWiring.methods.attachCargoTable( self, tableName )
        self.cargoData[tableName] = {}
        return self
    end

    function ServiceWiring.methods.attachCargoTableDefault( self, tableName )
        self:attachCargoTable( tableName )
        self.defaultCargoTable = tableName
        return self
    end

    function ServiceWiring.methods.fillCargoField( self, tableNameOrName, nameOrValue, valueOrNil )
        local tableName = tableNameOrName
        local field = nameOrValue
        local value = valueOrNil
        if valueOrNil == nil then
            value = key
            field = tableName
            tableName = self.defaultCargoTable
        end

        value = field:adapt( value )
        if value == nil then
            value = ''
        end

        self.cargoData[tableName][field[1]] = value
        return self
    end

    function ServiceWiring.methods.commitCargo( self )
        local sum = {}

        -- TODO: rewrite to active execution
        for tableName, values in pairs( self.cargoData ) do
            if #values > 0 then
                table.insert( sum, '{{#cargo_store:\n_table = ' .. tableName )
                for key, value in pairs( values ) do
                    if value == nil then
                        value = ''
                    elseif type( value ) == 'table' then
                        value = table.concat( value, ';;' )
                    end
                    table.insert( sum, '|' .. key .. '=' .. tostring( value ) )
                end
                table.insert( sum, '}}' )
            end
        end

        return table.concat( sum )
    end

    function ServiceWiring.methods.toString( self )
        return self.enableCategories and table.concat( self.categories ) or ''
    end
-- #endregion


-- Forward declarations
local Infobox
local Module
local Unit
local UnitRow
local KVUnitRow
local ItemTypeUnitRow
local Cell


-- #region Infobox
Infobox = Class( function ( self, config )
    ServiceWiring.constructor( self )

    self.config = config or {}
    self.modules = {}

    assert( self.config.title ~= nil, 'infobox header property "title" must not be null' )
    self.getHeader()
end, ServiceWiring )
    function Infobox.methods.module( self, last )
    	return self:insertModule( Module( self ), last )
    end

    function Infobox.methods.insertModule( self, instance, last )
    	return pushItem( self.modules, instance, last )
    end

    function Infobox.methods.getHeader( self, last )
        if self._modHeader then
            return self._modHeader
        end

    	self._modHeader = self:module( false )
    	-- Display a title box with a creature head
    	if self.config.withDinoHead then
    		self._modHeader
    			:unit()
    				:row()
    					:cell { Cell.X2W25, Cell.Left }
    						:text( string.format('[[File:%s.png|43px|alt=]]', self.config.title) )
    					:done()
    					:cell { Cell.X2W75, Cell.Right }
    						:text( self.config.title )
    					:done()
    				:done()
    			:done()
    	else
    		self._modHeader
    			:unit()
    				:row()
    					:cell { Cell.Single, Cell.Masthead }
    						:text( self.config.title )
    					:done()
    				:done()
    			:done()
    	end

        return self._modHeader
    end

    function Infobox.methods.toString( self )
    	return string.format( '<div class="info-arkitex info-framework">%s</div>%s%s',
            concatStringLikeTable( self.modules ), ServiceWiring.methods.toString( self ) )
    end

    function Infobox.methods.done( self )
    	return self:toString()
    end
-- #endregion


-- #region Module
Module = Class( function ( self, parent )
    self.parent = parent
    self.units = {}
end, Component )
    function Module.methods.appendUnit( self, unit )
    	table.insert( self.units, unit )
    	return unit
    end


    function Module.methods.unit( self, caption )
    	return self:appendUnit( Unit( self, caption ) )
    end


    function Module.methods.newUnit(self, cls, ...)
        return self:appendUnit(cls(self, unpack( {...} )))
    end


    -- Module to HTML converter
    function Module.methods.toString( self )
    	return string.format( '<div class="info-arkitex info-module">%s</div>', concatStringLikeTable( self.units ) )
    end
-- #endregion


-- #region Unit
Unit = Class( function ( self, parent, caption )
    self.parent = parent
    self.caption = caption
    self.rows = {}
end, Component )
    function Unit.methods.row( self )
    	return self:appendRow( UnitRow( self ) )
    end

    -- Name + Value unit row constructor
    function Unit.methods.rowKV( self, name, value )
    	return self:appendRow( KVUnitRow( self, name, value ) )
    end

    -- Item type row constructor
    function Unit.methods.rowItemType( self, itemType, itemCategory )
    	return self:appendRow( ItemTypeUnitRow( self, itemType, itemCategory ) )
    end


    function Unit.methods.appendRow(self, instance)
    	table.insert( self.rows, instance )
    	return instance
    end

    -- Module to HTML converter
    function Unit.methods.toString(self)
    	local captionHtml = ''
    	if self.caption then
    		captionHtml = string.format( '<div class="info-unit-caption">\'\'\'%s\'\'\'</div>', self.caption )
    	end

    	return string.format( '<div class="info-arkitex info-unit">%s%s</div>', captionHtml, concatStringLikeTable( self.rows ) )
    end
-- #endregion


-- #region Unit row
UnitRow = Class( function ( self, parent )
    self.parent = parent
    self.cells = {}
end, Component )
    function UnitRow.methods.cell(self, args)
	    return self:insertCell( Cell( self, args ) )
    end

    function UnitRow.methods.insertCell(self, instance)
	    table.insert( self.cells, instance )
	    return instance
    end

    function UnitRow.methods.newCell(self, cls, ...)
        return self:insertCell(cls(self, unpack( {...} )))
    end

    function UnitRow.methods.toString(self)
	    return string.format( '<div class="info-arkitex info-unit-row">%s</div>', concatStringLikeTable( self.cells ) )
    end
-- #endregion


-- #region Key-value unit row
KVUnitRow = Class( function ( self, parent, name, value )
    self.parent = parent
    self.mName = nil
    self.mValue = nil
    self:name(name):value(value)
end, UnitRow )
    -- Name setter
    function KVUnitRow.methods.name(self, name)
	    assert(name ~= nil, 'infobox named row name must not be null')
	    self.mName = name
	    return self
    end

    -- Value setter
    function KVUnitRow.methods.value(self, value)
	    if value == '' then
		    value = nil
	    end
	    self.mValue = value
	    return self
    end

    function KVUnitRow.methods.fillCargoField( self, name, value )
        return Component.methods.fillCargoField( self, name, value or self.mValue )
    end
    KVUnitRow.methods.cargo = KVUnitRow.methods.fillCargoField

    -- Unit row to HTML converter
    function KVUnitRow.methods.toString(self)
    	if self.mValue == nil then
    		return ''
    	end
    	return string.format( '<div class="info-arkitex info-unit-row">%s%s</div>',
    						  tostring( Cell( self, { Cell.Left, Cell.X2W40 } ):text( self.mName ) ),
    						  tostring( Cell( self, { Cell.Right, Cell.X2W60 } ):text( self.mValue ) ) )
    end
-- #endregion


-- #region Boolean key-value unit row
BoolKVUnitRow = Class( nil, KVUnitRow )
    function BoolKVUnitRow.methods.fillCargoField( self, name, value )
        return Component.methods.fillCargoField( self, name, value or self.mValue )
    end
    BoolKVUnitRow.methods.cargo = BoolKVUnitRow.methods.fillCargoField

    -- Unit row to HTML converter
    function BoolKVUnitRow.methods.toString(self)
    	if self.mValue == nil then
    		return ''
    	end
        local checkmarkValue = 'Unknown'
        if self.mValue == true then
            checkmarkValue = 'Yes'
        elseif self.mValue == false then
            checkmarkValue = 'No'
        end
    	return string.format(
            '<div class="info-arkitex info-unit-row">%s%s</div>',
    		tostring( Cell( self, { Cell.Left, Cell.X2W40 } ):text( self.mName ) ),
    		tostring( Cell( self, { Cell.Right, Cell.X2W60 } ):text(
                mw.getCurrentFrame():expandTemplate { title = 'Checkmark', args = { checkmarkValue } }
            ) )
        )
    end
-- #endregion


-- #region Common item type unit row
ItemTypeUnitRow = Class( function ( self, parent, itemType, itemCategory )
    self.parent = parent
    self.mName = 'Type'
    self.mValue = string.format( '[[:Category:%s|%s]]', itemCategory, itemType )
    self:addCategory( itemCategory )
end, KVUnitRow )
-- #endregion


-- #region Cell
Cell = Class( function ( self, parent, args )
    self.parent = parent
    self.mText = args.text
    self.classes = args
end, Component )
-- Area division
Cell.X1W100 = 'info-X1-100'
Cell.X2W25 = 'info-X2-25'
Cell.X2W40 = 'info-X2-40'
Cell.X2W50 = 'info-X2-50'
Cell.X2W60 = 'info-X2-60'
Cell.X2W75 = 'info-X2-75'
Cell.X3W33 = 'info-X3-33'
-- Directional
Cell.Left = 'info-arkitex-left'
Cell.Right = 'info-arkitex-right'
-- Special
Cell.Masthead = 'info-masthead'
Cell.Column = 'info-column'
Cell.ForceBackground = 'info-with-background'
    -- Text constructor
    function Cell.methods.text(self, text)
	    self.mText = text
	    return self
    end

    -- Cell to HTML converter
    function Cell.methods.toString(self)
    	local text = self.mText
    	if type( text ) == 'function' then
    		text = text()
    	end

    	if text == nil or text == '' then
    		return ''
    	end

        if type( text ) == 'table' then
            text = ItemList( text )
        elseif type( text ) == 'number' then
    		text = mw.getContentLanguage():formatNum( text )
    	end

    	return string.format( '<div class="info-arkitex %s">%s</div>', table.concat( self.classes, ' ' ), text )
    end
-- #endregion


-- Export constructors
return {
    Class = Class,
    Component = Component,
    CargoFieldInfo = CargoFieldInfo,
    ServiceWiring = ServiceWiring,
    Infobox = Infobox,
    Module = Module,
    Unit = Unit,
    UnitRow = UnitRow,
    KVUnitRow = KVUnitRow,
    BoolKVUnitRow = BoolKVUnitRow,
    ItemTypeUnitRow = ItemTypeUnitRow,
    Cell = Cell,
}