Deprecated: ScribuntoContent overrides fillParserOutput which was deprecated in MediaWiki 1.38. [Called from MediaWiki\Content\Renderer\ContentRenderer::getParserOutput in /var/www/html/includes/content/Renderer/ContentRenderer.php at line 47] in /var/www/html/includes/debug/MWDebug.php on line 381

Deprecated: Use of AbstractContent::getParserOutput was deprecated in MediaWiki 1.38. [Called from ContentHandler::callDeprecatedContentGPO in /var/www/html/includes/content/ContentHandler.php at line 1883] in /var/www/html/includes/debug/MWDebug.php on line 381

Deprecated: Use of ParserOutput::addModuleStyles with non-array argument was deprecated in MediaWiki 1.38. [Called from Capiunto\LuaLibrary::addResourceLoaderModules in /var/www/html/extensions/Capiunto/includes/LuaLibrary.php at line 35] in /var/www/html/includes/debug/MWDebug.php on line 381
Module:Ships: Difference between revisions - ΔV: Wiki

Module:Ships: Difference between revisions

From ΔV: Wiki
(Fixed processed cargo combined display, don't output nil for nils, allow columns to be selected for display)
(partially undid previous revision, added capability to use image and caption if so desired)
 
(19 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local p = {}
local p = {}
local d = require('Module:Data')
local d = require('Module:Data')
local dv = require('Module:Data/View')
local capiunto = require 'capiunto'


local processedCargoTypes = 6
local processedCargoTypes = 6
Line 6: Line 8:
p.view = {}
p.view = {}


 
p.view['Name'] = dv.title{'key', text='name'}
local makeGetter = function(path)
p.view['Make'] = dv.text{'make'}
    if type(path) == 'function' then
        return path
    elseif type(path) == 'string' then
        return d.path{path}
    elseif type(path) == 'table' then
        return d.path(path)
    end
end
 
 
local makeView = function(args)
    local path = table.remove(args, 1)
    local getter = makeGetter(path)
    local view = {}
    for k,v in pairs(args) do
        view[k] =v
    end
    return getter, view
end
 
local viewNotFound = function(name)
    return {
        heading = string.format("View %s is not defined", name),
        format = function(data) return nil end
    }
end
 
local title = function(args)
    local getter, view = makeView(args)
    view.format = function(data)
        local value = getter(data)
        if value then
            return '[[' .. getter(data) .. ']]'
        end
    end
    return view
end
 
local text = function(args)
    local getter, view = makeView(args)
    view.format = getter
    return view
end
 
 
local formatNum = function(n, ...)
    local unit = select(1, ...)
    if unit then
        return string.format('%s %s', mw.getContentLanguage():formatNum(n), unit)
    else
        return mw.getContentLanguage():formatNum(n)
    end
end
 
local number = function(args)
    local getter,view = makeView(args)
 
    view.format = function(data)
        local value = getter(data)
        if args.omitZero and value == 0 then
            return ''
        end
        if type(value) == 'number' then
              return formatNum(value, args.unit)
        end
        return value
    end
    view.sortType = 'number'
    view.sortValue = function(data)
        local value = getter(data)
        if type(value) == number then
            return tostring(value)
        end
    end
    return view
end
       
   
 
p.view['Name'] = title{'name'}
p.view['Make'] = text{'make'}


local nonZero = function(n)
local nonZero = function(n)
Line 96: Line 17:
p.view['Processed cargo'] = {
p.view['Processed cargo'] = {
     format = function(ship)
     format = function(ship)
         local pc  = formatNum(ship.processedCargo, 'kg')
         local pc  = dv.formatNum(ship.processedCargo, 'kg')
         local pcc = formatNum(ship.processedCargoCombined, 'kg (combined)')
         local pcc = dv.formatNum(ship.processedCargoCombined, 'kg (combined)')
         if nonZero(ship.processedCargo) and nonZero(ship.processedCargoCombined) then
         if nonZero(ship.processedCargo) and nonZero(ship.processedCargoCombined) then
             return pc .. ' + ' .. pcc
             return pc .. ' + ' .. pcc
Line 112: Line 33:
}
}


p.view['High-Stress'] = number{'highStress'}
p.view['Processed cargo (tons)'] = {
p.view['Low-Stress'] = number{'lowStress'}
    format = function(ship)
p.view['Drone hardpoints'] = number{'droneHardpoints'}
        local pc  = dv.formatNum(ship.processedCargo/1000, 't')
p.view['Docking bays'] = number{'dockingBays'}
        local pcc = dv.formatNum(ship.processedCargoCombined/1000, 't (combined)')
p.view['Crew'] = number{'crew'}
        if nonZero(ship.processedCargo) and nonZero(ship.processedCargoCombined) then
p.view['Engines'] = number{'engines'}
            return pc .. ' + ' .. pcc
p.view['Thrusters'] = number{'thrusters'}
        elseif nonZero(ship.processedCargoCombined) then
            return pcc
        elseif nonZero(ship.processedCargo) then
            return pc
        end
    end,
    sortType = 'number',
    sortValue = function(ship)
        return ship.processedCargo * processedCargoTypes + ship.processedCargoCombined
    end
}


p.view['Dry mass'] = number{'dryMass', unit='kg'}
p.view['High-Stress'] = dv.number{'highStress'}
p.view['Cargo bay'] = number{'cargoBay', unit='m^3'}
p.view['Low-Stress'] = dv.number{'lowStress'}
p.view['EMP shielding'] = number{'empShielding', unit='MJ', omitZero=true}
p.view['Drone hardpoints'] = dv.number{'droneHardpoints'}
p.view['New price'] = number{'newPrice', unit='E$'}
p.view['Docking bays'] = dv.number{'dockingBays'}
p.view['Crew'] = dv.number{'crew'}
p.view['Engines'] = dv.number{'engines'}
p.view['Thrusters'] = dv.number{'thrusters'}


p.view['Variant'] = text{'variant'}
p.view['Dry mass'] = dv.number{'dryMass', unit='kg'}


p.view['Dry mass (tons)'] = {
    format = function(ship)
        local pc  = dv.formatNum(ship.dryMass/1000, 't')
        if nonZero(ship.dryMass) then
            return pc
        elseif nonZero(ship.dryMass) then
            return pc
        end
    end,
    sortType = 'number',
    sortValue = function(ship)
        return ship.drymass
    end
}
p.view['Cargo bay'] = dv.number{'cargoBay', unit='m^3'}
p.view['EMP shielding'] = dv.number{'empShielding', unit='MJ', omitZero=true}
p.view['New price'] = dv.number{'priceNew', unit='E$'}
p.view['Stripped hull price'] = dv.number{'hullValue', unit='E$'}
p.view['Hull Width'] = dv.number{'width', unit='m'}
p.view['Hull Length'] = dv.number{'length', unit='m'}
p.view['Min. Cargo Bay Width'] = dv.number{'minBayWidth', unit='m'}
p.view['Variant'] = dv.text{'variant'}


for k,v in pairs(p.view) do
for k,v in pairs(p.view) do
Line 132: Line 91:
end
end


local displayValue = function(value)
p.infoboxRows = {
     if value == nil then
    'Make',
        return ''
    'High-Stress', 'Low-Stress', 'Drone hardpoints', 'Docking bays', 'Crew',
     end
    'Dry mass', 'Cargo bay', 'Processed cargo',
     return tostring(value)
    'New price',
end
    'Hull Length',
    'Hull Width',
    'Min. Cargo Bay Width'
}
 
p.infobox = function(f)
     local key = mw.text.trim(f.args[1])
    local data = mw.loadData('Module:Ships/Data')
     data = data[key]
 
     local cap = capiunto.create{title=p.view.Name.format(data)}


local displayTable = function(data, view, columns)
     if f.args.image then
     local views = {}
        cap:addImage(f.args.image, f.args.caption)
    for i,col in ipairs(columns) do
        view = p.view[col] or viewNotFound(col)
        table.insert(views, view)
     end
     end


    local result = {'{|', '|-'}
     for i,viewKey in ipairs(p.infoboxRows) do
     for j,view in ipairs(views) do
        local view = p.view[viewKey]
        table.insert(result, '!' .. displayValue(view.heading))
        local dataPoint = view.format(data)
    end
        -- Skip zeros
    for i,row in ipairs(data) do
        if view.sortType == 'number' then
        table.insert(result ,'|-')
            local sortValue = view.sortValue(data)
        for j,view in ipairs(views) do
            if sortValue == 0 then
            table.insert(result, '|' .. displayValue(view.format(row)))
                dataPoint = nil
        end
            end
        end
        if dataPoint then
            cap:addRow(view.heading, dataPoint)
        end
     end
     end
    table.insert(result,'|}')
     return cap
     return table.concat(result, '\n')
end
end


p.defaultColumns = {
p.defaultColumns = {
Line 164: Line 134:
     'High-Stress', 'Low-Stress', 'Drone hardpoints', 'Docking bays', 'Crew',
     'High-Stress', 'Low-Stress', 'Drone hardpoints', 'Docking bays', 'Crew',
     'Dry mass', 'Processed cargo',
     'Dry mass', 'Processed cargo',
     'Engines', 'EMP shielding', 'New price', 'Variant'
     'Engines', 'EMP shielding', 'New price',  
    'Hull Length',
    'Hull Width',
    'Min. Cargo Bay Width',
    'Variant'
}
}


p.list = function(f)
p.positionalArgs = function(f)
     local columns = {}
     local posArgs = {}
     for i,col in ipairs(f.args) do
     for i,posArg in ipairs(f.args) do
         table.insert(columns, col)
        -- positional arguments don't get trimmed
         table.insert(posArgs, mw.text.trim(posArg))
     end
     end
    return posArgs
end


p.list = function(f)
    local columns = p.positionalArgs (f)
     if table.getn(columns) == 0 then
     if table.getn(columns) == 0 then
         columns = p.defaultColumns
         columns = p.defaultColumns
     end
     end


     local data = mw.loadData('Module:Ships/Data')
     local data = mw.loadData('Module:Ships/Data')
Line 187: Line 167:
     })
     })


     return displayTable(data, p.view, columns)
     return dv.displayTable(data, p.view, columns)
end
end
p.variants = function(f)
    local posArgs = p.positionalArgs(f)
    local baseModel = table.remove(posArgs, 1)
    local columns = posArgs
    if table.getn(columns) == 0 then
        columns = p.defaultColumns
    end
    local data = mw.loadData('Module:Ships/Data')
    data = d.filter(data, function (ship)
        return ship.baseModel == baseModel
    end)
    data = d.sort(data, {
        d.on(d.path{'isVariant'}, d.asc),
        d.on(d.path{'hullValue'}, d.asc),
        d.on(d.path{'name'}, d.asc),
    })
    return dv.displayTable(data, p.view, columns)
end
p.minInfoboxRows = {
    'High-Stress', 'Low-Stress', 'Drone hardpoints', 'Docking bays', 'Crew',
    'Dry mass (tons)', 'Cargo bay', 'Processed cargo (tons)',
    'New price'
}
p.minInfobox = function(f)
    local key = mw.text.trim(f.args[1])
    local data = mw.loadData('Module:Ships/Data')
    data = data[key]
    local cap = capiunto.create{title=p.view.Name.format(data)}
    if f.args.image then
        cap:addImage(f.args.image, f.args.caption)
    end
    for i,viewKey in ipairs(p.minInfoboxRows) do
        local view = p.view[viewKey]
        local dataPoint = view.format(data)
        -- Skip zeros
        if view.sortType == 'number' then
            local sortValue = view.sortValue(data)
            if sortValue == 0 then
                dataPoint = nil
            end
        end
        if dataPoint then
            cap:addRow(view.heading, dataPoint)
        end
    end
    return cap
end


return p
return p

Latest revision as of 00:58, 19 November 2024

This ship module exists to present the data from Module:Ships/Data in tables of ships, ship variants, and ship info boxes.


Infobox

K37 TNTRL
K37-tntrl.png
Top-down view of the K37
Make Rusatom-Antonoff
High-Stress 1
Low-Stress 2
Crew 4
Dry mass 33,200 kg
Cargo bay 96 m^3
Processed cargo 7,000 kg
New price 365,999 E$
Hull Length 34.2 m
Hull Width 16 m
Min. Cargo Bay Width 2.4 m

Usage

{{#invoke:Ships|infobox|K37
|image=[[File:K37-tntrl.png|200px]]
|caption=Top-down view of the K37}}

The image and its caption are optional.

The infobox floats to the side of other content. Example output is to the right. The float is cleared here to keep it from being confused with the next section of the documentation.


Ship list

Usage

{{#invoke:Ships|list}}

Example output

Name High-Stress Low-Stress Drone hardpoints Docking bays Crew Dry mass Processed cargo Engines EMP shielding New price Hull Length Hull Width Min. Cargo Bay Width Variant
ND-LIS Kitsune 0 1 0 0 3 4,000 kg 18,000 kg (combined) 0 12.6 m 8 m 2.4 m
Cothon-212 0 4 0 0 3 83,700 kg 27,000 kg 1 100 MJ 54.4 m 28.9 m 3.6 m
Cothon-213 "Triplet" 0 4 0 0 3 84,700 kg 27,000 kg 3 54.4 m 28.9 m 3.6 m Triple main engines
Cothon-211 "Chonker" 0 4 0 0 3 83,700 kg 27,000 kg 1 100 MJ 56.3 m 28.9 m 4 m Larger excavator
Cothon-217 "Bender" 0 4 0 0 3 103,700 kg 108,000 kg (combined) 1 100 MJ 54.4 m 28.9 m 3.6 m Variable processed cargo storage
K37 TNTRL 1 2 0 0 4 33,200 kg 7,000 kg 1 365,999 E$ 34.2 m 16 m 2.4 m
KTA24 TNTRL 1 2 0 0 4 27,970 kg 3,000 kg 1 30.7 m 16 m 2.3 m Tug with angled reverse thrust
KX37 TNTRL 1 2 0 0 4 37,970 kg 10,000 kg 1 38.6 m 16 m 2.4 m Extended cargo hold
Runasimi KR37 TNTRL 1 2 0 0 6 47,500 kg 4,000 kg 1 200 MJ 34.2 m 16 m 2.2 m EMP shielded
K44 MHFTR Prototype 0 4 0 0 4 54,600 kg 14,000 kg 1 42 m 16 m 2.4 m Abandoned prototype
Eagle Prospector 0 2 2 0 6 27,500 kg 14,000 kg 2 2,539,999 E$ 50.1 m 14.6 m 4.8 m
Bald Eagle 0 2 2 0 6 29,500 kg 14,000 kg 4 10,000,000 E$ 33.2 m 14.6 m 4.9 m Racing
Peacock Prospector 0 2 2 0 4 27,500 kg 14,000 kg 2 50.1 m 14.6 m 4.8 m Luxury interior influences crew morale towards happy
Vulture Prospector 1 2 0 0 6 37,500 kg 14,000 kg 2 42.1 m 14.6 m 4.8 m Drone hardpoints replaced with a high-stress hardpoint
Pelican Prospector 0 2 2 0 6 62,000 kg 14,000 kg 2 56.4 m 14.6 m 4.8 m Larger cargo bay, grinders replaced with excavator
OCP-209 2 2 0 0 8 184,500 kg 50,000 kg (combined) 1 84.7 m 31.2 m 24.1 m
Antonoff-Titan K225 0 2 0 6 12 220,100 kg 7,000 kg 3 3,999,999 E$ 83.8 m 31.5 m 3.8 m
Antonoff-Titan K225-BB 0 2 0 2 12 190,100 kg 7,000 kg 3 83.8 m 31.5 m 3.8 m 4 docking bays removed to fit a much larger cargo bay
Antonoff-Titan K225 (modified) 0 2 0 0 12 250,100 kg 7,000 kg 9 100 MJ 83.8 m 31.5 m 3.8 m Custom research retrofit, replaced docking bays with torch fittings and a Faraday cage fitted to give EMP resistance
Elon Interstellar Model E 1 2 0 0 5 27,000 kg 11,000 kg 2 8,999,999 E$ 38.8 m 19.1 m 12.6 m

A list of columns to display can be specified as additional arguments

{{#invoke:Ships|list|Name
|Make
|Cargo bay
|Thrusters}}


Name Make Cargo bay Thrusters Stripped hull price
ND-LIS Kitsune Nakamura Dynamics 75 m^3 7 14,999 E$
Cothon-212 Conlido RVM 260 m^3 4 79,549 E$
Cothon-213 "Triplet" Conlido RVM 260 m^3 4 78,149 E$
Cothon-211 "Chonker" Conlido RVM 270 m^3 4 79,549 E$
Cothon-217 "Bender" Conlido RVM 260 m^3 4 102,049 E$
K37 TNTRL Rusatom-Antonoff 96 m^3 8 88,949 E$
KTA24 TNTRL Rusatom-Antonoff / Triskellion-Armstrong 58 m^3 8 87,853 E$
KX37 TNTRL Rusatom-Antonoff 110 m^3 8 140,249 E$
Runasimi KR37 TNTRL Rusatom-Antonoff / Runasimi 100 m^3 8 207,299 E$
K44 MHFTR Prototype Rusatom-Antonoff 136 m^3 8 523,200 E$
Eagle Prospector Mitsudaya-Starbus 125 m^3 8 184,099 E$
Bald Eagle Custom-built 40 m^3 4 297,499 E$
Peacock Prospector Mitsudaya-Starbus 125 m^3 8 297,949 E$
Vulture Prospector Mitsudaya-Starbus / Conlido RVM 90 m^3 8 455,449 E$
Pelican Prospector Mitsudaya-Starbus / Titan Heavy Industries 240 m^3 8 860,449 E$
OCP-209 Obonto Microengineering 610 m^3 8 217,200 E$
Antonoff-Titan K225 Antonoff-Titan Heavy Industries 280 m^3 10 2,064,824 E$
Antonoff-Titan K225-BB Antonoff-Titan Heavy Industries 710 m^3 10 2,674,749 E$
Antonoff-Titan K225 (modified) Antonoff-Titan Heavy Industries, Custom Modification 280 m^3 10 5,174,749 E$
Elon Interstellar Model E Elon Interstellar 140 m^3 6 6,024,649 E$

Ship variants

Usage

{{#invoke:Ships|variants|K37}}

Example output

Name High-Stress Low-Stress Drone hardpoints Docking bays Crew Dry mass Processed cargo Engines EMP shielding New price Hull Length Hull Width Min. Cargo Bay Width Variant
K37 TNTRL 1 2 0 0 4 33,200 kg 7,000 kg 1 365,999 E$ 34.2 m 16 m 2.4 m
KTA24 TNTRL 1 2 0 0 4 27,970 kg 3,000 kg 1 30.7 m 16 m 2.3 m Tug with angled reverse thrust
KX37 TNTRL 1 2 0 0 4 37,970 kg 10,000 kg 1 38.6 m 16 m 2.4 m Extended cargo hold
Runasimi KR37 TNTRL 1 2 0 0 6 47,500 kg 4,000 kg 1 200 MJ 34.2 m 16 m 2.2 m EMP shielded
K44 MHFTR Prototype 0 4 0 0 4 54,600 kg 14,000 kg 1 42 m 16 m 2.4 m Abandoned prototype


A list of columns to display can be specified as additional arguments

{{#invoke:Ships|variants|K37
|Name|Make
|Crew|Dry mass|Cargo|Processed cargo|EMP shielding
|Variant}}
Name Make Crew Dry mass Cargo bay Processed cargo EMP shielding Variant
K37 TNTRL Rusatom-Antonoff 4 33,200 kg 96 m^3 7,000 kg
KTA24 TNTRL Rusatom-Antonoff / Triskellion-Armstrong 4 27,970 kg 58 m^3 3,000 kg Tug with angled reverse thrust
KX37 TNTRL Rusatom-Antonoff 4 37,970 kg 110 m^3 10,000 kg Extended cargo hold
Runasimi KR37 TNTRL Rusatom-Antonoff / Runasimi 6 47,500 kg 100 m^3 4,000 kg 200 MJ EMP shielded
K44 MHFTR Prototype Rusatom-Antonoff 4 54,600 kg 136 m^3 14,000 kg Abandoned prototype

local p = {}
local d = require('Module:Data')
local dv = require('Module:Data/View')
local capiunto = require 'capiunto'

local processedCargoTypes = 6

p.view = {}

p.view['Name'] = dv.title{'key', text='name'}
p.view['Make'] = dv.text{'make'}

local nonZero = function(n)
    return n and n ~= 0
end

p.view['Processed cargo'] = {
    format = function(ship)
        local pc  = dv.formatNum(ship.processedCargo, 'kg')
        local pcc = dv.formatNum(ship.processedCargoCombined, 'kg (combined)')
        if nonZero(ship.processedCargo) and nonZero(ship.processedCargoCombined) then
            return pc .. ' + ' .. pcc
        elseif nonZero(ship.processedCargoCombined) then
            return pcc
        elseif nonZero(ship.processedCargo) then
            return pc
        end
    end,
    sortType = 'number',
    sortValue = function(ship)
         return ship.processedCargo * processedCargoTypes + ship.processedCargoCombined
    end
}

p.view['Processed cargo (tons)'] = {
    format = function(ship)
        local pc  = dv.formatNum(ship.processedCargo/1000, 't')
        local pcc = dv.formatNum(ship.processedCargoCombined/1000, 't (combined)')
        if nonZero(ship.processedCargo) and nonZero(ship.processedCargoCombined) then
            return pc .. ' + ' .. pcc
        elseif nonZero(ship.processedCargoCombined) then
            return pcc
        elseif nonZero(ship.processedCargo) then
            return pc
        end
    end,
    sortType = 'number',
    sortValue = function(ship)
         return ship.processedCargo * processedCargoTypes + ship.processedCargoCombined
    end
}

p.view['High-Stress'] = dv.number{'highStress'}
p.view['Low-Stress'] = dv.number{'lowStress'}
p.view['Drone hardpoints'] = dv.number{'droneHardpoints'}
p.view['Docking bays'] = dv.number{'dockingBays'}
p.view['Crew'] = dv.number{'crew'}
p.view['Engines'] = dv.number{'engines'}
p.view['Thrusters'] = dv.number{'thrusters'}

p.view['Dry mass'] = dv.number{'dryMass', unit='kg'}

p.view['Dry mass (tons)'] = {
    format = function(ship)
        local pc  = dv.formatNum(ship.dryMass/1000, 't')
        if nonZero(ship.dryMass) then
            return pc
        elseif nonZero(ship.dryMass) then
            return pc
        end
    end,
    sortType = 'number',
    sortValue = function(ship)
         return ship.drymass
    end
}

p.view['Cargo bay'] = dv.number{'cargoBay', unit='m^3'}
p.view['EMP shielding'] = dv.number{'empShielding', unit='MJ', omitZero=true}
p.view['New price'] = dv.number{'priceNew', unit='E$'}
p.view['Stripped hull price'] = dv.number{'hullValue', unit='E$'}

p.view['Hull Width'] = dv.number{'width', unit='m'}
p.view['Hull Length'] = dv.number{'length', unit='m'}
p.view['Min. Cargo Bay Width'] = dv.number{'minBayWidth', unit='m'}

p.view['Variant'] = dv.text{'variant'}

for k,v in pairs(p.view) do
    v.heading = v.heading or k
end

p.infoboxRows = {
    'Make',
    'High-Stress', 'Low-Stress', 'Drone hardpoints', 'Docking bays', 'Crew',
    'Dry mass', 'Cargo bay', 'Processed cargo',
    'New price', 
    'Hull Length', 
    'Hull Width', 
    'Min. Cargo Bay Width'
}

p.infobox = function(f)
    local key = mw.text.trim(f.args[1])
    local data = mw.loadData('Module:Ships/Data')
    data = data[key]

    local cap = capiunto.create{title=p.view.Name.format(data)}

    if f.args.image then
        cap:addImage(f.args.image, f.args.caption)
    end

    for i,viewKey in ipairs(p.infoboxRows) do
        local view = p.view[viewKey]
        local dataPoint = view.format(data)
        -- Skip zeros
        if view.sortType == 'number' then
            local sortValue = view.sortValue(data)
            if sortValue == 0 then
                dataPoint = nil
            end
        end
        if dataPoint then
            cap:addRow(view.heading, dataPoint)
        end
    end
    return cap
end


p.defaultColumns = {
    'Name',
    'High-Stress', 'Low-Stress', 'Drone hardpoints', 'Docking bays', 'Crew',
    'Dry mass', 'Processed cargo',
    'Engines', 'EMP shielding', 'New price', 
    'Hull Length', 
    'Hull Width', 
    'Min. Cargo Bay Width',
    'Variant'
}

p.positionalArgs = function(f)
    local posArgs = {}
    for i,posArg in ipairs(f.args) do
        -- positional arguments don't get trimmed
        table.insert(posArgs, mw.text.trim(posArg))
    end
    return posArgs
end

p.list = function(f)
    local columns = p.positionalArgs (f)
    if table.getn(columns) == 0 then
        columns = p.defaultColumns
    end


    local data = mw.loadData('Module:Ships/Data')

    data = d.sort(data, {
        d.on(d.path{'baseModelData', 'hullValue'}, d.asc),
        d.on(d.path{'baseModelData', 'name'}, d.asc),
        d.on(d.path{'isVariant'}, d.asc),
        d.on(d.path{'hullValue'}, d.asc),
        d.on(d.path{'name'}, d.asc),
    })

    return dv.displayTable(data, p.view, columns)
end

p.variants = function(f)
    local posArgs = p.positionalArgs(f)
    local baseModel = table.remove(posArgs, 1)
    local columns = posArgs
    if table.getn(columns) == 0 then
        columns = p.defaultColumns
    end

    local data = mw.loadData('Module:Ships/Data')

    data = d.filter(data, function (ship)
         return ship.baseModel == baseModel
    end)

    data = d.sort(data, {
        d.on(d.path{'isVariant'}, d.asc),
        d.on(d.path{'hullValue'}, d.asc),
        d.on(d.path{'name'}, d.asc),
    })

    return dv.displayTable(data, p.view, columns)
end

p.minInfoboxRows = {
    'High-Stress', 'Low-Stress', 'Drone hardpoints', 'Docking bays', 'Crew',
    'Dry mass (tons)', 'Cargo bay', 'Processed cargo (tons)',
    'New price'
}

p.minInfobox = function(f)
    local key = mw.text.trim(f.args[1])
    local data = mw.loadData('Module:Ships/Data')
    data = data[key]

    local cap = capiunto.create{title=p.view.Name.format(data)}

    if f.args.image then
        cap:addImage(f.args.image, f.args.caption)
    end

    for i,viewKey in ipairs(p.minInfoboxRows) do
        local view = p.view[viewKey]
        local dataPoint = view.format(data)
        -- Skip zeros
        if view.sortType == 'number' then
            local sortValue = view.sortValue(data)
            if sortValue == 0 then
                dataPoint = nil
            end
        end
        if dataPoint then
            cap:addRow(view.heading, dataPoint)
        end
    end
    return cap
end


return p