Module:module documentation
- The following documentation is located at Module:module documentation/documentation. [edit]
- Useful links: subpage list • links • transclusions • testcases • sandbox
This module automatically generates documentation for other modules. It fetches in-line comments in Lua code and converts them into a form that can be used on a documentation page via {{module documentation}}
. In fact, this module's documentation is an example of it in action!
It's helpful to do documentation this way, as it means function/method documentation is available in two places: at the top of the module page (as conventional Wikitext), and above the function itself (as a Lua comment). Each suits a different kind of editing style, and doing it this way keeps them synchronised.
A section of documentation is given using Lua's multi-line comment syntax, which looks something like this: --[==[ ... ]==]
. The number of equal signs should normally be two in order for the documentation to be properly snarfed by {{module documentation}}
. The following conventions can be used inside of the documentation:
- Long lines (both in regular paragraphs and in list items) can be broken by newlines optionally followed by spaces or tabs (especially useful in lists, to make the raw comment more readable). In such a case, the newline is converted to a space. Use two newlines in a row to break paragraphs. In general, it's recommended to break lines after at most 120 characters, to facilitate reading the raw comment.
- Template calls (using two braces) can be inserted literally and will be expanded.
- Single braces can be used to surround literal text, and will automatically be syntax-highlighted as Lua code. Nested braces inside of this literal text will be properly handled as long as they're balanced. If the first character of the literal text is itelf a brace, put a space before it (but not at the end), and it will be ignored.
- Backquotes can be used to surround literal text, which will be displayed using
<code>...</code>
. The stuff inside of backquotes cannot contain a backquote or extend to multiple lines. - Double backquotes can be used to surround placeholder variable names, which will be displayed using
<var>...</var>
. The stuff inside of backquotes can only contain letters, numbers, underscores, hyphens and periods.
export.show
editfunction export.show(frame)
The main entrypoint for {{module documentation}}
. The frame object can contain 3 optional arguments:
|comment_level=
: The number of equals signs (=) a given section uses. Default: 2 (i.e.--[==[ ... (comment block) ]==]
)- e.g. The value 4 means
--[====[ ... (comment block) ]====]
.
- e.g. The value 4 means
|section_level=
: The header level used for each function/method. Default: 2 (i.e. L2:== ... ==
).|identifier=
: A Lua string pattern. Only the comments of functions whose names match this pattern are used. When not given, all function are accepted.- This is useful when giving object methods, using a pattern such as
^object_name:
.
- This is useful when giving object methods, using a pattern such as
Usage notes
edit- All local functions are ignored, because they are invisible outside the module.
- The one exception to this is when object methods are defined inside a local function, as the object may be returned by the module.
- Curly brackets in comments are used as a shortcut for syntaxhighlight, as using syntaxhighlight doesn't work otherwise.
local m_str_utils = require("Module:string utilities")
local codepoint = m_str_utils.codepoint
local concat = table.concat
local insert = table.insert
local u = m_str_utils.char
local export = {}
--[===[ intro:
This module automatically generates documentation for other modules. It fetches in-line comments in Lua code and
converts them into a form that can be used on a documentation page via {{tl|module documentation}}. In fact, this
module's documentation is an example of it in action!
It's helpful to do documentation this way, as it means function/method documentation is available in two places:
at the top of the module page (as conventional Wikitext), and above the function itself (as a Lua comment). Each
suits a different kind of editing style, and doing it this way keeps them synchronised.
A section of documentation is given using Lua's multi-line comment syntax, which looks something like this:
{--[==[ ... ]==]}. The number of equal signs should normally be two in order for the documentation to be properly
snarfed by {{tl|module documentation}}. The following conventions can be used inside of the documentation:
# Long lines (both in regular paragraphs and in list items) can be broken by newlines optionally followed by spaces
or tabs (especially useful in lists, to make the raw comment more readable). In such a case, the newline is converted
to a space. Use two newlines in a row to break paragraphs. In general, it's recommended to break lines after at most
120 characters, to facilitate reading the raw comment.
# Template calls (using two braces) can be inserted literally and will be expanded.
# Single braces can be used to surround literal text, and will automatically be syntax-highlighted as Lua code. Nested
braces inside of this literal text will be properly handled as long as they're balanced. If the first character of the
literal text is itelf a brace, put a space before it (but not at the end), and it will be ignored.
# Backquotes can be used to surround literal text, which will be displayed using {<code>...</code>}. The stuff inside of
backquotes cannot contain a backquote or extend to multiple lines.
# Double backquotes can be used to surround placeholder variable names, which will be displayed using {<var>...</var>}.
The stuff inside of backquotes can only contain letters, numbers, underscores, hyphens and periods.
]===]
local function format_doc(str)
local code_blocks = {}
local code_blocks_i = 0
local private_use_start = 0x100000
return (str
-- {} blocks: blocks of code
-- Escape to avoid removing line breaks.
:gsub("%b{}", function(m0)
if m0:sub(2, 2) == "{" and m0:sub(-2, -2) == "}" then return nil end
local text = "<syntaxhighlight lang=lua" .. (m0:match("\n") and "" or " inline") .. ">" .. m0:sub(2, -2):gsub("^ +", "") .. "</syntaxhighlight>"
code_blocks_i = code_blocks_i + 1
code_blocks[code_blocks_i] = text
return u(private_use_start + code_blocks_i)
end)
:gsub("([^\n])\n[ \t]*([^ \t\n#*:;])", "%1 %2") -- join continuation lines
:gsub("([^\n])\n[ \t]*([^ \t\n#*:;])", "%1 %2") -- do it twice in case of a single-character line (admittedly rare)
:gsub("\n[ \t]+%f[*#:;]", "\n") -- remove indentation for list items
:gsub("%f[\n,{]\n%f[^\n*#:;]", "\n\n") -- wiki newlines
:gsub("(\n[ *#:]*)(|?[_%w]+=?):", "%1<code><b>%2</b></code>:") -- parameter names
:gsub("``([A-Za-z0-9_%-.]+)``", "<var>%1</var>") -- placeholder variable names between double backquotes
:gsub("`([^`\n]+)`", '<code>%1</code>') -- literal text between backquotes, set using <code>...</code>
:gsub("\244[\128-\191][\128-\191][\128-\191]", function(char)
return code_blocks[codepoint(char) - private_use_start]
end))
end
--[===[
The main entrypoint for {{tl|module documentation}}. The frame object can contain 3 optional arguments:
* |comment_level=: The number of equals signs (=) a given section uses. Default: 2 (i.e. {--[==[ ... (comment block) ]==]})
*: e.g. The value 4 means {--[====[ ... (comment block) ]====]}.
* |section_level=: The header level used for each function/method. Default: 2 (i.e. L2: {== ... ==}).
* |identifier=: A Lua string pattern. Only the comments of functions whose names match this pattern are used. When not given, all function are accepted.
*: This is useful when giving object methods, using a pattern such as {^object_name:}.
]===]
function export.show(frame)
local args = frame:getParent().args or {}
local comment_level = tonumber(args["comment_level"]) or 2
local function make_comment_pattern(typeid)
if typeid then
typeid = "%s*" .. typeid .. ":"
else
typeid = ""
end
return "%-%-%[" .. ("="):rep(comment_level) .. "%[" .. typeid .. "\n?(.-)\n?]" .. ("="):rep(comment_level) .. "]()"
end
local fn_comment_pattern = make_comment_pattern(nil)
local intro_comment_pattern = make_comment_pattern("intro")
local usage_comment_pattern = make_comment_pattern("usage")
local section_mark = ("="):rep(tonumber(args["section_level"]) or 2)
local pattern_identifier = args["identifier"] or ""
local mod_title = mw.title.getCurrentTitle()
if mod_title.text:match("/documentation$") then return "(<i>The generated documentation is located at the module page.</i>)" end
local mod_text = mod_title:getContent()
if not mod_text then return "(<i>The module page does not exist now.</i>)" end
local chunk
local intro_comment = mod_text:match("^.-" .. intro_comment_pattern)
if intro_comment then
chunk = { format_doc(intro_comment) }
else
chunk = {}
end
for p0, f, fn in mod_text:gmatch("()\n[ \t]*function +(([^\n(]+)[^\n)]+%))") do
if fn:match(pattern_identifier) then
local c = mod_text:sub(1, p0 - 1):match("^.*" .. fn_comment_pattern .. "%s*$")
insert(chunk, section_mark .. fn .. section_mark .. "\n\n" .. "<syntaxhighlight lang=lua inline>function " .. f .. "</syntaxhighlight>\n\n" .. format_doc(c or '<strong class="error">This function lacks documentation. Please add a description of its usages, inputs and outputs, or its difference from similar functions, or make it local to remove it from the function list.</strong>[[Category:Templates and modules needing documentation]]'))
end
end
local usage_comment = mod_text:match("^.*" .. usage_comment_pattern)
if usage_comment then
insert(chunk, section_mark .. "Usage notes" .. section_mark .. "\n\n" .. format_doc(usage_comment))
end
return frame:preprocess(concat(chunk, "\n\n"))
end
--[===[ usage:
* All local functions are ignored, because they are invisible outside the module.
* The one exception to this is when object methods are defined inside a local function, as the object may be returned by the module.
* Curly brackets in comments are used as a shortcut for syntaxhighlight, as using syntaxhighlight doesn't work otherwise.
]===]
return export