Module:Docbunto
| This module is rated as beta. It is considered ready for widespread use, but as it is still relatively new, it should be applied with some caution to ensure results are as expected. |
| This module depends on the following other modules: |
| Description | Is an automatic documentation generator for Scribunto modules. |
|---|---|
| Author(s) |
|
| Status | Beta |
| Testcase Status | |
| Updated | June 25, 2026 (31 days ago) |
| Dependencies | |
| Using code by | @stevedonovan (GitHub) |
Docbunto is an automatic documentation generator for Scribunto modules. The module is based on LuaDoc and LDoc. It produces documentation in the form of MediaWiki markup, using @tag-prefixed comments embedded in the source code of a Scribunto module. The taglet parser & doclet renderer Docbunto uses are also publicly exposed to other modules.
Docbunto code items are introduced by a block comment (--[[]]--), an inline comment with three hyphens (---), or an inline @tag comment. The module can use static code analysis to infer variable names, item privacy (local keyword), tables ({} constructor) and functions (function keyword). MediaWiki and Markdown formatting is supported.
Items are usually rendered in the order they are defined, if they are public items, or emulated classes extending the Lua primitives. There are many customisation options available to change Docbunto behaviour.
The following documentation and module is adapted from wikia:dev:Global Lua Modules/Docbunto.
Example
--- Item summary.
-- The item description follows. Information provided by the item comment
-- can be formatted with both '''MediaWiki''' and **Markdown** syntax.
-- The comments can generally span multiple lines.
--
-- By leaving empty comment lines, a new paragraph can be made in the
-- documentation. This can be used to give longer explanations whitespace.
--
-- @script ldoc
-- @param {table} options Configuration options.
-- @param {string} options.file Target file.
-- @param[opt] {string} options.filter Output filter.
-- @param[opt] {boolean} options.dump Text output to console.
-- @return {string} Compiled documentation.
gives:
ldoc(options)- Item summary. The item description follows. Information provided by the item comment can be formatted with both MediaWiki and Markdown syntax. The comments can generally span multiple lines.
- By leaving empty comment lines, a new paragraph can be made in the documentation. This can be used to give longer explanations whitespace.
- Parameters:
optionsConfiguration options. (table)options.fileTarget file. (string)options.filterOutput filter. (string; optional)options.dumpText output to console. (boolean; optional)
- Returns: Compiled documentation. (string)
Tags
Docbunto provides fifty two tags to document items. To document subitem types, Docbunto uses JSDoc-style {type} prefixes for typed subitem tags (e.g: @param {bool} or @field {string}. To designate optional subitems, Docbunto uses the LDoc-style [opt] modifier affixed to the tag (e.g: @param[opt] verbose).
Item type tags
Item type tags are the heart of Docbunto, and provide the name and type of a documentation item. Each item is rendered as a definition list with a description below it. Subitem tags can be placed alongside the item comments to provide further information.
@function(item; specialised; alias@lfunction)- Module-level function, either inside a table or outside. The documented, top-level parameters of the function are rendered adjacent to the name in parentheses. Relevant tags include
@param,@error,@returnand@factory. @table(item; specialised)- Module-level table, ideally a table constructor delimited by
{}. Relevant tags include@field. @member(item; generic; alias@property)- Module or class data member, typically a primitive value (
string,booleanornumbertype). As this tag denotes a generic type, Docbunto will try to qualify the type with a{type}prefix. @variable(item; generic)- Local variable. Can be rendered with a primitive type qualifier, as with
@member. Implies@localtag. @section(section)- Section header, used to structure documentation. The summary (or value) of a
@sectionitem is rendered as a header, while the description is rendered below it as a paragraph if present. @type(section; class; alias@typedef)- Module-level emulated class variable. A
@typetag is rendered as a header for the class name, with the description in a paragraph below.
Subitem tags
Subitem tags can be used at the module and item level to associate information with a Lua module or item. These tags are rendered as subitems below the description, inside nested definition or unordered lists.
@usage(preformatted multi-line; alias@code)- Code demonstration, rendered in preformatted text. The tag provides brace-delimited wikitext or Lua syntax highlighting, depending on the value.
@param(multi-line; alias@argument)- Parameter tag, accepting multi-line values and
{type}notation. Optional parameters have[opt]affixed to the tag. In module-level comment blocks, this tag implies a function module. @field(multi-line)- Table field name, identified by index key. Supports
{type}notation for field value. @error(multi-line; alias@raise,@throwsor@exception)- Error thrown by a Lua function. Accepts type via
{type}notation. The line number can be specified by[number]affixed to the tag. @return(multi-line; alias@returns)- Return value for function, supporting Lua's multiple returns. Accepts type via
{type}notation, and accepts[opt]affixed to the tag if they are not always returned. In module-level comment blocks, this tag implies a function module.
Typed subitem tags
These are typed versions of subitem tags. These tags are added for backwards compatibility with LDoc. These tags either imply a tag type, or permit types with type prefix notation (as with LDoc).
@tfield(multi-line)- Typed
@fieldtag. Expectstypeprefix without spaces. @tparam(multi-line)- Typed
@paramtag. Expectstypeprefix without spaces. @treturn(multi-line)- Typed
@returntag. Expectstypeprefix without spaces. @bool(multi-line)- Boolean table field, or boolean function parameter. Implies
booleantype. @func(multi-line)- Function table field, or subroutine function parameter. Implies
functiontype. @int(multi-line)- Integer table field, or integer function parameter. Implies
numbertype. @number(multi-line)- Numeric table field, or numeric function parameter. Implies
numbertype. @string(multi-line)- String table field, or string function parameter. Implies
stringtype. @tab(multi-line)- Tabular table field, or table function parameter. Implies
tabletype.
Module type tags
These tags are used at the project level to identify the module name and type. These tags are usually optional & are used to customise the package name for a module function.
@module(executable; invocable; alias@package)- Executable Scribunto modules. Docbunto documentation defaults to this type.
@script(executable)- Executable scripts (libraries) exclusively used in other modules.
@classmod(executable)- Defines a class module used to create objects. Unlike the
@typetag, class modules are externally usable by other Lua modules. @submodule(non-executable)- Lua code items to be coalesced into another module package.
@example(non-executable)- Non-executable code example, used to support documentation.
@file(non-executable)- Non-executable, generic Lua modules for consumption by other modules. Often data used by
mw.loadData. @topic(non-executable)- Static documentation file, designed for human reading. Implies the file is non-executable.
Module information tags
These tags provide general information about a module, independent of the code items the module contains.
@image(single-line; single-use)- Infobox image for {{Infobox Lua}}. Only supports single images.
@caption(single-line; single-use)- Infobox image caption for {{Infobox Lua}}.
@release(single-line; single-use)- Release status for the module, indicating stability. See the {{Status}} template for accepted values.
@author(single-line)- An author for a Lua file, responsible for code development or maintenance.
@copyright(single-line; single-use)- Copyright (corporate or crowdsourced) ownership for file. This item is not rendered.
@license(single-line; single-use)- License for the source code of the module. This item is not rendered, as it is assumed the code is licensed under CC-BY-SA 3.0 or to the public domain. However, it is a useful substitute to license boilerplate.
@require(single-line; alias@requires)- Dependency for the module. Accepts the full page name as a value.
@credit(single-line; alias@credits)- An author of external source code, whose work has been reused in the module.
@attribution(single-line)- A contributor who has contributed to a module without authoring it or any source code.
Flags
Flags are item tags that provide additional information about a Docbunto item. These do not require a value and act as item modifiers.
@local(item flag)- Lack of external access or usage for Lua items, outside of their block scope.
@export(inline flag)- Precedes export table constructors in factory functions or modules. Docbunto can infer that export field variables are package or class items, and render them as namepaths in the corresponding namespace. If you are mapping a field variable to a different key name, use the
@aliastag. @private(item flag)- Access restriction flag for Lua items. Alias for the
@privatetag. @constructor(item flag)- Class subroutine used for object creation.
@static(item flag)- Whether a class method is independent of class instantiation. In Lua, static methods are called with
Class.method(...)instead ofClass:method(...). When a factory exports functions without aself, Docbunto uses this flag to format static methods exported by a class correctly.
Override tags
These tags are added for backwards compatibility with LuaDoc. They overwrite item information deduced by Docbunto. It is generally advised to avoid using these tags.
@class(single-line; identifier)- Type override for Docbunto item, kept for backward compatibility with LuaDoc. Not to be mistaken with the
@typetag for classes. @name(single-line; identifier)- Variable name override for Docbunto item. The value of the tag is equivalent to the full internal Lua variable name within the module source, and must include any table indexes.
@alias(single-line; identifier)- External name for Docbunto item when used by other modules. When an item is exported by a module or factory function, the alias is the external index (including the
[]square bracket operators). @summary(single-line; alias@abstract,@aboutor@bug)- Brief item summary for documentation, limited to a sentence.
@description(multi-line; alias@detailsor@discussion)- Extended item description for documentation. Can span multiple lines and paragraphs.
Annotation tags
These tags provide comments & explanation for functionality, or issues adjacent to a code item. Annotations can be used globally, in a module or item comment block. However, annotations cannot be used inline in a code item.
@warning(multi-line)- Developer warning for users of missing or changing functionality.
@fixme(multi-line; alias@bug)- Developer bug reminder, to be patched in future.
@note(multi-line)- Developer notes for users or other maintainers.
@todo(multi-line)- TODO note for developer and other maintainers. If placed on a module using auto documentation, will populate Category:Modules with TODO items.
@see(multi-line)- Additional, external pages or information to direct the user to.
Other tags
@pragma(single-line)- Processing directive for Docbunto. Allows the developer to override Docbunto options from the source code.
- Accepts
[yesno]modifier affixed to tag; this can be used to deactivate options. - Usage:
@pragma all- documents private parameters (likeoptions.allset totrue)@pragma[off] code- renders article infobox and lede (likeoptions.codeset tofalse/nil)
@factory(single-line)- Factory function used for object creation. The value of this tag is the class name for the exported instance. Docbunto prefixes exported items with the class name for convenience. For class methods that are missing a
selfparameter, the@staticflag can be used.
Types and item references
Docbunto recognises fourteen types and links them to entries in the Extension:Scribunto/Lua reference manual. Other types are assumed to be custom classes defined with the @type tag in the documentation page. The noluaref option will disable these manual links if they become excessive.
- Lua whitelisted native types.
-
nilboolean(aliasbool)stringnumber(aliasint)table(aliastab)function(aliasfunc)
- Scribunto library classes.
-
DataWrapper(aliasmw.loadData)FrameLang(aliasmw.language)Uri(aliasmw.uri)HTMLBuilder(aliasmw.html)Msg(aliasmw.msg)
Item references, in the @{item|text} syntax, also support global functions and library items available to Scribunto. These will link to the relevant entry in the standard library documentation or Scribunto library documentation on MediaWiki.
Infobox customisation
A custom infobox can be generated by creating Module:Docbunto/infobox with a function:
--- Infobox for Lua modules.
-- @script infobox
-- @param {table} data Taglet documentation data.
-- @param {string} codepage Code page name.
-- @param {table} frame Template invocation frame.
-- @param {table} options Docbunto configuration options.
-- @return {table} Infobox template data for `frame:expandTemplate`.
return function(data, codepage, frame, options)
local infobox = {}
-- Infobox template name.
infobox.title = 'Infobox Lua'
-- Infobox argument list.
infobox.args = {}
infobox.args.Title = codepage
return infobox
end
Formatting
Docbunto uses a strict subset of Markdown and MediaWiki syntax. Note that Markdown cannot be used in the documentation preface (options.preface).
- Bold:
'''Bold'''or**Bold** - Italic:
''Emphasis''or*Emphasis* - Headers:
=== Header ===or### Header ###- These need to be preceded by two newlines to work.
-- This line belongs to a module documentation lede. -- -- ## Usage ## -- This line belongs to the @{Usage} section.
- Definition lists:
; Definition term.or: Definition description. - Ordered lists:
# Numbered list item - Unordered lists:
* Unsorted list item - Links:
[https://mediawiki.org MediaWiki],[[mw:MediaWiki]]or[https://commonmark.org](Markdown) - Item references:
@{item}or@{item|text} - Code:
<code>code</code>or`code` - Math:
<math display="inline">E=mc^2</math>or$$E=mc^2$$ - Chemical equations:
<chem>H2O</chem>or$$\ce{H2O}$$ - Tables:
{| ... |}(syntax is described in the tables help page)
Limitations:
- Code blocks are not supported - this simplifies parsing of text paragraphs. The
@usagetag is available for documenting code examples. - Markdown tables and open headers (using dashes below the header text) are too complex to parse without a full Markdown parser, and will not be added.
Documentation
Package items
docbunto._main( args )(function)- Template entrypoint for Template:Docbunto.
- Parameter:
argsArguments (table) - Returns: Module documentation output. (string)
docbunto.main( frame )(function)- Entrypoint for the module.
- Parameter:
frameModule frame. (Frame) - Returns: Module documentation output. (string)
docbunto.build( modname, options )(function)- Scribunto documentation generator entrypoint.
- Parameters:
docbunto.taglet( modname, options )(function)- Docbunto taglet parser for Scribunto modules.
- Parameters:
- Errors:
- Returns: Module documentation data. (table)
docbunto.doclet( data, options )(function)- Doclet renderer for Docbunto taglet data.
- Parameters:
- Returns: Wikitext documentation output. (string)
docbunto.tags(table)- Token dictionary for Docbunto tags. Maps Docbunto tag names to tag tokens.
- Multi-line tags use the
'M'token. - Multi-line preformatted tags use the
'ML'token. - Identifier tags use the
'ID'token. - Single-line tags use the
'S'token. - Flags use the
'N'token. - Type tags use the
'T'token.
- Multi-line tags use the
Other items
options(table)- Configuration options.
- Fields:
options.allInclude local items in documentation. (boolean; optional)options.autodocWhether the module is being called to automatically provide missing documentation. (boolean; optional)options.boilerplateRemoval of boilerplate (license block comments). (boolean; optional)options.captionInfobox image caption. (string; optional)options.codeOnly document Docbunto code items - exclude article infobox and lede from rendered documentation. Permits article to be edited in VisualEditor. (boolean; optional)options.colonFormat tags with a:suffix and without the@prefix. This bypasses the "doctag soup" some authors complain of. (boolean; optional)options.imageInfobox image. (string; optional)options.boxInclude infobox in output. (boolean; optional)options.footerAppend a horizontal line & "generated by Docbunto" to the bottom of the output. (boolean; optional)options.noluarefDon't link to the Lua reference manual for types. (boolean; optional)options.cardInclude endmatter card in the output at the end of the documentation. (boolean; optional)options.plainDisable Markdown formatting in documentation. (boolean; optional)options.prefacePreface text to insert between lede & item documentation, used to provide usage and code examples. (string; optional)options.simpleLimit documentation to descriptions only. Removes documentation of subitem tags such as@paramand@field(see list). (boolean; optional)options.sortSort documentation items in alphabetical order. (boolean; optional)options.stripRemove table index in documentation. (boolean; optional)options.ulistIndent subitems as<ul>lists (LDoc/JSDoc behaviour). (boolean; optional)options.verboseShow the code description of a package function when documenting function modules. (boolean; optional)
Documentation above is auto generated
--- Docbunto is an automatic documentation generator for Scribunto modules.
-- The module is based on LuaDoc and LDoc. It produces documentation in
-- the form of MediaWiki markup, using `@tag`-prefixed comments embedded
-- in the source code of a Scribunto module. The taglet parser & doclet
-- renderer Docbunto uses are also publicly exposed to other modules.
--
-- Docbunto code items are introduced by a block comment (`--[[]]--`), an
-- inline comment with three hyphens (`---`), or an inline `@tag` comment.
-- The module can use static code analysis to infer variable names, item
-- privacy (`local` keyword), tables (`{}` constructor) and functions
-- (`function` keyword). MediaWiki and Markdown formatting is supported.
--
-- Items are usually rendered in the order they are defined, if they are
-- public items, or emulated classes extending the Lua primitives. There
-- are many customisation options available to change Docbunto behaviour.
--
-- @module docbunto
-- @alias p
-- @require Module:I18n
-- @require Module:Lua_lexer
-- @require Module:Unindent
-- @require Module:Yesno
-- @require Module:Arguments
-- @require Module:Documentation
-- @author [[wikia:dev:User:8nml|8nml]] (Fandom Dev Wiki)
-- @author [[User:Awesome Aasim|Awesome Aasim]] (Wikimedia port)
-- @attribution [https://github.com/stevedonovan @stevedonovan] ([https://github.com/stevedonovan/LDoc GitHub])
-- @release beta
-- <nowiki>
local p = {}
-- Module dependencies.
local title = mw.title.getCurrentTitle()
local i18n = require('Module:I18n').loadMessages('Docbunto'):useContentLang()
local references = mw.loadData('Module:Docbunto/references')
local lexer = require('Module:Lua lexer')
local unindent = require('Module:Unindent')
local yesno = require('Module:Yesno')
local doc = require('Module:Documentation')
local fileTooBig
local usingOptionsContent
local hasTodoItems
-- Module variables.
local DEFAULT_TITLE = title.namespace == 828 and doc.getEnvironment({}).templateTitle.text or ''
local DEFAULT_VARIABLE = DEFAULT_TITLE
:gsub('^%u', mw.ustring.lower)
:gsub('%s', '_'):gsub('%p', '.')
local frame, gsub, match
local syntaxhighlightPresent = mw.getCurrentFrame()
:preprocess("<syntaxhighlight lang=\"lua\">-- Lua</syntaxhighlight>")
~= "<syntaxhighlight lang=\"lua\">-- Lua</syntaxhighlight>"
-- Docbunto variables & tag tokens.
local TAG_MULTI = 'M'
local TAG_ID = 'ID'
local TAG_SINGLE = 'S'
local TAG_TYPE = 'T'
local TAG_FLAG = 'N'
local TAG_MULTI_LINE = 'ML'
-- Docbunto processing patterns.
local patterns = {}
--- Configuration options.
-- @table options
-- @field[opt] {boolean} options.all Include local items in
-- documentation.
-- @field[opt] {boolean} options.autodoc Whether the module is being
-- called to automatically provide missing documentation.
-- @field[opt] {boolean} options.boilerplate Removal of
-- boilerplate (license block comments).
-- @field[opt] {string} options.caption Infobox image caption.
-- @field[opt] {boolean} options.code Only document Docbunto code
-- items - exclude article infobox and lede from
-- rendered documentation. Permits article to be
-- edited in VisualEditor.
-- @field[opt] {boolean} options.colon Format tags with a `:` suffix
-- and without the `@` prefix. This bypasses the "doctag
-- soup" some authors complain of.
-- @field[opt] {string} options.image Infobox image.
-- @field[opt] {boolean} options.box Include infobox in output.
-- @field[opt] {boolean} options.footer Append a horizontal line &
-- "generated by Docbunto" to the bottom of the output.
-- @field[opt] {boolean} options.noluaref Don't link to the [[mw:
-- Extension:Scribunto/Lua reference manual|Lua reference manual]] for types.
-- @field[opt] {boolean} options.card Include endmatter card in the
-- output at the end of the documentation.
-- @field[opt] {boolean} options.plain Disable Markdown formatting
-- in documentation.
-- @field[opt] {string} options.preface Preface text to insert
-- between lede & item documentation, used to provide
-- usage and code examples.
-- @field[opt] {boolean} options.simple Limit documentation to
-- descriptions only. Removes documentation of
-- subitem tags such as `@param` and `@field` ([[#Item
-- subtags|see list]]).
-- @field[opt] {boolean} options.sort Sort documentation items in
-- alphabetical order.
-- @field[opt] {boolean} options.strip Remove table index in
-- documentation.
-- @field[opt] {boolean} options.ulist Indent subitems as `<ul>`
-- lists (LDoc/JSDoc behaviour).
-- @field[opt] {boolean} options.verbose Show the code
-- description of a package function when documenting
-- function modules.
-- Docbunto private utilities.
local utils = {}
--- @{string.find} optimisation for @{string} functions.
-- @function utils.strfind_wrap
-- @param {function} strfunc String library function.
-- @return {function} Function wrapped in @{string.find} check.
-- @local
function utils.strfind_wrap(func)
return function(...)
local arg = {...}
if string.find(arg[1], arg[2]) then
return func(...);
end
end
end
--- String concatenation utility.
-- @function join
-- @param {sequence<string>} tbl Table of string segments
-- @return {function} A concatenated string.
-- @local
function utils.join(tbl)
return table.concat(tbl, '')
end
--- Pattern configuration function.
-- Resets patterns for each documentation build.
-- @function utils.configure_patterns
-- @param {options} options Configuration options.
-- @local
function utils.configure_patterns(options)
-- Setup Unicode or ASCII character encoding (optimisation).
gsub = utils.strfind_wrap(
options.unicode
and mw.ustring.gsub
or string.gsub
)
match = utils.strfind_wrap(
options.unicode
and mw.ustring.match
or string.match
)
patterns.DOCBUNTO_SUMMARY =
options.iso639_th
and '^[^ ]+'
or
options.unicode
and '^[^.։。।෴۔።]+[.։。।෴۔።]?'
or '^[^.]+%.?'
patterns.DOCBUNTO_CONCAT = options.iso639_th and '' or ' '
patterns.DOCBUNTO_TYPE = '^{({*[^}]+}*)}%s*'
patterns.DOCBUNTO_WIKITEXT = '^[{|!}:#*=]+[%s-}]+'
-- Setup parsing tag patterns with colon mode support.
patterns.DOCBUNTO_TAG = options.colon and '^%s*(%w+):' or '^%s*@(%w+)'
patterns.DOCBUNTO_TAG_COMMENT = '^[-%s]*' .. patterns.DOCBUNTO_TAG:sub(2)
patterns.DOCBUNTO_TAG_VALUE = patterns.DOCBUNTO_TAG .. '(.*)'
patterns.DOCBUNTO_TAG_MOD_VALUE = patterns.DOCBUNTO_TAG .. '%[([^%]]*)%](.*)'
patterns.DOCBUNTO_TAG_OPTION_VALUE = '(%[[^%]]*%])%s*(.*)'
end
--- Tag processor function.
-- @function utils.process_tag
-- @param {string} str Tag string to process.
-- @return {table} Tag object.
-- @local
function utils.process_tag(str)
local tag = {}
if str:find(patterns.DOCBUNTO_TAG_MOD_VALUE) then
tag.name, tag.modifiers, tag.value = str:match(patterns.DOCBUNTO_TAG_MOD_VALUE)
local modifiers = {}
for m in tag.modifiers:gmatch('[^%s,]+') do
local i = m:find('=')
if i then
local key = mw.text.trim(m:sub(1, i - 1))
local val = mw.text.trim(m:sub(i + 1, #m))
modifiers[key] = val
else
modifiers[m] = true
end
end
if modifiers.optchain then
modifiers.opt = true
modifiers.optchain = nil
end
tag.modifiers = modifiers
else
tag.name, tag.value = str:match(patterns.DOCBUNTO_TAG_VALUE)
end
tag.value = mw.text.trim(tag.value)
if p.tags._type_alias[tag.name] then
if p.tags._type_alias[tag.name] ~= 'variable' then
tag.value = p.tags._type_alias[tag.name] .. ' ' .. tag.value
tag.name = 'field'
end
if tag.value:match('^%S+') ~= '...' then
tag.value = tag.value:gsub('^(%S+)', '{%1}')
end
end
tag.name = p.tags._alias[tag.name] or tag.name
if tag.name ~= 'usage' and tag.value:find(patterns.DOCBUNTO_TYPE) then
tag.type = tag.value:match(patterns.DOCBUNTO_TYPE)
if tag.type:find('^%?') then
tag.type = tag.type:sub(2) .. '|nil'
end
tag.value = tag.value:gsub(patterns.DOCBUNTO_TYPE, '')
end
if (tag.name == 'param' or tag.name == 'field') and tag.value:find(patterns.DOCBUNTO_TAG_OPTION_VALUE) then
tag.value = tag.value:gsub(patterns.DOCBUNTO_TAG_OPTION_VALUE, '%1 %2')
tag.modifiers = tag.modifiers or {}
tag.modifiers.opt = true
end
if p.tags[tag.name] == TAG_FLAG then
tag.value = true
end
return tag
end
--- Module info extraction utility.
-- @function utils.extract_info
-- @param {table} documentation Package doclet info.
-- @return {table} Information name-value map.
-- @local
function utils.extract_info(documentation)
local info = {}
for _, tag in ipairs(documentation.tags) do
if p.tags._module_info[tag.name] then
if info[tag.name] then
if not info[tag.name]:find('^%* ') then
info[tag.name] = '* ' .. info[tag.name]
end
info[tag.name] = info[tag.name] .. '\n* ' .. tag.value
else
info[tag.name] = tag.value
end
end
end
return info
end
--- Type extraction utility.
-- @function utils.extract_types
-- @param {table} item Item documentation data.
-- @return {sequence<string>} Item types.
-- @local
function utils.extract_types(item)
local item_types = {}
for _, tag in ipairs(item.tags) do
if p.tags[tag.name] == TAG_TYPE then
item_types[1] = tag.name
if tag.name == 'variable' then
local implied_local = utils.process_tag('@local')
table.insert(item.tags, mw.clone(implied_local))
item.tags['local'] = mw.clone(implied_local)
end
if
p.tags._generic_tags[item_types[1]] and
not p.tags._project_level[item_types[1]] and
tag.type
then
item_types[#item_types + 1] = tag.type
end
break
end
end
return item_types
end
--- Name extraction utility.
-- @function utils.extract_name
-- @param {table} item Item documentation data.
-- @param {boolean} project Whether the item is project-level.
-- @return {string} Item name.
-- @local
function utils.extract_name(item, opts)
opts = opts or {}
local item_name
for _, tag in ipairs(item.tags) do
if p.tags[tag.name] == TAG_TYPE then
item_name = tag.value; break
end
end
if item_name or not opts.project then
return item_name
end
item_name = item.code:match('\nreturn%s+([%w_]+)')
if item_name == 'p' and not item.tags['alias'] then
local implied_alias = { name = 'alias', value = 'p' }
item.tags['alias'] = implied_alias
table.insert(item.tags, implied_alias)
end
item_name = (item_name and item_name ~= 'p')
and item_name
or item.filename
:gsub('^' .. mw.site.namespaces[828].name .. ':', '')
:gsub('^(%u)', mw.ustring.lower)
:gsub('/', '.'):gsub(' ', '_')
return item_name
end
--- Source code utility for item name detection.
-- @function utils.deduce_name
-- @param {string} tokens Stream tokens for first line.
-- @param {string} index Stream token index.
-- @param {table} opts Configuration options.
-- @param[opt] {boolean} opts.lookahead Whether a variable name succeeds the index.
-- @param[opt] {boolean} opts.lookbehind Whether a variable name precedes the index.
-- @return {string} Item name.
-- @local
function utils.deduce_name(tokens, index, opts)
local name = ''
if opts.lookbehind then
for i2 = index, 1, -1 do
if tokens[i2].type ~= 'keyword' then
name = tokens[i2].data .. name
else
break
end
end
elseif opts.lookahead then
for i2 = index, #tokens do
if tokens[i2].type ~= 'keyword' and not tokens[i2].data:find('^%(') then
name = name .. tokens[i2].data
else
break
end
end
end
return name
end
--- Parses Lua item names to extract a hierarchy.
--- @function utils.parse_hierarchy
-- @param {string} item_name Documentation item name.
-- @return {sequence<string>} List of global keys to index the
-- variable reference.
-- @local
function utils.parse_hierarchy(item_name)
local hierarchy = {}
for _, lexeme in ipairs(lexer(item_name)) do
if lexeme.type == 'ident' or lexeme.type == 'string' or lexeme.type == 'number' then
hierarchy[#hierarchy + 1] = lexeme.text
end
end
return hierarchy
end
--- Code analysis utility.
-- @function utils.code_heuristic
-- @param {table} item Item documentation data.
-- @local
function utils.code_heuristic(item)
local is_section = item.tags['private'] or item.tags['local'] or item.type == 'type'
if item.name and item.type and is_section then
return
end
local tokens = lexer(item.code:match('^[^\n]*'))[1]
local t, i = tokens[1], 1
local item_name, item_type
local lexemes = {}
while t do
if t.type ~= 'whitespace' then
lexemes[#lexemes + 1] = tokens[i]
end
t, i = tokens[i + 1], i + 1
end
t, i = lexemes[1], 1
while t do
if t.data == '=' and not item.name then
item_name = utils.deduce_name(lexemes, i - 1, { lookbehind = true })
end
if t.data == 'function' and not item.name then
item_type = 'function'
if lexemes[i + 1] and lexemes[i + 1].data ~= '(' then
item_name = utils.deduce_name(lexemes, i + 1, { lookahead = true })
end
end
if t.data == '{' or t.data == '{}' and not item.type then
item_type = 'table'
end
if t.data == 'local' and not is_section then
local implied_local = utils.process_tag('@local')
table.insert(item.tags, implied_local)
item.tags['local'] = mw.clone(implied_local)
end
t, i = lexemes[i + 1], i + 1
end
item.name = item.name or item_name or ''
item.type = item.type or item_type
if #(item.types or {}) == 0 then
item.types[1] = item.type
end
end
--- Array hash map conversion utility.
-- @function utils.hash_map
-- @param {table} item Item documentation data array.
-- @return {table} Item documentation data map.
-- @local
function utils.hash_map(array)
local map = array
for _, element in ipairs(array) do
if map[element.name] and not map[element.name].name then
table.insert(map[element.name], mw.clone(element))
elseif map[element.name] and map[element.name].name then
map[element.name] = { map[element.name], mw.clone(element) }
else
map[element.name] = mw.clone(element)
end
end
return map
end
--- Item export utility.
-- @function utils.export_item
-- @param {table} documentation Package documentation data.
-- @param {string} item_reference Identifier name for item.
-- @param {string} item_index Identifier name for item.
-- @param {string} item_alias Export alias for item.
-- @param {boolean} factory_item Whether the documentation item is a factory function.
-- @local
function utils.export_item(documentation, item_reference, item_index, item_alias, factory_item)
for _, item in ipairs(documentation.items) do
if item_reference == item.name then
item.tags['local'] = nil
item.tags['private'] = nil
for index, tag in ipairs(item.tags) do
if p.tags._privacy_tags[tag.name] then
table.remove(item.tags, index)
end
end
item.type, item.types[1] = 'member', 'member'
local accessor
if item_alias:find('^%[') then
accessor = ''
else
local is_class = documentation.type == 'classmod' or factory_item
if is_class and not item.tags['static'] then
accessor = ':'
else
accessor = '.'
end
end
if factory_item then
item.alias = utils.join{
documentation.items[item_index].tags['factory'].value,
accessor,
item_alias
}
else
item.alias = utils.join{
((documentation.tags['alias'] or {}).value or documentation.name),
accessor,
item_alias
}
end
item.hierarchy = utils.parse_hierarchy(item.alias)
break
end
end
end
--- Subitem tag correction utility.
-- @function utils.correct_subitem_tag
-- @param {table} item Item documentation data.
-- @local
function utils.correct_subitem_tag(item)
local field_tag = item.tags['field']
if item.type ~= 'function' or not field_tag then
return
end
if field_tag.name then
field_tag.name = 'param'
else
for _, tag_el in ipairs(field_tag) do
tag_el.name = 'param'
end
end
local param_tag = item.tags['param']
if param_tag and not param_tag.name then
if field_tag.name then
table.insert(param_tag, field_tag)
else
for _, tag_el in ipairs(field_tag) do
table.insert(param_tag, tag_el)
end
end
elseif param_tag and param_tag.name then
if field_tag.name then
param_tag = { param_tag, field_tag }
else
for i, tag_el in ipairs(field_tag) do
if i == 1 then
param_tag = { param_tag }
end
for _, tag_el in ipairs(field_tag) do
table.insert(param_tag, tag_el)
end
end
end
else
param_tag = field_tag
end
item.tags['field'] = nil
end
--- Item override tag utility.
-- @function utils.override_item_tag
-- @param {table} item Item documentation data.
-- @param {string} name Tag name.
-- @param[opt] {string} alias Target alias for tag.
-- @local
function utils.override_item_tag(item, name, alias)
if item.tags[name] then
item[alias or name] = item.tags[name].value
end
end
--- Identity function.
-- @function utils.identity
-- @param x The variable by reference.
-- @return The parameter `x` by reference.
-- @local
function utils.identity(x)
return x
end
--- Argument processing
-- @param {string} funcName name of function
-- @return function to process arguments
-- @local
local function makeInvokeFunc(funcName)
return function (f)
local args = require("Module:Arguments").getArgs(f, {
valueFunc = function (key, value)
if type(value) == 'string' then
value = value:match('^%s*(.-)%s*$') -- Remove whitespace.
if key == 'heading' or value ~= '' then
return value
else
return nil
end
else
return value
end
end
})
return p[funcName](args)
end
end
-- Docbunto rendering logic.
local frontend = {}
--- Markdown header converter.
-- @function urils.markdown_header
-- @param {string} hash Leading hash.
-- @param {string} text Header text.
-- @return {string} MediaWiki header.
-- @local
function frontend.markdown_header(hash, text)
local symbol = '='
return utils.join{
'\n',
symbol:rep(#hash), ' ', text, ' ', symbol:rep(#hash),
'\n'
}
end
--- Item reference formatting.
-- @function urils.item_reference
-- @param {string} ref Item reference.
-- @return {string} Internal MediaWiki link to article item.
-- @local
function frontend.item_reference(ref)
local temp = mw.text.split(ref, '|')
local item = temp[1]
local text = temp[2] or temp[1]
if references.items[item] then
local interwiki = 'mw'
item = interwiki .. references.items[item]
else
item = '#' .. item
end
return mw.text.tag{
name = 'code',
content = utils.join{ '[[', item, '|', text, ']]' }
}
end
--- Doclet type reference preprocessor.
-- Formats types with links to the [[mw:Extension:Scribunto/Lua reference manual|Lua reference manual]].
-- @function frontend.type_reference
-- @param {table} item Item documentation data.
-- @param {options} options Configuration options.
-- @local
function frontend.type_reference(item, options)
local interwiki = 'mw:'
if
not options.noluaref and
item.value and
item.value:match('^%S+') == mw.text.tag('code', nil, '...')
then
item.value = item.value:gsub('^(%S+)', mw.text.tag{
name = 'code',
content = utils.join{ '[[', interwiki, 'Lua reference manual#varargs|...]]' }
})
end
-- Handle tag type references.
item.types = item.types or item.type and { item.type }
if not item.types then
return
end
for index, item_type in ipairs(item.types) do
item.types[index] = item.types[index]:gsub('[%w_. ]+', function(item_type)
local data = references.types[item_type]
local name = data and data.name or item_type
if not name:match('%.') and not name:match('^%u') and data then
name = i18n:msg('type-' .. name)
end
if data and not options.noluaref then
return utils.join{ '[[', interwiki, data.link, '|', name , ']]' }
elseif
item_type:find(i18n:msg('line-no'):gsub('$1', ''))
then
return utils.join { '[[' , options.file , '#L-', item_type:sub(6), '|', name, ']]' }
elseif
not options.noluaref and
not p.tags._generic_tags[item_type]
then
return utils.join { '[[#', item_type, '|', name, ']]' }
end
end)
end
item.type = table.concat(item.types, i18n:msg('separator-dot'))
end
--- Markdown preprocessor to MediaWiki format.
-- @function frontend.markdown
-- @param {string} str Unprocessed Markdown string.
-- @return {string} MediaWiki-compatible markup with HTML formatting.
-- @local
function frontend.markdown(str)
-- Bold & italic tags.
str = str:gsub('%*%*%*([^\n*]+)%*%*%*', '<b><i>%1</i></b>')
str = str:gsub('%*%*([^\n*]+)%*%*', '<b>%1</b>')
str = str:gsub('%*([^\n*]+)%*', '<i>%1</i>')
-- Self-closing header support.
str = str:gsub('%f[^\n%z](#+) *([^\n#]+) *#+%s', frontend.markdown_header)
-- External and internal links.
str = str:gsub('%[([^\n%]]+)%]%(([^\n][^\n)]-)%)', '[%2 %1]')
str = str:gsub('%{@link%s+([^}]+)%}', '[%1]')
str = str:gsub('%@{([^\n}]+)}', frontend.item_reference)
-- Programming & scientific notation.
str = str:gsub('%f["`]`([^\n`]+)`%f[^"`]', '<code><nowiki>%1</nowiki></code>')
str = str:gsub('%$%$([^\n$]+)%$%$', '<math display="inline">%1</math>')
-- Strikethroughs.
str = str:gsub('~~([^\n~]+)~~', '<del>%1</del>')
-- HTML output.
return str
end
--- Doclet function item preprocessor.
-- Formats item name as a function call with top-level arguments.
-- @function frontend.pretask_function_name
-- @param {table} item Item documentation data.
-- @param {options} options Configuration options.
-- @local
function frontend.pretask_function_name(item, options)
local target = item.alias and 'alias' or 'name'
item[target] = item[target] .. '('
if
item.tags['param'] and
item.tags['param'].value and
not item.tags['param'].value:find('^[%w_]+[.[]')
then
if (item.tags['param'].modifiers or {})['opt'] then
local optional = mw.html.create('span')
optional:css('opacity', '0.65')
optional:wikitext(item.tags['param'].value:match('^(%S+)'))
item[target] = item[target] .. tostring(optional)
else
item[target] = item[target] .. item.tags['param'].value:match('^(%S*)')
end
elseif item.tags['param'] then
for index, tag in ipairs(item.tags['param']) do
if not tag.value:find('^[%w_]+[.[]') then
if (tag.modifiers or {})['opt'] then
local param = mw.html.create('span')
param:css('opacity', '0.65')
param:wikitext((index > 1 and ', ' or '') .. tag.value:match('^%S+'))
item[target] = item[target] .. tostring(param)
else
item[target] = item[target] .. (index > 1 and ', ' or '') .. tag.value:match('^(%S+)')
end
end
end
end
item[target] = item[target] .. ')'
end
--- Doclet parameter/field subitem preprocessor.
-- Indents and wraps variable prefix with `code` tag.
-- @function pretask_variable_prefix
-- @param {table} item Item documentation data.
-- @param {options} options Configuration options.
-- @local
function frontend.pretask_variable_prefix(item, options)
local indent_symbol = options.ulist and '*' or ':'
local indent_level, indentation, item_id
if item.value then
indent_level = item.value:match('^%S*') == '...'
and 0
or select(2, item.value:match('^%S*'):gsub('[.[]', ''))
indentation = indent_symbol:rep(indent_level)
item_id = options.item_id
item.value = indentation .. item.value:gsub(
'^(%S+)',
mw.text.tag{
name = 'code',
attrs = { id = item_id .. '~%1' },
content = '%1'
},
1
)
elseif item then
for _, item_el in ipairs(item) do
frontend.pretask_variable_prefix(item_el, options)
end
end
end
--- Doclet usage subitem preprocessor.
-- Formats usage example with `<syntaxhighlight>` tag.
-- @function frontend.pretask_usage_highlight
-- @param {table} item Item documentation data.
-- @param {options} options Configuration options.
-- @local
function frontend.pretask_usage_highlight(item, options)
if item.value then
item.value = unindent(mw.text.trim(item.value))
if item.value:find('^{{.+}}$') then
item.value = item.value:gsub('=', mw.text.nowiki)
local multi_line = item.value:find('\n') and '|m = 1|' or '|'
if item.value:match('^{{([^:]+)') == '#invoke' then
item.value = item.value:gsub('^{{#invoke:', '{{mlx' .. multi_line)
else
if options.entrypoint then
item.value = item.value:gsub('^([^|]+)|%s*([^|}]-)(%s*)([|}])','%1|"%2"%3%4')
end
item.value = item.value:gsub('^{{', '{{tlx' .. multi_line)
end
local highlight_class = tonumber(mw.site.currentVersion:match('^%d%.%d+')) > 1.19
and 'mw-highlight'
or 'mw-geshi'
if item.value:find('\n') then
local highlight_div = mw.html.create('div')
highlight_div.addClass(highlight_class)
highlight_div.addClass('mw-content-ltr')
highlight_div:attr('dir', 'ltr')
highlight_div:wikitext(item.value)
item.value = tostring(highlight_div)
else
item.value = mw.text.tag{
name = 'span',
attrs = { class = 'code' },
content = item.value
}
end
else
local highlight_tag = mw.html.create('syntaxhighlight')
highlight_tag:attr('lang', 'lua')
if not item.value:find('\n') then
highlight_tag:attr('inline', 'inline')
end
highlight_tag:wikitext(item.value)
item.value = tostring(highlight_tag)
end
elseif item then
for _, item_el in ipairs(item) do
frontend.pretask_usage_highlight(item_el, options)
end
end
end
--- Doclet error subitem preprocessor.
-- Formats line numbers (`{#}`) in error tag values.
-- @function frontend.pretask_error_line
-- @param {table} item Item documentation data.
-- @local
function frontend.pretask_error_line(item, options)
if item.name then
local line
if item.modifiers and item.modifiers.line then
line = item.modifiers.line
end
for mod in pairs(item.modifiers or {}) do
if mod:find('^%d+$') then line = mod end
end
if line then
if item.types then
item.types[#item.types + 1] = i18n:msg('line-no', line)
else
item.type = i18n:msg('line-no', line)
end
end
elseif item then
for _, item_el in ipairs(item) do
frontend.pretask_error_line(item_el, options)
end
end
end
--- Doclet item renderer.
-- @function frontend.render_item
-- @param {table} stream Wikitext documentation stream.
-- @param {table} item Item documentation data.
-- @param {options} options Configuration options.
-- @param[opt] {function} pretask Item data preprocessor.
-- @local
function frontend.render_item(stream, item, options, pretask)
if pretask then pretask(item, options) end
local item_name = item.alias or item.name
frontend.type_reference(item, options)
local item_type = item.type
for _, name in ipairs(p.tags._subtype_hierarchy) do
if item.tags[name] then
item_type = item_type .. i18n:msg('separator-dot') .. name
end
end
item_type = i18n:msg('parentheses', item_type)
if options.strip and item.export and item.hierarchy then
item_name = item_name:gsub('^[%w_]+[.[]?', '')
end
-- add single spaces between parentheses similar to
-- MediaWiki documentation
if not item_name:find( '()', 1, true ) then
item_name = item_name:gsub('%(', '( ')
item_name = item_name:gsub('%)', ' )')
end
item_name = item_name:gsub('%[', '[ ')
item_name = item_name:gsub('%]', ' ]')
local code_node = mw.html.create('code')
:attr('id', options.item_id)
:wikitext(item_name)
if not usingOptionsContent then
local span_node = stream:wikitext(';')
:tag('span')
:addClass('plainlinks docbunto-link')
if fileTooBig or not syntaxhighlightPresent then
span_node:wikitext('[{{fullurl:' .. options.file .. '|action=edit#mw-ce-l' .. item.lineno .. '}} ' .. tostring(code_node) .. ']')
elseif title.namespace == 828 and options.file == title.text then
span_node:wikitext('[[' .. '#L-' .. item.lineno .. '|' .. tostring(code_node) .. ']]')
else
span_node:wikitext('[[' .. options.file .. '#L-' .. item.lineno .. '|' .. tostring(code_node) .. ']]')
end
else
stream:wikitext(';' .. tostring(code_node))
end
stream
:wikitext(item_type)
:newline()
if (#(item.summary or '') + #item.description) ~= 0 then
local separator = #(item.summary or '') ~= 0 and #item.description ~= 0
and (item.description:find(patterns.DOCBUNTO_WIKITEXT) and '\n' or patterns.DOCBUNTO_CONCAT)
or ''
local intro = (item.summary or '') .. separator .. item.description
intro = intro
:gsub('\n([{:#*])', '\n:%1')
:gsub('\n\n([^=])', '\n:%1')
stream:wikitext(':' .. intro):newline()
end
end
--- Doclet tag renderer.
-- @function frontend.render_tag
-- @param {table} stream Wikitext documentation stream.
-- @param {string} name Item tag name.
-- @param {table} tag Item tag data.
-- @param {options} options Configuration options.
-- @param[opt] {function} pretask Item data preprocessor.
-- @local
function frontend.render_tag(stream, name, tag, options, pretask)
if pretask then pretask(tag, options) end
if tag.value then
frontend.type_reference(tag, options)
local tag_name = i18n:msg('tag-' .. name, '1')
-- Handle ul/ol/dl comment support.
stream:wikitext(':')
:tag('b')
:wikitext(tag_name)
:done():done()
:wikitext(i18n:msg('separator-semicolon'))
:wikitext((mw.text.trim(tag.value):gsub('\n([{:#*])', '\n:%1')))
if tag.value:find('\n[{:#*]') and (tag.type or (tag.modifiers or {})['opt']) then
stream:newline():wikitext(':')
end
-- Type and optional appositive for tag.
if tag.type and (tag.modifiers or {})['opt'] then
stream:wikitext(i18n:msg{
key = 'parentheses',
args = {
tag.type ..
i18n:msg('separator-colon') ..
i18n:msg('optional')
}
})
elseif tag.type then
stream:wikitext(i18n:msg{
key = 'parentheses',
args = { tag.type }
})
elseif (tag.modifiers or {})['opt'] then
stream:wikitext(i18n:msg{
key = 'parentheses',
args = { i18n:msg('optional') }
})
end
stream:newline()
else
local tag_name = i18n:msg('tag-' .. name, tostring(#tag))
stream
:wikitext(':')
:tag('b')
:wikitext(tag_name)
:done():done()
:wikitext(i18n:msg('separator-semicolon'))
:newline()
for _, tag_el in ipairs(tag) do
frontend.type_reference(tag_el, options)
local marker = options.ulist and '*' or ':'
stream:wikitext(':' .. marker .. tag_el.value:gsub('\n([{:#*])', '\n:' .. marker .. '%1'))
if tag_el.value:find('\n[{:#*]') and (tag_el.type or (tag_el.modifiers or {})['opt']) then
stream:newline():wikitext(':' .. marker .. (tag_el.value:match('^[*:]+') or ''))
end
if tag_el.type and (tag_el.modifiers or {})['opt'] then
stream:wikitext(i18n:msg{
key = 'parentheses',
args = {
tag_el.type ..
i18n:msg('separator-colon') ..
i18n:msg('optional')
}
})
elseif tag_el.type then
stream:wikitext(i18n:msg{
key = 'parentheses',
args = { tag_el.type }
})
elseif (tag_el.modifiers or {})['opt'] then
stream:wikitext(i18n:msg{
key = 'parentheses',
args = { i18n:msg('optional') }
})
end
stream:newline()
end
end
end
--- Template entrypoint for [[Template:Docbunto]].
-- @function p._main
-- @param {table} args Arguments
-- @return {string} Module documentation output.
function p._main(args)
frame = mw.getCurrentFrame()
local modname = mw.text.trim(args[1] or args.file or DEFAULT_TITLE)
local options = {}
options.file = modname
options.all = yesno(args.all or false)
options.autodoc = yesno(args.autodoc or false)
options.boilerplate = yesno(args.boilerplate or options.autodoc or false)
options.box = yesno(args.box or args.infobox or options.autodoc or false)
options.caption = args.caption
options.card = yesno(args.card or (title.namespace == 828 and not options.autodoc))
options.code = yesno(args.code or false)
options.colon = yesno(args.colon or false)
options.footer = yesno(args.footer or title.namespace == 828)
options.image = args.image
options.migration = yesno(args.migration or false)
options.noluaref = yesno(args.noluaref or false)
options.plain = yesno(args.plain or false)
options.preface = args.preface
options.simple = yesno(args.simple or false)
options.sort = yesno(args.sort or false)
options.strip = yesno(args.strip or false)
options.verbose = yesno(args.verbose or false)
options.ulist = yesno(args.ulist or false)
return p.build(modname, options)
end
--- Entrypoint for the module.
-- @function p.main
-- @param {Frame} frame Module frame.
-- @return {string} Module documentation output.
p.main = makeInvokeFunc('_main')
--- Scribunto documentation generator entrypoint.
-- @function p.build
-- @param[opt] {string} modname Module page name (without namespace).
-- Default: second-level subpage.
-- @param[opt] {options} options Configuration options.
function p.build(modname, options)
modname = modname or DEFAULT_TITLE
options = options or {}
local tagdata = p.taglet(modname, options)
local docdata = p.doclet(tagdata, options)
return docdata
end
--- Docbunto taglet parser for Scribunto modules.
-- @function p.taglet
-- @param[opt] {string} modname Module page name (without namespace).
-- @param[opt] {options} options Configuration options.
-- @error[line=1081] {string} 'Lua source code not found in $1'
-- @error[line=1087] {string} 'documentation markup for Docbunto not found in $1'
-- @return {table} Module documentation data.
function p.taglet(modname, options)
modname = modname or DEFAULT_TITLE
options = options or {}
local filepath = mw.site.namespaces[828].name .. ':' .. modname
local content = mw.title.new(filepath):getContent()
-- Content checks.
if options.content then
content = options.content
usingOptionsContent = true
else
content = mw.title.new(filepath):getContent() or error(i18n:msg('no-content', filepath), 0)
end
-- Syntax highlighting is disabled for files above a specific size
fileTooBig = content:len() > 102400
if
not content:match('%-%-%-') and
not content:match(options.colon and '%w+:' or '@%w+')
then
error(i18n:msg('no-markup', filepath))
end
-- Remove leading escapes.
content = content:gsub('^%-%-+%s*<[^>]+>\n', '')
-- Remove closing pretty comments.
content = content:gsub('\n%-%-%-%-%-+(\n[^-]+)', '\n-- %1')
-- Remove boilerplate block comments.
if options.boilerplate then
content = content:gsub('^%-%-%[=*%[\n.-\n%-?%-?%]%=*]%-?%-?%s+', '')
content = content:gsub('%s+%-%-%[=*%[\n.-\n%-?%-?%]%=*]%-?%-?$', '')
end
-- Configure patterns for colon mode and Unicode character encoding.
options.unicode = type(content:find('[^%w%c%p%s]+')) == 'number'
options.iso639_th = type(content:find('\224\184[\129-\155]')) == 'number'
utils.configure_patterns(options)
-- Content lexing.
local lines = lexer(content)
local tokens = {}
local dummy_token = {
data = '',
posFirst = 1,
posLast = 1
}
local token_closure = 0
for _, line in ipairs(lines) do
if #line == 0 then
dummy_token.type = token_closure == 0
and 'whitespace'
or tokens[#tokens].type
table.insert(tokens, mw.clone(dummy_token))
else
for _, token in ipairs(line) do
if token.data:find('^%[=*%[$') or token.data:find('^%-%-%[=*%[$') then
token_closure = 1
end
if token.data:find(']=*]') then
token_closure = 0
end
table.insert(tokens, token)
end
end
end
-- Start documentation data.
local documentation = {}
documentation.filename = filepath
documentation.description = ''
documentation.code = content
documentation.comments = {}
documentation.tags = {}
documentation.items = {}
local line_no = 0
local item_index = 0
-- Taglet tracking variables.
local start_mode = true
local comment_mode = false
local doctag_mode = false
local export_mode = false
local special_tag = false
local pragma_mode = false
local factory_mode = false
local return_mode = false
local comment_tail = ''
local tag_name = ''
local new_item = false
local new_tag = false
local new_tag_item = false
local new_item_code = false
local pretty_comment = false
local comment_brace = false
local t, i, item = tokens[1], 1
local p_success, p_error = pcall(function()
while t do
-- Taglet variable update.
new_item = t.data:find('^%-%-%-') or t.data:find('^%-%-%[%=*%[$')
comment_tail = t.data:gsub('^%-%-+', '')
tag_name = comment_tail:match(patterns.DOCBUNTO_TAG)
tag_name = p.tags._alias[tag_name] or tag_name
new_tag = type(p.tags[tag_name]) == 'string'
local last_token = t.posFirst ~= 1 and 2 or 1
new_tag_item = (
new_tag and i > 1 and tokens[i - last_token].type ~= 'comment'
and not tokens[i - last_token].data:find(patterns.DOCBUNTO_TAG_COMMENT)
)
pretty_comment =
t.data:find('^%-%-%-+%s*$') or
t.data:find('[^-]+%-%-%-+%s*$') or
t.data:find('%-*[ \t]*<nowiki>$') or
t.data:find('%-*[ \t]*<pre>$')
comment_brace =
t.data:find('^%-%-%[=*%[$') or
t.data:find('^%-%-%]=*%]$') or
t.data:find('^%]=*%]%-%-$')
pragma_mode = tag_name == 'pragma'
export_mode = tag_name == 'export' and not doctag_mode
special_tag = pragma_mode or export_mode
local tags, tag, subtokens, separator
-- Line counter.
if t.posFirst == 1 then
line_no = line_no + 1
end
-- Data insertion logic.
if t.type == 'comment' then
if new_item then
comment_mode = true
end
if (new_tag or new_tag_item) and not special_tag then
comment_mode = true
doctag_mode = true
end
-- Module-level documentation taglet.
if start_mode then
table.insert(documentation.comments, t.data)
end
-- Module description.
if
start_mode and comment_mode and
not new_tag and not doctag_mode and
not pretty_comment and not comment_brace
then
separator = mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT)
and '\n'
or (#documentation.description ~= 0 and patterns.DOCBUNTO_CONCAT or '')
documentation.description = utils.join{
documentation.description,
separator,
mw.text.trim(comment_tail)
}
end
-- Switch from module description to module-level tag list.
if start_mode and new_tag and not export_mode then
doctag_mode = true
table.insert(documentation.tags, utils.process_tag(comment_tail))
-- Concatenating module-level tag with more information.
elseif
start_mode and doctag_mode and
not pretty_comment and not comment_brace
then
tags = documentation.tags
if p.tags[tags[#tags].name] == TAG_MULTI then
separator = mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT)
and '\n'
or patterns.DOCBUNTO_CONCAT
tags[#tags].value = utils.join{
tags[#tags].value,
separator,
mw.text.trim(comment_tail)
}
elseif p.tags[tags[#tags].name] == TAG_MULTI_LINE then
tags[#tags].value = utils.join{
tags[#tags].value,
'\n',
comment_tail
}
end
end
-- Detect a new documentation item.
if not start_mode and (new_item or new_tag_item) and not special_tag then
table.insert(documentation.items, {})
item_index = item_index + 1
item = documentation.items[item_index]
item.lineno = line_no
item.code = ''
item.comments = {}
item.description = ''
item.tags = {}
end
-- Concatenate an item with more information.
if
not start_mode and comment_mode and
not new_tag and not doctag_mode and
not pretty_comment and not comment_brace
then
separator = (mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT))
and '\n'
or (#item.description ~= 0 and patterns.DOCBUNTO_CONCAT or '')
item.description = utils.join{
item.description,
separator,
mw.text.trim(comment_tail)
}
end
-- Switch from item information to item tags.
if not start_mode and new_tag and not special_tag then
doctag_mode = true
table.insert(item.tags, utils.process_tag(comment_tail))
-- Extend item tags with lines of further information.
elseif
not start_mode and doctag_mode and
not pretty_comment and not comment_brace
then
tags = item.tags
if p.tags[tags[#tags].name] == TAG_MULTI then
separator = mw.text.trim(comment_tail):find(patterns.DOCBUNTO_WIKITEXT)
and '\n'
or patterns.DOCBUNTO_CONCAT
tags[#tags].value = utils.join{
tags[#tags].value,
separator,
mw.text.trim(comment_tail)
}
elseif p.tags[tags[#tags].name] == TAG_MULTI_LINE then
tags[#tags].value = utils.join{
tags[#tags].value,
'\n',
comment_tail
}
end
end
-- Save pre-item comments to item information.
if not start_mode and (comment_mode or doctag_mode) then
table.insert(item.comments, t.data)
end
-- Export tag support.
if export_mode then
factory_mode = t.posFirst ~= 1
if factory_mode then
item.exports = true
else
documentation.exports = true
end
-- Parse export token data while concatenating code.
subtokens = {}
while t and (not factory_mode or (factory_mode and t.data ~= 'end')) do
if factory_mode then
item.code = utils.join{
item.code,
(t.posFirst == 1 and '\n' or ''),
t.data
}
end
-- Fetch our item export tokens aka "subtokens".
t, i = tokens[i + 1], i + 1
if t and t.posFirst == 1 then
line_no = line_no + 1
end
if t and t.type ~= 'whitespace' and t.type ~= 'keyword' and t.type ~= 'comment' then
table.insert(subtokens, t)
end
end
-- Tracking and boolean variables for parsing exports.
local separator = { [','] = true, [';'] = true }
local brace = { ['{'] = true, ['}'] = true }
local item_reference, item_alias = '', ''
local sequence_index, has_key = 0, false
local subtoken, index = subtokens[2], 2
-- Logic that exports items to an item or module.
while not brace[subtoken.data] do
if subtoken.data == '=' then
has_key = true
elseif not separator[subtoken.data] then
if has_key then
item_reference = item_reference .. subtoken.data
else
item_alias = item_alias .. subtoken.data
end
end
if subtokens[index + 1] and separator[subtokens[index + 1].data] or brace[subtokens[index + 1].data] then
if not has_key then
sequence_index = sequence_index + 1
item_reference, item_alias = item_alias, item_reference
item_alias = '[' .. tostring(sequence_index) .. ']'
end
utils.export_item(documentation, item_reference, item_index, item_alias, factory_mode)
item_reference, item_alias, has_key = '', '', false
end
subtoken, index = subtokens[index + 1], index + 1
end
if not factory_mode then
break -- end of file
else
factory_mode = false -- end of item
end
end
-- Pragma tag support.
if pragma_mode then
tag = utils.process_tag(comment_tail)
if tag.modifiers then
options[tag.value] = yesno((next((tag ).modifiers or {})), true)
elseif tag.value:find(' ') then
local index = tag.value:find(' ')
local key = tag.value:sub(1, index - 1)
local val = tag.value:sub(index + 1)
options[key] = yesno(val)
elseif options[tag.value] == nil then
options[tag.value] = true
end
end
end
-- Package data post-processing.
if (t.type ~= 'comment' or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$')) and (comment_mode or doctag_mode) and start_mode then
documentation.tags = utils.hash_map(documentation.tags)
documentation.name = utils.extract_name(documentation, { project = true })
documentation.info = utils.extract_info(documentation)
documentation.types = utils.extract_types(documentation) or { 'module' }
documentation.type = documentation.types[1]
-- N.B: summary field = 1st comment sentence; description = text remainder.
if #documentation.description ~= 0 then
documentation.summary = match(documentation.description, patterns.DOCBUNTO_SUMMARY)
documentation.description = documentation.description:sub(select(2, documentation.description:find('%s*', #documentation.summary + 1)) + 1)
end
documentation.description = documentation.description:gsub('%s%s+', '\n\n')
documentation.executable = p.tags._code_types[documentation.type] and true or false
utils.correct_subitem_tag(documentation)
utils.override_item_tag(documentation, 'name')
utils.override_item_tag(documentation, 'alias')
utils.override_item_tag(documentation, 'summary')
utils.override_item_tag(documentation, 'description')
utils.override_item_tag(documentation, 'class', 'type')
end
-- Item data post-processing.
if
(t.type ~= 'comment' or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$'))
and (comment_mode or doctag_mode) and item_index ~= 0
then
item.tags = utils.hash_map(item.tags)
item.name = utils.extract_name(item)
item.types = utils.extract_types(item)
item.type = item.types[1]
if #item.description ~= 0 then
item.summary = match(item.description, patterns.DOCBUNTO_SUMMARY)
item.description = item.description:sub(select(2, item.description:find('%s*', #item.summary + 1)) + 1)
end
item.description = item.description:gsub('%s%s+', '\n\n')
new_item_code = true
end
-- Documentation block reset.
if t.type ~= 'comment' or t.data:find('^%-%-%]=*%]$') or t.data:find('^%]=*%]%-%-$') then
start_mode = false
comment_mode = false
doctag_mode = false
export_mode = false
pragma_mode = false
end
-- Don't concatenate module return value into item code.
if t.data == 'return' and t.posFirst == 1 then
return_mode = true
end
-- Item code concatenation.
if item_index ~= 0 and not doctag_mode and not comment_mode and not comment_brace and not return_mode then
separator = #item.code ~= 0 and t.posFirst == 1 and '\n' or ''
item.code = utils.join{ item.code, separator, t.data }
-- Code analysis on item headline.
if new_item_code and item.code:find('\n') and t.posFirst == 1 then
utils.code_heuristic(item)
new_item_code = false
end
end
t, i = tokens[i + 1], i + 1
end
documentation.lineno = line_no
local package_name = (documentation.tags['alias'] or {}).value or documentation.name or DEFAULT_VARIABLE
local package_alias = (documentation.tags['alias'] or {}).value or 'p'
local export_ptn = '^%s([.[])'
for _, item in ipairs(documentation.items) do
if item.name == package_alias or (item.name and item.name:match('^' .. package_alias .. '[.[]')) then
item.alias = item.name:gsub(export_ptn:format(package_alias), documentation.name .. '%1')
end
if
item.name == package_name or
item.tags['export'] or
(item.name and package_name and item.name:find(export_ptn:format(package_name))) or
(item.alias and package_name and item.alias:find(export_ptn:format(package_name)))
then
item.export = true
item.tags['local'] = nil
item.tags['private'] = nil
for index, tag in ipairs(item.tags) do
if tag.name == 'local' or tag.name == 'private' then
table.remove(item.tags, index)
end
end
end
if item.name and (item.name:find('[.:]') or item.name:find('%[[\'"]')) then
item.hierarchy = utils.parse_hierarchy(item.name)
end
item.type = item.type or ((item.alias or item.name or ''):find('[.[]') and 'member' or 'variable')
utils.correct_subitem_tag(item)
utils.override_item_tag(item, 'name')
utils.override_item_tag(item, 'alias')
utils.override_item_tag(item, 'summary')
utils.override_item_tag(item, 'description')
utils.override_item_tag(item, 'class', 'type')
end
-- Sort documentation items by their access level.
-- Items are sorted by whether they are exported at the module level.
-- Items are then subsorted by whether the item is `@local` aka `@private`.
table.sort(documentation.items, function(item1, item2)
local inaccessible1 = item1.tags['local'] or item1.tags['private']
local inaccessible2 = item2.tags['local'] or item2.tags['private']
-- Send package items to the top.
if item1.export and not item2.export then
return true
elseif item2.export and not item1.export then
return false
-- Send private items to the bottom.
elseif inaccessible1 and not inaccessible2 then
return false
elseif inaccessible2 and not inaccessible1 then
return true
-- Optional alphabetical sort.
elseif options.sort then
return (item1.alias or item1.name) < (item2.alias or item2.name)
-- Sort via source code order by default.
else
return item1.lineno < item2.lineno
end
end)
end)
if not p_success then
mw.addWarning("Docbunto error: " .. p_error)
end
return documentation
end
--- Doclet renderer for Docbunto taglet data.
-- @function p.doclet
-- @param {table} data Taglet documentation data.
-- @param[opt] {options} options Configuration options.
-- @return {string} Wikitext documentation output.
function p.doclet(data, options)
local documentation = mw.html.create()
local namespace_ptn = utils.join{ '^', mw.site.namespaces[828].name, ':' }
local codepage = data.filename:gsub(namespace_ptn, '', 1)
options = options or {}
options.file = data.filename
frame = frame or mw.getCurrentFrame():getParent() or mw.getCurrentFrame()
local maybe_md = options.plain and utils.identity or frontend.markdown
local tohtml = function(str)
return frame:preprocess(maybe_md(str))
end
-- Disable edit sections for automatic documentation pages.
if not options.code then
documentation:wikitext(frame:preprocess('__NOEDITSECTION__'))
end
-- Information
if not options.code and options.box then
local custom, infobox = pcall(require, 'Module:Docbunto/infobox')
if custom and type(infobox) == 'function' then
documentation:wikitext(infobox(data, codepage, frame, options, title, maybe_md)):newline()
end
end
-- Documentation lede.
if not options.code and (#(data.summary or '') + #data.description) ~= 0 then
local separator = #(data.summary or '') ~= 0 and #data.description ~= 0
and (data.description:find(patterns.DOCBUNTO_WIKITEXT) and '\n\n' or patterns.DOCBUNTO_CONCAT)
or ''
local intro = utils.join{ data.summary or '', separator, data.description }
intro = tohtml(intro:gsub('^(' .. codepage .. ')', mw.text.tag('b', nil, '%1')))
if options.migration then
documentation:wikitext(frame:preprocess(mw.text.tag{
name = 'syntaxhighlight',
content = intro
})):newline():newline()
else
documentation:wikitext(intro):newline():newline()
end
end
-- Custom documentation preface.
if options.preface then
documentation:wikitext(options.preface):newline():newline()
end
-- Start code documentation.
local codedoc = mw.html.create()
local function_module = data.tags['param'] or data.tags['return']
local header_type =
documentation.type == 'classmod'
and 'class'
or function_module
and 'function'
or 'items'
if (function_module or #data.items ~= 0) and not options.code or options.preface then
codedoc
:tag('h2')
:wikitext(i18n:msg('header-documentation'))
:done()
:newline()
end
if (function_module or #data.items ~= 0) then
codedoc
:tag('h3')
:wikitext(i18n:msg('header-' .. header_type))
:done()
:newline()
end
-- Function module support.
if function_module then
data.type, data.types[1] = 'function', 'function'
options.item_id = data.name
if not options.code or not options.verbose then data.description = '' end
frontend.render_item(codedoc, data, options, frontend.pretask_function_name)
if not options.simple and data.tags['param'] then
frontend.render_tag(codedoc, 'param', data.tags['param'], options, frontend.pretask_variable_prefix)
end
if not options.simple and data.tags['error'] then
frontend.render_tag(codedoc, 'error', data.tags['error'], options, frontend.pretask_error_line)
end
if not options.simple and data.tags['return'] then
frontend.render_tag(codedoc, 'return', data.tags['return'], options)
end
end
-- Render documentation items.
local other_header = false
local private_header = false
local inaccessible
for _, item in ipairs(data.items) do
inaccessible = item.tags['local'] or item.tags['private']
if not options.all and inaccessible then
break
end
if
not other_header and item.type ~= 'section' and item.type ~= 'type' and
not item.export and not item.hierarchy and not inaccessible
then
codedoc
:tag('h3')
:wikitext(i18n:msg('header-other'))
:done()
:newline()
other_header = true
end
if not private_header and options.all and inaccessible then
codedoc
:tag('h3')
:wikitext(i18n:msg('header-private'))
:done()
:newline()
private_header = true
end
if item.type == 'section' then
codedoc
:tag('h3')
:wikitext((mw.ustring.gsub(item.summary or item.alias or item.name, '[.։。।෴۔።]$', '')))
:done()
:newline()
if #item.description ~= 0 then
codedoc:wikitext(mw.text.trim(item.description)):newline()
end
elseif item.type == 'type' then
if options.strip and item.export and item.hierarchy then
if item.alias then
item.alias = item.alias:gsub('^[%w_]+[.[]?', '')
else
item.name = item.name:gsub('^[%w_]+[.[]?', '')
end
end
codedoc
:tag('h3')
:wikitext(mw.text.tag('code', {}, item.alias or item.name))
:done()
:newline()
if (#(item.summary or '') + #item.description) ~= 0 then
local separator = #(item.summary or '') ~= 0 and #item.description ~= 0
and (item.description:find(patterns.DOCBUNTO_WIKITEXT) and '\n\n' or patterns.DOCBUNTO_CONCAT)
or ''
codedoc:wikitext((item.summary or '') .. separator .. item.description):newline()
end
elseif item.type == 'function' then
options.item_id = item.alias or item.name
frontend.render_item(codedoc, item, options, frontend.pretask_function_name)
if not options.simple and item.tags['param'] then
frontend.render_tag(codedoc, 'param', item.tags['param'], options, frontend.pretask_variable_prefix)
end
if not options.simple and item.tags['error'] then
frontend.render_tag(codedoc, 'error', item.tags['error'], options, frontend.pretask_error_line)
end
if not options.simple and item.tags['return'] then
frontend.render_tag(codedoc, 'return', item.tags['return'], options)
end
elseif
item.type == 'table' or
item.type ~= nil and (
item.type == 'member' or
item.type == 'variable'
) and (item.alias or item.name)
then
options.item_id = item.alias or item.name
frontend.render_item(codedoc, item, options)
if not options.simple and item.tags['field'] then
frontend.render_tag(codedoc, 'field', item.tags['field'], options, frontend.pretask_variable_prefix)
end
end
if item.type ~= 'section' and item.type ~= 'type' then
if not options.simple and item.tags['note'] then
frontend.render_tag(codedoc, 'note', item.tags['note'], options)
end
if not options.simple and item.tags['warning'] then
frontend.render_tag(codedoc, 'warning', item.tags['warning'], options)
end
if not options.simple and item.tags['fixme'] then
frontend.render_tag(codedoc, 'fixme', item.tags['fixme'], options)
end
if not options.simple and item.tags['todo'] then
frontend.render_tag(codedoc, 'todo', item.tags['todo'], options)
hasTodoItems = true
end
if not options.simple and item.tags['usage'] then
frontend.render_tag(codedoc, 'usage', item.tags['usage'], options, frontend.pretask_usage_highlight)
end
if not options.simple and item.tags['see'] then
frontend.render_tag(codedoc, 'see', item.tags['see'], options)
end
end
end
-- Render module-level annotations.
local header_level = options.code and 'h3' or 'h2'
local header_text
for _, tag_name in ipairs(p.tags._annotation_hierarchy) do
if data.tags[tag_name] then
header_text = i18n:msg('tag-' .. tag_name, data.tags[tag_name].value and '1' or '2')
header_text = '<' .. header_level .. '>' .. ' ' .. header_text .. ' ' .. '</' .. header_level .. '>'
codedoc:newline():wikitext(header_text):newline()
if data.tags[tag_name].value then
codedoc:wikitext(data.tags[tag_name].value):newline()
else
for _, tag_el in ipairs(data.tags[tag_name]) do
codedoc:wikitext('* ' .. tag_el.value):newline()
end
end
end
end
-- Byline for autogeneration.
if options.footer then
codedoc:newline()
codedoc:tag('hr'):done()
codedoc:newline()
codedoc:tag('p'):css( 'text-align', 'right' ):wikitext(i18n:msg('message-autogeneration'))
end
-- Add nowiki tags for EOF termination in tests.
codedoc:tag('nowiki', { selfClosing = true })
-- Code documentation formatting.
codedoc = tohtml(tostring(codedoc))
documentation:wikitext(codedoc)
-- Endmatter table for module information.
if options.card then
local endmatter = mw.html.create('table')
endmatter:addClass('wikitable')
endmatter:css('width', '75%')
endmatter:css('margin', '0 auto 1em auto')
local module_type = data.type == 'module'
and i18n:msg('invocable-module')
or i18n:msg('meta-module')
endmatter
:tag('caption')
:wikitext('Module information card (autogenerated)')
endmatter
:tag('tr')
:tag('th'):attr('scope', 'col'):wikitext('Name'):done()
:tag('th'):attr('scope', 'col'):wikitext('Value'):done()
if options.image or data.info and data.info['image'] then
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext(i18n:msg('endmatter-image')):done()
:tag('td')
:attr('align', 'center')
:wikitext('[[File:' .. (options.image or data.info['image']) .. '|270px|center]]')
:done()
:done()
end
if options.caption or data.info and data.info['caption'] then
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext(i18n:msg('endmatter-caption')):done()
:tag('td')
:attr('align', options.image or data.info['image'] and 'center' or 'left')
:wikitext(tohtml(options.caption or data.info['caption']))
:done()
:done()
end
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext((gsub(i18n:msg('type-variable'), '^%w', mw.ustring.upper))):done()
:tag('td')
:attr('align', options.image or data.info and data.info['image'] and 'center' or 'left')
:wikitext(mw.text.tag({
name = options.image or data.info and data.info['image'] and 'h1' or 'span',
content = mw.text.tag({
name = 'b',
content = mw.text.tag({
name = 'code',
content = data.name or DEFAULT_VARIABLE
})
})
})):done()
:done()
:tag('tr')
:tag('td'):wikitext(i18n:msg('code')):done()
:tag('td'):wikitext(tohtml('[[' .. data.filename .. ']]')):done()
:done()
if data.info and data.info['release'] then
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext(i18n:msg('release-status')):done()
:tag('td'):wikitext(tohtml(data.info['release'])):done()
:done()
end
if data.summary then
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext(i18n:msg('description')):done()
:tag('td'):wikitext(tohtml(data.summary)):done()
:done()
end
if data.info and data.info['author'] then
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext(i18n:msg('author')):done()
:tag('td'):newline():wikitext(tohtml(data.info['author'])):done()
:done()
end
if data.info and data.info['attribution'] then
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext(i18n:msg('using-code-by')):done()
:tag('td'):wikitext(tohtml(data.info['attribution'])):done()
:done()
end
if data.info and data.info['credit'] then
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext(i18n:msg('other-attribution')):done()
:tag('td'):wikitext(tohtml(data.info['credit'])):done()
:done()
end
if data.info and data.info['require'] then
data.info['require'] = data.info['require']
:gsub('^[^[%s]+$', '[[%1]]')
:gsub('%* ([^[%s]+)', '* [[%1]]')
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext(i18n:msg('dependencies')):done()
:tag('td'):newline():wikitext(tohtml(data.info['require'])):done()
:done()
end
if data.info and data.info['demo'] then
endmatter
:tag('tr')
:tag('td'):attr('scope', 'row'):wikitext(i18n:msg('examples')):done()
:tag('td'):wikitext(tohtml(data.info['demo'])):done()
:done()
end
endmatter:newline()
endmatter = tostring(endmatter)
documentation:newline():newline()
documentation:wikitext(endmatter)
end
documentation = tostring(documentation)
if hasTodoItems and options.autodoc
and mw.title.new('Module:' .. options.file).fullText == mw.title.getCurrentTitle().fullText then
documentation = documentation .. '[[Category:' .. i18n:msg('todo') .. ']]'
end
if mw.title.new('Module:Docbunto/styles.css').exists then
documentation = documentation .. frame:extensionTag('templatestyles', '', {src = 'Module:Docbunto/styles.css'})
end
return documentation
end
--- Token dictionary for Docbunto tags.
-- Maps Docbunto tag names to tag tokens.
-- * Multi-line tags use the `'M'` token.
-- * Multi-line preformatted tags use the `'ML'` token.
-- * Identifier tags use the `'ID'` token.
-- * Single-line tags use the `'S'` token.
-- * Flags use the `'N'` token.
-- * Type tags use the `'T'` token.
-- @table p.tags
p.tags = {
-- Item-level tags, available for global use.
['param'] = 'M', ['see'] = 'M', ['note'] = 'M', ['usage'] = 'ML',
['description'] = 'M', ['field'] = 'M', ['return'] = 'M',
['fixme'] = 'M', ['todo'] = 'M', ['warning'] = 'M', ['error'] = 'M';
['class'] = 'ID', ['name'] = 'ID', ['alias'] = 'ID';
['summary'] = 'S', ['pragma'] = 'S', ['factory'] = 'S',
['release'] = 'S', ['author'] = 'S', ['copyright'] = 'S', ['license'] = 'S',
['image'] = 'S', ['caption'] = 'S', ['require'] = 'S', ['attribution'] = 'S',
['credit'] = 'S', ['demo'] = 'S';
['local'] = 'N', ['export'] = 'N', ['private'] = 'N', ['constructor'] = 'N',
['static'] = 'N';
-- Project-level tags, all scoped to a file.
['module'] = 'T', ['script'] = 'T', ['classmod'] = 'T', ['topic'] = 'T',
['submodule'] = 'T', ['example'] = 'T', ['file'] = 'T';
-- Module-level tags, used to register module items.
['function'] = 'T', ['table'] = 'T', ['member'] = 'T', ['variable'] = 'T',
['section'] = 'T', ['type'] = 'T';
}
p.tags._alias = {
-- Normal aliases.
['about'] = 'summary',
['abstract'] = 'summary',
['brief'] = 'summary',
['bug'] = 'fixme',
['argument'] = 'param',
['credits'] = 'credit',
['code'] = 'usage',
['details'] = 'description',
['discussion'] = 'description',
['exception'] = 'error',
['lfunction'] = 'function',
['package'] = 'module',
['property'] = 'member',
['raise'] = 'error',
['requires'] = 'require',
['returns'] = 'return',
['throws'] = 'error',
['typedef'] = 'type',
-- Typed aliases.
['bool'] = 'field',
['func'] = 'field',
['int'] = 'field',
['number'] = 'field',
['string'] = 'field',
['tab'] = 'field',
['vararg'] = 'param',
['tfield'] = 'field',
['tparam'] = 'param',
['treturn'] = 'return'
}
p.tags._type_alias = {
-- Implicit type value alias.
['bool'] = 'boolean',
['func'] = 'function',
['int'] = 'number',
['number'] = 'number',
['string'] = 'string',
['tab'] = 'table',
['vararg'] = '...',
-- Pure typed modifier alias.
['tfield'] = 'variable',
['tparam'] = 'variable',
['treturn'] = 'variable'
}
p.tags._project_level = {
-- Contains code.
['module'] = true,
['script'] = true,
['classmod'] = true,
['submodule'] = true,
['file'] = true,
-- Contains documentation.
['topic'] = true,
['example'] = true
}
p.tags._code_types = {
['module'] = true,
['script'] = true,
['classmod'] = true
}
p.tags._module_info = {
['image'] = true,
['caption'] = true,
['release'] = true,
['author'] = true,
['copyright'] = true,
['license'] = true,
['require'] = true,
['credit'] = true,
['attribution'] = true,
['demo'] = true
}
p.tags._annotation_tags = {
['field'] = true,
['warning'] = true,
['fixme'] = true,
['note'] = true,
['todo'] = true,
['see'] = true
}
p.tags._annotation_hierarchy = {
'warning',
'fixme',
'note',
'todo',
'see'
}
p.tags._privacy_tags = {
['private'] = true,
['local'] = true
}
p.tags._generic_tags = {
['variable'] = true,
['member'] = true
}
p.tags._subtype_tags = {
['factory'] = true,
['local'] = true,
['private'] = true,
['constructor'] = true,
['static'] = true
}
p.tags._subtype_hierarchy = {
'private',
'local',
'static',
'factory',
'constructor'
}
return p