Kontent qismiga oʻtish

Modul:module documentation

Vikilug‘atdan olingan

Bu modul uchun Modul:module documentation/doc nomli hujjat sahifasini yaratishingiz mumkin

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



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)
			local next_char = m0:sub(2, 2)
			if next_char == "|" then
				-- Wikitable; don't try to parse it as code. But we do want to parse special syntax in them (in
				-- particular {...} syntax for embedded code snippets), and if we return nil that won't happen.
				-- Instead, we call format_doc() recursively on the innards.
				return "{" .. format_doc(m0:sub(2, -2)) .. "}"
			end
			if next_char == "{" 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)
		-- Join continued lines in a paragraph. We don't want to do that if there are two newlines in a row,
		-- and not if the second line begins with whitespace or a certain special characters (#, * or : indicating
		-- a list item; | indicating a wikitable item; semicolon for bolded items).
    	:gsub("([^\n])\n[ \t]*([^ \t\n#*:;|])", "%1 %2")
    	-- Repeat the previous in case of a single-character line (admittedly rare).
    	:gsub("([^\n])\n[ \t]*([^ \t\n#*:;|])", "%1 %2")
		: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 metafunc_comment_pattern = make_comment_pattern("func:%s*(([^\n(]+)[^\n)]+%))")
	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

	-- This contains function and intro documentation. Each element is a two-element list of {POSITION, DOCS} specifying
	-- the generated documentation for a function and the character position in the file where it was found (for sorting
	-- purposes).
	local docs
	
	local intro_comment = mod_text:match("^.-" .. intro_comment_pattern)
	if intro_comment then
		docs = { {1, format_doc(intro_comment) }}
	else
		docs = {}
	end

	-- Look for actual functions.
	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(docs, {p0, 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

	-- Now look for comments with the function declaration inside them (used for metatable functions etc.).
	for p0, f, fn, comment in mod_text:gmatch("()" .. metafunc_comment_pattern) do
		insert(docs, {p0, section_mark .. fn .. section_mark .. "\n\n" .. "<syntaxhighlight lang=lua inline>function " .. f .. "</syntaxhighlight>\n\n" .. format_doc(comment)})
	end

	table.sort(docs, function(a, b) return a[1] < b[1] end)
	
	local chunks = {}
	for i, decl in ipairs(docs) do
		insert(chunks, decl[2])
	end

	return frame:preprocess(concat(chunks, "\n\n"))
end

return export