Difference between revisions of "Module:Sandbox/Arcangelus"

From RimWorld Wiki
Jump to navigation Jump to search
m
m
 
(43 intermediate revisions by the same user not shown)
Line 1: Line 1:
local yesno, getArgs -- lazily initialized
 
local p = {}
 
local wrap = {} -- Holds wrapper functions that process arguments from #invoke. These act as intemediary between functions meant for #invoke and functions meant for Lua.
 
 
--Copied from https://en.wikipedia.org/wiki/Module:Math
 
local function err(msg)
 
-- Generates wikitext error messages.
 
return mw.ustring.format('<strong class="error">Formatting error: %s</strong>', msg)
 
end
 
 
local function unpackNumberArgs(args)
 
-- Returns an unpacked list of arguments specified with numerical keys.
 
local ret = {}
 
for k, v in pairs(args) do
 
if type(k) == 'number' then
 
table.insert(ret, v)
 
end
 
end
 
return unpack(ret)
 
end
 
 
local function makeArgArray(...)
 
-- Makes an array of arguments from a list of arguments that might include nils.
 
local args = {...} -- Table of arguments. It might contain nils or non-number values, so we can't use ipairs.
 
local nums = {} -- Stores the numbers of valid numerical arguments.
 
local ret = {}
 
for k, v in pairs(args) do
 
v = p._cleanNumber(v)
 
if v then
 
nums[#nums + 1] = k
 
args[k] = v
 
end
 
end
 
table.sort(nums)
 
for i, num in ipairs(nums) do
 
ret[#ret + 1] = args[num]
 
end
 
return ret
 
end
 
 
local function fold(func, ...)
 
-- Use a function on all supplied arguments, and return the result. The function must accept two numbers as parameters,
 
-- and must return a number as an output. This number is then supplied as input to the next function call.
 
local vals = makeArgArray(...)
 
local count = #vals -- The number of valid arguments
 
if count == 0 then return
 
-- Exit if we have no valid args, otherwise removing the first arg would cause an error.
 
nil, 0
 
end
 
local ret = table.remove(vals, 1)
 
for _, val in ipairs(vals) do
 
ret = func(ret, val)
 
end
 
return ret, count
 
end
 
 
--[[
 
function p.hello()
 
    return 'Hola'
 
end
 
 
aname=string.match("Blood Filtration Limit",' %(.*)% ')
 
 
return p
 
]]
 
 
--[[
 
Fold arguments by selectively choosing values (func should return when to choose the current "dominant" value).
 
]]
 
local function binary_fold(func, ...)
 
local value = fold((function(a, b) if func(a, b) then return a else return b end end), ...)
 
return value
 
end
 
 
--[[
 
sum
 
 
Finds the sum
 
 
Usage:
 
{{#invoke:Math| sum | value1 | value2 | ... }}
 
OR
 
{{#invoke:Math| sum }}
 
 
Note, any values that do not evaluate to numbers are ignored.
 
]]
 
 
function wrap.sum(args)
 
return p._sum(unpackNumberArgs(args))
 
end
 
 
function p._sum(...)
 
local sums, count = fold((function(a, b) return a + b end), ...)
 
if not sums then
 
return 0
 
else
 
return sums
 
end
 
end
 
 
--[[
 
average
 
 
Finds the average
 
 
Usage:
 
{{#invoke:Math| average | value1 | value2 | ... }}
 
OR
 
{{#invoke:Math| average }}
 
 
Note, any values that do not evaluate to numbers are ignored.
 
]]
 
 
function wrap.average(args)
 
return p._average(unpackNumberArgs(args))
 
end
 
 
function p._average(...)
 
local sum, count = fold((function(a, b) return a + b end), ...)
 
if not sum then
 
return 0
 
else
 
return sum / count
 
end
 
end
 
 
--[[
 
round
 
 
Rounds a number to specified precision
 
 
Usage:
 
{{#invoke:Math | round | value | precision }}
 
 
--]]
 
 
function wrap.round(args)
 
local value = p._cleanNumber(args[1] or args.value or 0)
 
local precision = p._cleanNumber(args[2] or args.precision or 0)
 
if value == nil or precision == nil then
 
return err('round input appears non-numeric')
 
else
 
return p._round(value, precision)
 
end
 
end
 
 
function p._round(value, precision)
 
local rescale = math.pow(10, precision or 0);
 
return math.floor(value * rescale + 0.5) / rescale;
 
end
 
 
--[[
 
log10
 
 
returns the log (base 10) of a number
 
 
Usage:
 
{{#invoke:Math | log10 | x }}
 
]]
 
 
function wrap.log10(args)
 
return math.log10(args[1])
 
end
 
 
--[[
 
mod
 
 
Implements the modulo operator
 
 
Usage:
 
{{#invoke:Math | mod | x | y }}
 
 
--]]
 
 
function wrap.mod(args)
 
local x = p._cleanNumber(args[1])
 
local y = p._cleanNumber(args[2])
 
if not x then
 
return err('first argument to mod appears non-numeric')
 
elseif not y then
 
return err('second argument to mod appears non-numeric')
 
else
 
return p._mod(x, y)
 
end
 
end
 
 
function p._mod(x, y)
 
local ret = x % y
 
if not (0 <= ret and ret < y) then
 
ret = 0
 
end
 
return ret
 
end
 
 
 
--[[
 
--[[
gcd
+
-- A proper solution needs to consider PC, PlayStation 4 and Xbox One. Maybe Steam deck as well.
 
 
Calculates the greatest common divisor of multiple numbers
 
 
 
Usage:
 
{{#invoke:Math | gcd | value 1 | value 2 | value 3 | ... }}
 
--]]
 
 
 
function wrap.gcd(args)
 
return p._gcd(unpackNumberArgs(args))
 
end
 
 
 
function p._gcd(...)
 
local function findGcd(a, b)
 
local r = b
 
local oldr = a
 
while r ~= 0 do
 
local quotient = math.floor(oldr / r)
 
oldr, r = r, oldr - quotient * r
 
end
 
if oldr < 0 then
 
oldr = oldr * -1
 
end
 
return oldr
 
end
 
local result, count = fold(findGcd, ...)
 
return result
 
end
 
 
 
--[[
 
precision_format
 
 
 
Rounds a number to the specified precision and formats according to rules
 
originally used for {{template:Rnd}}.  Output is a string.
 
 
 
Usage:
 
{{#invoke: Math | precision_format | number | precision }}
 
]]
 
 
 
function wrap.precision_format(args)
 
local value_string = args[1] or 0
 
local precision = args[2] or 0
 
return p._precision_format(value_string, precision)
 
end
 
  
function p._precision_format(value_string, precision)
+
-- This solution uses images: https://splatoonwiki.org/wiki/Module:Button
-- For access to Mediawiki built-in formatter.
+
-- Switch: https://splatoonwiki.org/w/index.php?title=Special%3APrefixIndex&prefix=Switch_Icon_&namespace=6
local lang = mw.getContentLanguage();
+
--
 +
-- Possible to get icons from wikipedia, if needed.
 +
-- For Xbox:
 +
-- https://commons.wikimedia.org/wiki/Category:Xbox_controller_buttons
 +
-- https://commons.wikimedia.org/wiki/Category:Xbox_Series_Controller_icons
 +
-- For PlayStation 4:
 +
-- https://commons.wikimedia.org/wiki/Category:PlayStation_controller_buttons
 +
--
 +
-- Other possible sources (Creative Commons Attribution):
 +
-- https://zacksly.itch.io/ps5-button-icons-and-controls ; https://zacksly.itch.io/xbox-series-button-icons-and-controls
  
local value
+
-- Below this line was taken from https://en.wikipedia.org/wiki/Module:Key. As the CSS was was not copied ovee, this is incomplete and only kinda works. Not properly implemented as I'm not happy on the handling of console inputs (controller)
value, value_string = p._cleanNumber(value_string)
+
-- Aditionally, there seems to be some kind of issue with dark mode. See here to avoid it: https://www.mediawiki.org/wiki/Recommendations_for_night_mode_compatibility_on_Wikimedia_wikis?minervanightmode=1 ]]
precision = p._cleanNumber(precision)
+
--------------------------------------------------
 +
-- This module implements {{key press}}.
  
-- Check for non-numeric input
+
local kbdPrefix = '<kbd class="keyboard-key nowrap">'
if value == nil or precision == nil then
 
return err('invalid input when rounding')
 
end
 
  
local current_precision = p._precision(value)
+
local kbdSuffix = '</kbd>'
local order = p._order(value)
 
  
-- Due to round-off effects it is neccesary to limit the returned precision under
+
local keyText = {
-- some circumstances because the terminal digits will be inaccurately reported.
+
['caps lock'] = '⇪ Caps Lock',
if order + precision >= 14 then
+
['[[caps lock]]'] = '⇪ [[Caps Lock]]',
if order + p._precision(value_string) >= 14 then
+
['shift'] = '⇧ Shift',
precision = 13 - order;
+
['[[shift key|shift]]'] = '⇧ [[Shift key|Shift]]',
end
+
['enter'] = '↵ Enter',
end
+
['[[enter key|enter]]'] = '↵ [[Enter key|Enter]]',
 +
['cmd'] = '⌘ Cmd',
 +
['[[command key|cmd]]'] = '⌘ [[Command key|Cmd]]',
 +
['command'] = '⌘ Command',
 +
['[[command key|command]]'] = '⌘ [[Command key|Command]]',
 +
['opt'] = '⌥ Opt',
 +
['[[option key|opt]]'] = '⌥ [[Option key|Opt]]',
 +
['option'] = '⌥ Option',
 +
['[[option key|option]]'] = '⌥ [[Option key|Option]]',
 +
['tab'] = 'Tab ↹',
 +
['[[tab key|tab]]'] = '[[Tab key|Tab]] ↹',
 +
['backspace'] = '← Backspace',
 +
['[[backspace]]'] = '← [[Backspace]]',
 +
['win'] = '⊞ Win',
 +
['[[windows key|win]]'] = '⊞ [[Windows key|Win]]',
 +
['menu'] = '≣ Menu',
 +
['[[menu key|menu]]'] = '≣ [[Menu key|Menu]]',
 +
['up'] = '↑',
 +
['[[arrow keys|up]]'] = '[[Arrow keys|↑]]',
 +
['down'] = '↓',
 +
['[[arrow keys|down]]'] = '[[Arrow keys|↓]]',
 +
['left'] = '←',
 +
['[[arrow keys|left]]'] = '[[Arrow keys|←]]',
 +
['right'] = '→',
 +
['[[arrow keys|right]]'] = '[[Arrow keys|→]]',
 +
['asterisk'] = '&#42;',
 +
['hash'] = '&#35;',
 +
['[[#]]'] = '[[Number sign|#]]',
 +
['colon'] = '&#58;',
 +
['[[:]]'] = '[[Colon (punctuation)|:]]',
 +
['pipe'] = '&#124;',
 +
['[[|]]'] = '[[Pipe symbol|&#124;]]',
 +
['semicolon'] = '&#59;',
 +
['[[;]]'] = '[[Semi-colon|&#59;]]',
 +
['equals'] = '&#61;',
  
-- If rounding off, truncate extra digits
+
-- Left & right analog sticks.
if precision < current_precision then
+
['l up'] = 'L↑',
value = p._round(value, precision)
+
['l down'] = 'L↓',
current_precision = p._precision(value)
+
['l left'] = 'L←',
end
+
['l right'] = 'L→',
 +
['l ne'] = 'L↗',
 +
['l se'] = 'L↘',
 +
['l nw'] = 'L↖',
 +
['l sw'] = 'L↙',
  
local formatted_num = lang:formatNum(math.abs(value))
+
['r up'] = 'R↑',
local sign
+
['r down'] = 'R↓',
 +
['r left'] = 'R←',
 +
['r right'] = 'R→',
 +
['r ne'] = 'R↗',
 +
['r se'] = 'R↘',
 +
['r nw'] = 'R↖',
 +
['r sw'] = 'R↙',
  
-- Use proper unary minus sign rather than ASCII default
+
-- PlayStation.
if value < 0 then
+
['ex'] = '×',
sign = ''
+
['circle'] = '',
else
+
['square'] = '□',
sign = ''
+
['triangle'] = '△',
end
 
 
 
-- Handle cases requiring scientific notation
 
if string.find(formatted_num, 'E', 1, true) ~= nil or math.abs(order) >= 9 then
 
value = value * math.pow(10, -order)
 
current_precision = current_precision + order
 
precision = precision + order
 
formatted_num = lang:formatNum(math.abs(value))
 
else
 
order = 0;
 
end
 
formatted_num = sign .. formatted_num
 
  
-- Pad with zeros, if needed
+
-- Nintendo 64 and GameCube.
if current_precision < precision then
+
['c up'] = 'C↑',
local padding
+
['c down'] = 'C↓',
if current_precision <= 0 then
+
['c left'] = 'C←',
if precision > 0 then
+
['c right'] = 'C→',
local zero_sep = lang:formatNum(1.1)
+
['c ne'] = 'C↗',
formatted_num = formatted_num .. zero_sep:sub(2,2)
+
['c se'] = 'C↘',
 +
['c nw'] = 'C↖',
 +
['c sw'] = 'C↙',
 +
}
  
padding = precision
+
local keyAlias = {
if padding > 20 then
+
-- ['alternate name for key (alias)'] = 'name for key used in key table'
padding = 20
+
['[[cmd key|cmd]]'] = '[[command key|cmd]]',
end
+
['[[cmd key|command]]'] = '[[command key|command]]',
 +
['[[opt key|opt]]'] = '[[option key|opt]]',
 +
['[[option key]]'] = '[[option key|option]]',
 +
['[[opt key|option]]'] = '[[option key|option]]',
 +
['[[win key|win]]'] = '[[windows key|win]]',
 +
['*'] = 'asterisk',
 +
['#'] = 'hash',
 +
[':'] = 'colon',
 +
[';'] = 'semicolon',
 +
['l-up'] = 'l up',
 +
['l-down'] = 'l down',
 +
['l-left'] = 'l left',
 +
['l-right'] = 'l right',
 +
['l-ne'] = 'l ne',
 +
['l-se'] = 'l se',
 +
['l-nw'] = 'l nw',
 +
['l-sw'] = 'l sw',
 +
['r-up'] = 'r up',
 +
['r-down'] = 'r down',
 +
['r-left'] = 'r left',
 +
['r-right'] = 'r right',
 +
['r-ne'] = 'r ne',
 +
['r-se'] = 'r se',
 +
['r-nw'] = 'r nw',
 +
['r-sw'] = 'r sw',
 +
['ps x'] = 'ex',
 +
['ps c'] = 'circle',
 +
['ps s'] = 'square',
 +
['ps t'] = 'triangle',
 +
['c-up'] = 'c up',
 +
['c-down'] = 'c down',
 +
['c-left'] = 'c left',
 +
['c-right'] = 'c right',
 +
['c-ne'] = 'c ne',
 +
['c-se'] = 'c se',
 +
['c-nw'] = 'c nw',
 +
['c-sw'] = 'c sw',
 +
}
  
formatted_num = formatted_num .. string.rep('0', padding)
+
local Collection = {}
end
+
Collection.__index = Collection
else
+
do
padding = precision - current_precision
+
function Collection:add(item)
if padding > 20 then
+
if item ~= nil then
padding = 20
+
self.n = self.n + 1
end
+
self[self.n] = item
formatted_num = formatted_num .. string.rep('0', padding)
 
 
end
 
end
 
end
 
end
 
+
function Collection:join(sep)
-- Add exponential notation, if necessary.
+
return table.concat(self, sep)
if order ~= 0 then
 
-- Use proper unary minus sign rather than ASCII default
 
if order < 0 then
 
order = '−' .. lang:formatNum(math.abs(order))
 
else
 
order = lang:formatNum(order)
 
end
 
 
 
formatted_num = formatted_num .. '<span style="margin:0 .15em 0 .25em">×</span>10<sup>' .. order .. '</sup>'
 
end
 
 
 
return formatted_num
 
end
 
 
 
--[[
 
divide
 
 
 
Implements the division operator
 
 
 
Usage:
 
{{#invoke:Math | divide | x | y | round= | precision= }}
 
 
 
--]]
 
function wrap.divide(args)
 
local x = args[1]
 
local y = args[2]
 
local round = args.round
 
local precision =  args.precision
 
if not yesno then
 
yesno = require('Module:Yesno')
 
end
 
return p._divide(x, y, yesno(round), precision)
 
end
 
 
 
function p._divide(x, y, round, precision)
 
if y == nil or y == "" then
 
return err("Empty divisor")
 
elseif not tonumber(y) then
 
if type(y) == 'string' and string.sub(y, 1, 1) == '<' then
 
return y
 
else
 
return err("Not a number: " .. y)
 
end
 
elseif x == nil or x == "" then
 
return err("Empty dividend")
 
elseif not tonumber(x) then
 
if type(x) == 'string' and string.sub(x, 1, 1) == '<' then
 
return x
 
else
 
return err("Not a number: " .. x)
 
end
 
else
 
local z = x / y
 
if round then
 
return p._round(z, 0)
 
elseif precision then
 
return p._round(z, precision)
 
else
 
return z
 
end
 
 
end
 
end
end
+
function Collection:sort(comp)
 
+
table.sort(self, comp)
--[[
 
Helper function that interprets the input numerically.  If the
 
input does not appear to be a number, attempts evaluating it as
 
a parser functions expression.
 
]]
 
 
 
function p._cleanNumber(number_string)
 
if type(number_string) == 'number' then
 
-- We were passed a number, so we don't need to do any processing.
 
return number_string, tostring(number_string)
 
elseif type(number_string) ~= 'string' or not number_string:find('%S') then
 
-- We were passed a non-string or a blank string, so exit.
 
return nil, nil;
 
 
end
 
end
 
+
function Collection.new()
-- Attempt basic conversion
+
return setmetatable({n = 0}, Collection)
local number = tonumber(number_string)
 
 
 
-- If failed, attempt to evaluate input as an expression
 
if number == nil then
 
local success, result = pcall(mw.ext.ParserFunctions.expr, number_string)
 
if success then
 
number = tonumber(result)
 
number_string = tostring(number)
 
else
 
number = nil
 
number_string = nil
 
end
 
else
 
number_string = number_string:match("^%s*(.-)%s*$") -- String is valid but may contain padding, clean it.
 
number_string = number_string:match("^%+(.*)$") or number_string -- Trim any leading + signs.
 
if number_string:find('^%-?0[xX]') then
 
-- Number is using 0xnnn notation to indicate base 16; use the number that Lua detected instead.
 
number_string = tostring(number)
 
end
 
 
end
 
end
 
return number, number_string
 
 
end
 
end
  
--TEST
+
local function keyPress(args)
function wrap.TableRow(args)
+
local chainNames = {
return p._TableRow(unpackNumberArgs(args))
+
'chain first',
end
+
'chain second',
function p._TableRow(skillBase, skillBonus, statMin, statMax, capImportance, capLimit, resultCols, LV, Ln)
+
'chain third',
argumentos={skillBase,skillBonus,statMin,statMax,capImportance,capLimit,resultCols,LV,Ln}
+
'chain fourth',
R_Pval=""
+
'chain fifth',
R_Sval=""
+
'chain sixth',
R_Tval=""
+
'chain seventh',
for i = 0,8 do --This should prevent errors if a number is not defined.
+
'chain eighth',
if type(argumentos[i])~='number' then
+
'chain ninth',
argumentos[i]=0
+
}
 +
local result = Collection.new()
 +
local chainDefault = args.chain or '+'
 +
for i, id in ipairs(args) do
 +
if i > 1 then
 +
result:add(args[chainNames[i - 1]] or chainDefault)
 
end
 
end
 +
local lc = id:lower()
 +
local text = keyText[lc] or keyText[keyAlias[lc]] or id
 +
result:add(kbdPrefix .. text .. kbdSuffix)
 
end
 
end
 +
return result:join()
 +
--mw.getCurrentFrame():extensionTag{
 +
-- name = 'templatestyles', args = { src = 'Template:Key press/styles.css'}
 +
-- }..result:join()
 
 
if Ln==nil then
 
factor = skillBase + skillBonus * LV
 
else
 
factor = Ln
 
end
 
local Pval = math.min(math.max(factor,statMin),statMax)
 
R_Pval = math.floor(Pval*10000)/100
 
-- Pval = tonumber(Pval.format("%.3f", exact))
 
if tonumber(resultCols)>1 then
 
Pval = factor * ( 1 + capImportance * math.min(capLimit-1, 0.25))
 
Pval = math.floor(Pval*10000)/100
 
R_Sval="<td>"..Pval.."</td>"
 
end
 
if tonumber(resultCols)>2 then
 
Pval = factor * ( 1 + capImportance * math.min(capLimit-1, 0.5))
 
Pval = math.floor(Pval*10000)/100
 
R_Tval="<td>"..Pval.."</td>"
 
end
 
 
return R_Pval..R_Sval..R_Tval
 
 
end
 
end
  
function wrap.SkillValue(args)
+
local function keypress(frame)
return p._SkillValue(unpackNumberArgs(args))
+
-- Called by "{{key press|...}}".
end
+
-- Using the template doubles the post‐expand include size.
function p._SkillValue(a, b, mult)
+
return keyPress(frame:getParent().args)
return a + b * mult
 
end
 
 
 
function p._sum2()
 
return 1+2+3+4+5
 
 
end
 
end
  
function wrap.twomult(args)
+
local function press(frame)
return p._twomult(unpackNumberArgs(args))
+
-- Called by "{{#invoke:key|press|...}}".
 +
return keyPress(frame.args)
 
end
 
end
function p._twomult(a, b)
 
return a * b
 
end
 
 
function wrap.potencia(args)
 
return p._potencia(unpackNumberArgs(args))
 
end
 
function p._potencia(a, b)
 
return a^b
 
end
 
 
--[[
 
Wrapper function that does basic argument processing. This ensures that all functions from #invoke can use either the current
 
frame or the parent frame, and it also trims whitespace for all arguments and removes blank arguments.
 
]]
 
 
local mt = { __index = function(t, k)
 
return function(frame)
 
if not getArgs then
 
getArgs = require('Module:Arguments').getArgs
 
end
 
return wrap[k](getArgs(frame))  -- Argument processing is left to Module:Arguments. Whitespace is trimmed and blank arguments are removed.
 
end
 
end }
 
  
return setmetatable(p, mt)
+
return {
 +
keypress = keypress,
 +
press = press,
 +
}

Latest revision as of 20:50, 21 August 2024

Welcome to the RimWorld Wiki sandbox!
This sandbox is where you can experiment and practice working on a wiki page. This page will usually have little or no content. Feel free to add content or to make changes and save them to see the results.

To learn about editing and formatting start here: Help:Contents. Just start with the basics... enter some text, and learn the other pieces as you go.

Your content contributions are welcome and important. The wiki is a collaborative effort and others can help with formatting and other improvements.]

Best wishes!

Description[edit]

This is a doc attached to my sandbox. I'll use it to see the effects of my changes W/o messing something important

NOTE: LUA is usually slower than ParserFunctions for short statements. The factor varies from 7-1 to 2-1.
Lua only is an advantage to long statements, nested logic, loops (maybe others case i don't see right now).

expr only uses 1 Preprocessor visited node count, in general. Variables may change that.


function p._TableRow(skillBase, skillBonus, statMin, statMax, capImportance, capLimit, resultCols, LV, Ln)



--[[
-- A proper solution needs to consider PC, PlayStation 4 and Xbox One. Maybe Steam deck as well.

-- This solution uses images: https://splatoonwiki.org/wiki/Module:Button
-- Switch: https://splatoonwiki.org/w/index.php?title=Special%3APrefixIndex&prefix=Switch_Icon_&namespace=6
--
-- Possible to get icons from wikipedia, if needed.
-- For Xbox:
--	https://commons.wikimedia.org/wiki/Category:Xbox_controller_buttons
--	https://commons.wikimedia.org/wiki/Category:Xbox_Series_Controller_icons
-- For PlayStation 4:
--	https://commons.wikimedia.org/wiki/Category:PlayStation_controller_buttons
--
-- Other possible sources (Creative Commons Attribution):
-- https://zacksly.itch.io/ps5-button-icons-and-controls ; https://zacksly.itch.io/xbox-series-button-icons-and-controls

-- Below this line was taken from https://en.wikipedia.org/wiki/Module:Key. As the CSS was was not copied ovee, this is incomplete and only kinda works. Not properly implemented as I'm not happy on the handling of console inputs (controller)
-- Aditionally, there seems to be some kind of issue with dark mode. See here to avoid it: https://www.mediawiki.org/wiki/Recommendations_for_night_mode_compatibility_on_Wikimedia_wikis?minervanightmode=1 ]]
--------------------------------------------------
-- This module implements {{key press}}.

local kbdPrefix = '<kbd class="keyboard-key nowrap">'

local kbdSuffix = '</kbd>'

local keyText = {
	['caps lock'] = '⇪ Caps Lock',
	['[[caps lock]]'] = '⇪ [[Caps Lock]]',
	['shift'] = '⇧ Shift',
	['[[shift key|shift]]'] = '⇧ [[Shift key|Shift]]',
	['enter'] = '↵ Enter',
	['[[enter key|enter]]'] = '↵ [[Enter key|Enter]]',
	['cmd'] = '⌘ Cmd',
	['[[command key|cmd]]'] = '⌘ [[Command key|Cmd]]',
	['command'] = '⌘ Command',
	['[[command key|command]]'] = '⌘ [[Command key|Command]]',
	['opt'] = '⌥ Opt',
	['[[option key|opt]]'] = '⌥ [[Option key|Opt]]',
	['option'] = '⌥ Option',
	['[[option key|option]]'] = '⌥ [[Option key|Option]]',
	['tab'] = 'Tab ↹',
	['[[tab key|tab]]'] = '[[Tab key|Tab]] ↹',
	['backspace'] = '← Backspace',
	['[[backspace]]'] = '← [[Backspace]]',
	['win'] = '⊞ Win',
	['[[windows key|win]]'] = '⊞ [[Windows key|Win]]',
	['menu'] = '≣ Menu',
	['[[menu key|menu]]'] = '≣ [[Menu key|Menu]]',
	['up'] = '↑',
	['[[arrow keys|up]]'] = '[[Arrow keys|↑]]',
	['down'] = '↓',
	['[[arrow keys|down]]'] = '[[Arrow keys|↓]]',
	['left'] = '←',
	['[[arrow keys|left]]'] = '[[Arrow keys|←]]',
	['right'] = '→',
	['[[arrow keys|right]]'] = '[[Arrow keys|→]]',
	['asterisk'] = '&#42;',
	['hash'] = '&#35;',
	['[[#]]'] = '[[Number sign|#]]',
	['colon'] = '&#58;',
	['[[:]]'] = '[[Colon (punctuation)|:]]',
	['pipe'] = '&#124;',
	['[[|]]'] = '[[Pipe symbol|&#124;]]',
	['semicolon'] = '&#59;',
	['[[;]]'] = '[[Semi-colon|&#59;]]',
	['equals'] = '&#61;',

	-- Left & right analog sticks.
	['l up'] = 'L↑',
	['l down'] = 'L↓',
	['l left'] = 'L←',
	['l right'] = 'L→',
	['l ne'] = 'L↗',
	['l se'] = 'L↘',
	['l nw'] = 'L↖',
	['l sw'] = 'L↙',

	['r up'] = 'R↑',
	['r down'] = 'R↓',
	['r left'] = 'R←',
	['r right'] = 'R→',
	['r ne'] = 'R↗',
	['r se'] = 'R↘',
	['r nw'] = 'R↖',
	['r sw'] = 'R↙',

	-- PlayStation.
	['ex'] = '×',
	['circle'] = '○',
	['square'] = '□',
	['triangle'] = '△',

	-- Nintendo 64 and GameCube.
	['c up'] = 'C↑',
	['c down'] = 'C↓',
	['c left'] = 'C←',
	['c right'] = 'C→',
	['c ne'] = 'C↗',
	['c se'] = 'C↘',
	['c nw'] = 'C↖',
	['c sw'] = 'C↙',
}

local keyAlias = {
	-- ['alternate name for key (alias)'] = 'name for key used in key table'
	['[[cmd key|cmd]]'] = '[[command key|cmd]]',
	['[[cmd key|command]]'] = '[[command key|command]]',
	['[[opt key|opt]]'] = '[[option key|opt]]',
	['[[option key]]'] = '[[option key|option]]',
	['[[opt key|option]]'] = '[[option key|option]]',
	['[[win key|win]]'] = '[[windows key|win]]',
	['*'] = 'asterisk',
	['#'] = 'hash',
	[':'] = 'colon',
	[';'] = 'semicolon',
	['l-up'] = 'l up',
	['l-down'] = 'l down',
	['l-left'] = 'l left',
	['l-right'] = 'l right',
	['l-ne'] = 'l ne',
	['l-se'] = 'l se',
	['l-nw'] = 'l nw',
	['l-sw'] = 'l sw',
	['r-up'] = 'r up',
	['r-down'] = 'r down',
	['r-left'] = 'r left',
	['r-right'] = 'r right',
	['r-ne'] = 'r ne',
	['r-se'] = 'r se',
	['r-nw'] = 'r nw',
	['r-sw'] = 'r sw',
	['ps x'] = 'ex',
	['ps c'] = 'circle',
	['ps s'] = 'square',
	['ps t'] = 'triangle',
	['c-up'] = 'c up',
	['c-down'] = 'c down',
	['c-left'] = 'c left',
	['c-right'] = 'c right',
	['c-ne'] = 'c ne',
	['c-se'] = 'c se',
	['c-nw'] = 'c nw',
	['c-sw'] = 'c sw',
}

local Collection = {}
Collection.__index = Collection
do
	function Collection:add(item)
		if item ~= nil then
			self.n = self.n + 1
			self[self.n] = item
		end
	end
	function Collection:join(sep)
		return table.concat(self, sep)
	end
	function Collection:sort(comp)
		table.sort(self, comp)
	end
	function Collection.new()
		return setmetatable({n = 0}, Collection)
	end
end

local function keyPress(args)
	local chainNames = {
		'chain first',
		'chain second',
		'chain third',
		'chain fourth',
		'chain fifth',
		'chain sixth',
		'chain seventh',
		'chain eighth',
		'chain ninth',
	}
	local result = Collection.new()
	local chainDefault = args.chain or '+'
	for i, id in ipairs(args) do
		if i > 1 then
			result:add(args[chainNames[i - 1]] or chainDefault)
		end
		local lc = id:lower()
		local text = keyText[lc] or keyText[keyAlias[lc]] or id
		result:add(kbdPrefix .. text .. kbdSuffix)
	end
	return result:join()
	--mw.getCurrentFrame():extensionTag{
--		name = 'templatestyles', args = { src = 'Template:Key press/styles.css'} 
--	}..result:join()
	
end

local function keypress(frame)
	-- Called by "{{key press|...}}".
	-- Using the template doubles the post‐expand include size.
	return keyPress(frame:getParent().args)
end

local function press(frame)
	-- Called by "{{#invoke:key|press|...}}".
	return keyPress(frame.args)
end

return {
	keypress = keypress,
	press = press,
}