User:Ganaram inukshuk/Provisional style guide for Lua: Difference between revisions

Ganaram inukshuk (talk | contribs)
No edit summary
Ganaram inukshuk (talk | contribs)
No edit summary
 
(14 intermediate revisions by the same user not shown)
Line 1: Line 1:
The following style guide is a provisional guide adapted from the LuaRocks style guide: https://github.com/luarocks/lua-style-guide
The following style guide is a personal, provisional guide adapted from the LuaRocks style guide: https://github.com/luarocks/lua-style-guide


Parts of it are adapted for use on the wiki, with details on specific conventions. If a specific convention is missing, fall back to that described in the Luarocks style guide.
Parts of it are adapted for use on the wiki, with details on specific conventions. If a specific convention is missing, fall back to that described in the Luarocks style guide.
== Provisional additions for version 2 ==
Consistency with param names, including spelling:
* Define all param names at the module level, not the template level
* Use snake_case for all param names throughout; anything remotely close to snake_case is normalized to snake_case
* "debug" and "nocat" are reserved for toggling categories; the use of "debug" this way has already been the standard


== Preliminaries ==
== Preliminaries ==
Line 10: Line 17:
=== Naming and declaring variables ===
=== Naming and declaring variables ===


Variable names should be short and descriptive, written <code>snake_case</code>. Exceptions to this include counter variables, like with <code>i</code> in for loops. Boolean variables are prefixed with <code>is_</code>. Constants are named using <code>TRAIN_CASE</code>.
Variable names should be short and descriptive, written in <code>snake_case</code>. Exceptions to this include counter variables, like with <code>i</code> in for loops. Boolean variables are prefixed with <code>is_</code> or <code>has_</code> wherever possible. Constants are named using <code>TRAIN_CASE</code>.


All variable declarations should start with <code>local</code>.
All variable declarations should start with <code>local</code>.
Line 16: Line 23:
=== Strings ===
=== Strings ===


Strings are enclosed in double quotes <code>" "</code>. If a string contains double quotes, do not escape them while enclosing the string in double quotes, as this reduces readability; instead, enclose the string in single quotes <code>' '</code>. Escape characters are only necessary if the string contains both single and double quotes.
Strings are enclosed in double quotes <code>" "</code>. If a string contains double quotes, do not escape them while enclosing the string in double quotes, as this reduces readability; instead, enclose the string in single quotes <code>' '</code>. Escape characters for quotes should only be used if the string contains both single and double quotes.


'''Preferred'''
'''Preferred'''
Line 29: Line 36:
=== Line lengths ===
=== Line lengths ===


The 80-column limit is to be observed wherever possible for both comments and code, except for cases where strings, inline comments, or function calls are too long.
The 80-column limit should be observed wherever possible for both comments and code, except for cases where strings, inline comments, or function calls are too long.


<syntaxhighlight lang="lua">
<syntaxhighlight lang="lua">
Line 48: Line 55:
=== Naming, declaring, and calling functions ===
=== Naming, declaring, and calling functions ===


As with variables, functions are named using <code>snake_case</code>.
As with variables, functions are named using <code>snake_case</code>. Functions that return a boolean variable are prefixed with <code>is_</code> or <code>has_</code>.


''tbd''
''tbd''
Line 62: Line 69:
Only block comments <code> --[[  ]]-- </code> should only be used for commented-out code. This can be hard to do if a string contains a <code>]]</code>, so <code>--</code> may be used instead.
Only block comments <code> --[[  ]]-- </code> should only be used for commented-out code. This can be hard to do if a string contains a <code>]]</code>, so <code>--</code> may be used instead.


== Conventions for lua modules ==
== Conventions for Lua modules ==


=== Boilerplate code ===
=== Boilerplate code ===
Alphabetize dependencies, except for p, which goes last and separated by a line. Equals signs may be lined up. Placement of comments to be determined.
Alphabetize dependencies, except for p, which goes last and separated by a line. Equals signs may be lined up.
 
'''Preferred order'''<syntaxhighlight lang="lua" line="1">local mos    = require("Module:MOS")
local rat    = require("Module:Rational")
local utils  = require("Module:Utils")


'''Preferred order'''<syntaxhighlight lang="lua" line="1">
local p = {}</syntaxhighlight>'''Avoid'''<syntaxhighlight lang="lua" line="1">
local p = {}
local mos    = require("Module:MOS")
local mos    = require("Module:MOS")
local rat    = require("Module:Rational")
local rat    = require("Module:Rational")
local utils  = require("Module:Utils")
local utils  = require("Module:Utils")
local et      = require("Module:ET")
</syntaxhighlight><syntaxhighlight lang="lua" line="1">
local tip    = require("Module:Template input parse")
local tamnams = require("Module:TAMNAMS")
local yesno  = require("Module:Yesno")
 
local p = {}
local p = {}
</syntaxhighlight>'''Avoid'''<syntaxhighlight lang="lua" line="1">
local rat = require("Module:Rational")
local mos = require("Module:MOS")
local mos = require("Module:MOS")
local rat = require("Module:Rational")
local utils = require("Module:Utils")
local utils = require("Module:Utils")
local et = require("Module:ET")
</syntaxhighlight>
local tip = require("Module:Template input parse")
 
local tamnams = require("Module:TAMNAMS")
=== Placement of comments and TODO comments ===
local yesno = require("Module:Yesno")
Comments may be placed before modules as a preface, inline with modules to describe what each module does, and after the boilerplate code as a descripion. TODO comments should be placed before the code's description for ease of access.
 
TODO comments should not be confused with the todo categories, nor should such code-based TODOs ever be placed in [[Template:Todo]]; placing such tasks in the template makes it harder to maintain code and mixes up code-based tasks with wiki-based tasks.<syntaxhighlight lang="lua" line="1">-- Preface goes here
local mos    = require("Module:MOS")       -- For mos scale functions
local rat    = require("Module:Rational") -- For JI ratio calculations
local utils  = require("Module:Utils")     -- Contains the gcd function
 
local p = {}
local p = {}
</syntaxhighlight>


-- TODO goes here, if any
-- Description goes here</syntaxhighlight>
=== Use of functions ===
=== Use of functions ===


===== "Main", wrapper, and tester functions =====
===== Modules that implement templates =====
Templates should consist of at least two functions: a "main" function prefixed with an underscore, and a wrapper function without an underscore.
Modules that implement templates should consist of at least two functions: a "main" function prefixed with an underscore, and a wrapper function of the same name but without an underscore.


The use of a wrapper and "main" function allows for a module to be used directly in another module or indirectly through its corresponding template. This rule may be disregarded for simple modules, or modules whose use is to provide a single function for other modules.
The use of a wrapper and "main" function allows for a module to be used directly in another module or indirectly through its corresponding template. A module should only provide '''one''' wrapper for '''one''' template. If the "main" function is short enough, its code may be merged with the wrapper function.


For testing purposes, a tester function may be added, which is itself a wrapper that calls the main function.<syntaxhighlight lang="lua">
A tester function may be added for testing purposes, which is itself a wrapper that also calls the "main" function. This allows it to be tested using the in-browser console by calling mw.logObject(p.tester()). Tester functions may be removed if the "main" function is determined to be functional under expected conditions.<syntaxhighlight lang="lua">-- "Main" function to be called by wrapper or another module
-- "Main" function to be called by wrapper or another module
function p._call_me(args)
function p._call_me(args)
     return "something" .. args["something"]
     return "something" .. args["something"]
end
end


-- Function to be called by template
-- Wrapper function; to be called by template using {{#invoke}}
function p.call_me(frame)
function p.call_me(frame)
     return p._call_me(frame.args)
     return p._call_me(frame.args)
Line 110: Line 124:
     local args = { ["something"] = 123 }
     local args = { ["something"] = 123 }
     return p._call_me(args)
     return p._call_me(args)
end</syntaxhighlight><syntaxhighlight lang="lua">-- Wrapper function; to be called by template
function p.call_me(frame)
    local arg = frame.args["arg"]
   
    -- Code goes here
   
    return call_me(arg)
end</syntaxhighlight>
==== Modules that serve as libraries ====
Modules that serve as libraries for other modules do not need a wrapper function, apart from the tester. Examples include [[Module:MOS]], [[Module:Rational]], and [[Module:Utils]].
Modules that serve as libraries for multiple, related templates may provide multiple wrapper functions. If a module provides multiple wrappers that each call a corresponding function, the name of the function called within the wrapper must be prefixed with an underscore.<syntaxhighlight lang="lua">-- MAIN FUNCTIONS
-- Usable by other modules
function p._call_me(arg1, arg2, arg3)
    -- Code goes here
end
end
</syntaxhighlight>
 
function p._truncate(arg1)
    -- Code goes here
end
 
-- WRAPPER FUNCTIONS
-- Usable by templates with {{#invoke}}
 
function p._call_me(frame)
    return p._clamp(frame.args["arg1"], frame.args["arg2"], frame.args["arg3"])
end
 
function p.truncate(frame)
    return p._truncate(frame.args["arg1"])
end</syntaxhighlight>Modules that only provide one function may simply consist of that function as a return value. Example: [[Module:Yesno]].


==== Helper functions ====
==== Helper functions ====
For code readability, the use of helper functions is recommended.
For code readability and code reusability within the module, the use of helper functions is recommended.


Helper functions may be nested within the calling function if those helpers only serve that function. This rule may be disregarded for testing purposes. Nested functions have access to the variables and parameters of the outer function, so an equivalent nested function may require fewer parameters.<syntaxhighlight lang="lua">function some_function(args)
Ideally, helper functions should be nested within the calling function if those helpers only serve that function, but this rule may be disregarded for testing purposes, or for helper functions needed by more than one function. Nested functions have access to the variables and parameters of the outer function, so an equivalent nested function may require fewer parameters.<syntaxhighlight lang="lua">function p._call_me(args)
     -- Get args here
     -- Get args here
     local arg1 = args["Arg 1"]
     local arg1 = args["Arg 1"]
Line 126: Line 171:
     end
     end
end</syntaxhighlight>
end</syntaxhighlight>
==== What to pass into a "main" function ====
For an underscore-prefixed "main" function, if its expected inputs is determined to be fixed (for example, no new features are expected to ever be added), the function may accept them individually.<syntaxhighlight lang="lua">-- "Main" function to be called by wrapper or another module
-- Function signature consists of three args and the return value (not shown)
function p._call_me(arg1, arg2, arg3)
    -- Code goes here
end</syntaxhighlight>If there is large amounts of input, or if the number of inputs is not known, the function should have a single parameter that is a table of passed-in arguments. This allows for expansion of the template's inputs without making the function signature any larger.<syntaxhighlight lang="lua">
-- "Main" function to be called by wrapper or another module
-- Function signature consists of one table and the output (not shown)
function p._call_me(args)
    -- Get args here
    local arg1 = args["args1"]
    local arg2 = args["args2"]
    local arg3 = args["args3"]
   
    -- Code goes here
end
</syntaxhighlight>
==== What to get out of a function ====
Most functions should return something (a value, a string, a table, or more than one of those things), with the exception of functions that change the contents of a table. Modules that implement templates typically return a string that represents something that can be interpreted as wikitext. Guidance on how to create such strings is explained in later sections.
=== Concatenating strings ===
When concatenating long strings, it is best practice to insert constituent strings into a table, then concatenate afterwards. https://www.lua.org/pil/11.6.html
This rule does not apply to concatenating a few small strings together.
'''Preferred'''<syntaxhighlight lang="lua">
local lines = {}
table.insert(lines, '{| class="wikitable"')
table.insert(lines, '|+ Caption text')
table.insert(lines, '|-')
table.insert(lines, '! Header 1')
table.insert(lines, '! Header 2')
table.insert(lines, '! Header 3')
table.insert(lines, '|-')
table.insert(lines, '| aa')
table.insert(lines, '| bb')
table.insert(lines, '| cc')
table.insert(lines, '|-')
table.insert(lines, '| dd')
table.insert(lines, '| ee')
table.insert(lines, '| ff')
table.insert(lines, '|}')
return table.concat(lines, '\n')
</syntaxhighlight>'''Avoid'''<syntaxhighlight lang="lua">
local result = '{| class="wikitable"\n'
    .. '|+ Caption text\n'
    .. '|-\n'
    .. '! Header 1\n'
    .. '! Header 2\n'
    .. '! Header 3\n'
    .. '|-\n'
    .. '| aa\n'
    .. '| bb\n'
    .. '| cc\n'
    .. '|-\n'
    .. '| dd\n'
    .. '| ee\n'
    .. '| ff\n'
    .. '|}\n'
</syntaxhighlight>'''Allowed''' (although an alternative to this exists by using <code>string.format()</code>)<syntaxhighlight lang="lua">
local table_line = "Number of steps: " .. tonumber(num_steps) .. " steps"
</syntaxhighlight>


=== Mediawiki table formatting ===
=== Mediawiki table formatting ===
Line 144: Line 255:
| ee
| ee
| ff
| ff
|}</syntaxhighlight>'''Avoid'''<syntaxhighlight lang="wikitext">
|}</syntaxhighlight>
{| class="wikitable"
 
'''Avoid'''
 
<syntaxhighlight lang="wikitext">{| class="wikitable"
|+ Caption text
|+ Caption text
|-
|-
Line 154: Line 268:
| dd || ee || ff
| dd || ee || ff
|}
|}
</syntaxhighlight>
=== <code>ipairs</code> versus <code>pairs</code> versus <code>for</code> loop ===
<syntaxhighlight lang="lua">
local sometable = { "egg", "bread", "banana", nil, "milk" }


</syntaxhighlight>
-- As a for loop
-- Order is guaranteed, but where to stop and start is needed
-- Iterates through all numeric indices, even if values are nil
for i = 1, #sometable do
    -- Code goes here
end


=== Concatenating strings ===
-- As a for loop using ipairs
'''Experimental; yet to be fully adopted'''
-- Order is guaranteed, in ascending order
-- Iterates through numeric keys/indices, but stops at the first value of nil encountered
-- Obviates using sometable[i], as value has the same... value
for index, value in ipairs(sometable) do
    -- Code goes here
end


Brought up when trying to make example code; turns out, this is recommended practice for large tables: https://www.lua.org/pil/11.6.html<syntaxhighlight lang="lua">
-- As a for loop using pairs
    table.insert(lines, '{| class="wikitable"')
-- Order is not guaranteed
    table.insert(lines, '|+ Caption text')
-- Iterates through numeric and non-numeric keys
    table.insert(lines, '|-')
for key, value in pairs(sometable) do
    table.insert(lines, '! Header 1')
     -- Code goes here
    table.insert(lines, '! Header 2')
end
     table.insert(lines, '! Header 3')
    table.insert(lines, '|-')
    table.insert(lines, '| aa')
    table.insert(lines, '| bb')
    table.insert(lines, '| cc')
    table.insert(lines, '|-')
    table.insert(lines, '| dd')
    table.insert(lines, '| ee')
    table.insert(lines, '| ff')
    table.insert(lines, '|}')
   
    return table.concat(lines, '\n')
</syntaxhighlight>
</syntaxhighlight>


== Templates and modules ==
== Templates and modules ==


=== Param names ===
=== Param names ===
Capitalized, short, and descriptive parameter names are preferred wherever possible, such as <code>Scale Signature</code>, and not <code>scalesig</code> or <code>ssg</code>. Non-capitalized parameter names are used for debugging, testing, or meta-use (parameters used to build other templates and typically would never be seen in normal use), such as <code>name</code> and <code>debug</code>.
Capitalized, short, and descriptive parameter names are preferred wherever possible, such as <code>Scale Signature</code>, and not <code>scalesig</code> or <code>ssg</code>.
 
Non-capitalized parameter names are used for debugging, testing, or meta-use (parameters used to build other templates and typically would never be seen in normal use), such as <code>name</code> and <code>debug</code>. Templates with simple usage can use non-capitalized parameter names throughout.