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

From Xenharmonic Wiki
Jump to navigation Jump to search
Ganaram inukshuk (talk | contribs)
Ganaram inukshuk (talk | contribs)
Line 103: Line 103:
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.
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.


For testing purposes, a tester function may be added, which is itself a wrapper that calls the "main" function. This allows it to be tested in 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
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
function p._call_me(args)
function p._call_me(args)
     return "something" .. args["something"]
     return "something" .. args["something"]
Line 119: Line 119:
end</syntaxhighlight>The guidelines stated above do not apply to the following:
end</syntaxhighlight>The guidelines stated above do not apply to the following:


* Modules that serve as libraries for other modules; in such cases, there is no wrapper function other than the tester function.
* Modules that serve as libraries for other modules; in such cases, there is no wrapper function other than the tester function. Example: [[Module:MOS]] and [[Module:Rational]]
* Modules that provide wrappers to several related templates; in such cases, the rule regarding one wrapper for one template does not apply.
* Modules that provide multiple functions, including wrapper functions, meant to be invoked by multiple templates; in such cases, the rule regarding one wrapper for one template does not apply. Example: [[Module:Rational]] and [[Module:Utils]]
* Modules that provide only one function for other modules; in such cases, no wrapper functions are necessary.
* Modules that provide only one function for other modules; in such cases, no wrapper functions are necessary. Example: [[Module:Yesno]]
* Template-based modules whose wrapper function calls another module's functions; in such cases, no other functions are necessary.
* Template-based modules where writing a separate "main" function produces a short function; in such cases, the code for that function may be merged with the wrapper function. Example: [[Module:MOS scalesig]]
* Template-based modules whose wrapper function calls another module's functions; in such cases, no other functions are necessary. Example: [[Module:MOS scalesig]]


==== Helper functions ====
==== Helper functions ====
Line 139: Line 140:


==== What to pass into a "main" function ====
==== 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 inputs may be passed in one-by-one.<syntaxhighlight lang="lua">
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
-- "Main" function to be called by wrapper or another module
function p._call_me(arg1, arg2, arg3)
function p._call_me(arg1, arg2, arg3)
     -- Code goes here
     -- Code goes here
end
end
</syntaxhighlight>If there is large amounts of input, or if the number of inputs is not known, it should be entered as a table of arguments.<syntaxhighlight lang="lua">
</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.<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)

Revision as of 01:03, 16 October 2025

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.

Preliminaries

Indentation and formatting

The in-browser Lua editor uses tabs for indenting, which equates to 4 spaces.

Naming and declaring variables

Variable names should be short and descriptive, written snake_case. Exceptions to this include counter variables, like with i in for loops. Boolean variables are prefixed with is_. Constants are named using TRAIN_CASE.

All variable declarations should start with local.

Strings

Strings are enclosed in double quotes " ". 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 ' '. Escape characters for quotes should only be used if the string contains both single and double quotes.

Preferred

local sentence = "That's his cheese quesadilla!"
local quotation = "And he said to me, \"One man's trash is another man's treasure.\""

Avoid

local formatting = "padding: 0.25em 0.5em; border: 1px solid white;\" colspan="2\" | "

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.

	local function navbox_title()
		if not title then return "" end
		local has_navbar = name ~= nil

		return "|-\n" ..
			'! style="text-align: center; background-color: #eaecf0; white-space: nowrap; margin: 0em 4em 0em 4em;' ..
			'padding: 0.25em 0.5em; border: 1px solid white;" colspan="2" | ' ..
			'<span style="display: inline-block; float: left; text-align: left; font-weight: normal; font-style: normal; min-width: 4em; padding: 0px; margin: 0px;">' ..
			(has_navbar and navbar(name, "mini", "") or "") .. '</span>' ..
			'<span style="font-size: 1.05em;">' .. title .. '</span>' ..
			(is_collapsible and '' or '<span style="display: inline-block; float: right; font-size: 0.8em; width: 5em;">&nbsp;</span>')
	end

Naming, declaring, and calling functions

As with variables, functions are named using snake_case. Functions that return a boolean variable are prefixed with is_.

tbd

Comments in code

Well-commented code should speak for itself. Self-documenting code can only go so far, so comments should be used to briefly describe what the function does. If a module has several types of functions, comments can also be used as section separators.

TODOs are allowed in comments. Separating TODOs into the module's documentation page makes code harder to maintain.

Commented-out code

Only block comments --[[ ]]-- should only be used for commented-out code. This can be hard to do if a string contains a ]], so -- may be used instead.

Conventions for Lua modules

Boilerplate code

Alphabetize dependencies, except for p, which goes last and separated by a line. Equals signs may be lined up.

Preferred order

local mos     = require("Module:MOS")
local rat     = require("Module:Rational")
local utils   = require("Module:Utils")

local p = {}

Avoid

local p = {}
local mos     = require("Module:MOS")
local rat     = require("Module:Rational")
local utils   = require("Module:Utils")
local p = {}
local rat = require("Module:Rational")
local mos = require("Module:MOS")
local utils = require("Module:Utils")

Placement of comments and TODO comments

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.

-- 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 = {}

-- TODO goes here, if any

-- Description goes here

Use of functions

"Main", wrapper, and tester functions

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. A module should only provide one wrapper for one template.

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.

-- "Main" function to be called by wrapper or another module
function p._call_me(args)
    return "something" .. args["something"]
end

-- Wrapper function; to be called by template
function p.call_me(frame)
    return p._call_me(frame.args)
end

-- Tester function; test wrapper that calls the "main" function
function p.tester()
    local args = { ["something"] = 123 }
    return p._call_me(args)
end

The guidelines stated above do not apply to the following:

  • Modules that serve as libraries for other modules; in such cases, there is no wrapper function other than the tester function. Example: Module:MOS and Module:Rational
  • Modules that provide multiple functions, including wrapper functions, meant to be invoked by multiple templates; in such cases, the rule regarding one wrapper for one template does not apply. Example: Module:Rational and Module:Utils
  • Modules that provide only one function for other modules; in such cases, no wrapper functions are necessary. Example: Module:Yesno
  • Template-based modules where writing a separate "main" function produces a short function; in such cases, the code for that function may be merged with the wrapper function. Example: Module:MOS scalesig
  • Template-based modules whose wrapper function calls another module's functions; in such cases, no other functions are necessary. Example: Module:MOS scalesig

Helper functions

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

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. Nested functions have access to the variables and parameters of the outer function, so an equivalent nested function may require fewer parameters.

function some_function(args)
    -- Get args here
    local arg1 = args["Arg 1"]
    local arg2 = args["Arg 2"]
    
    -- Helper function
    function helper_function(some_value)
        return "result" .. some_value
    end
end

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.

-- "Main" function to be called by wrapper or another module
function p._call_me(arg1, arg2, arg3)
    -- Code goes here
end

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.

-- "Main" function to be called by wrapper or another module
function p._call_me(args)
    -- Get args here
    local arg1 = args["args1"]
    local arg2 = args["args2"]
    local arg3 = args["args3"]
    
    -- Code goes here
end

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

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')

Avoid

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'

Allowed (although an alternative to this exists by using string.format())

local table_line = "Number of steps: " .. tonumber(num_steps) .. " steps"

Mediawiki table formatting

Wikitables should be written with one line per cell instead of one line per row. This is for ease-of-reading when debugging the output of a module-generated table. Add a space between pipes/exclamation points and table entries to avoid accidentally adding new rows, such as when inputting negative numbers. Mediawiki tables generated using Lua code must follow this convention.

Preferred

{| class="wikitable"
|+ Caption text
|-
! Header 1
! Header 2
! Header 3
|-
| aa
| bb
| cc
|-
| dd
| ee
| ff
|}

Avoid

{| class="wikitable"
|+ Caption text
|-
! Header 1 !! Header 2 !! Header 3
|-
| aa || bb || cc
|-
| dd || ee || ff
|}

ipairs versus pairs versus for loop

local sometable = { "egg", "bread", "banana", nil, "milk" }

-- 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

-- As a for loop using ipairs
-- 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

-- As a for loop using pairs
-- Order is not guaranteed
-- Iterates through numeric and non-numeric keys
for key, value in pairs(sometable) do
    -- Code goes here
end


Templates and modules

Param names

Capitalized, short, and descriptive parameter names are preferred wherever possible, such as Scale Signature, and not scalesig or ssg.

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 name and debug. Templates with simple usage can use non-capitalized parameter names throughout.