-- SPDX-FileCopyrightText: © 2026 Vladimir Zorin -- SPDX-License-Identifier: LicenseRef-OWL-1.0-or-later -- Licensed under OWL v1.0+. See LICENSE. --[==[ LIMAN documentation comment parser. Extracts module-level and function-level documentation from Lua source files using the LIMAN doc comment convention. ]==] local rel_to_module = function(rel_path) return rel_path:gsub("%.lua$", ""):gsub("/", ".") end -- Extract the first --[==[...]==] block that appears before any code -- (after license/comment header lines). local extract_module_doc = function(content) local pos = 1 while true do local line_start, line_end = content:find("[^\n]*\n", pos) if not line_start then break end local line = content:sub(line_start, line_end - 1) if line:match("^%s*$") or line:match("^%-%-[^%[]") or line:match("^%-%-$") then pos = line_end + 1 else break end end local doc_start = content:find("^%-%-%[==%[", pos) if doc_start then local _, _, doc_body = content:find("%-%-%[==%[(.-)%]==%]", pos) if doc_body then return doc_body:match("^%s*(.-)%s*$") end end return nil end -- Extract function doc blocks and validate they precede function definitions. -- Returns a list of { name, summary, signature, detail? } and a list of warnings. local extract_function_docs = function(content, include_details) local functions = {} local warnings = {} local lines = {} for line in (content .. "\n"):gmatch("([^\n]*)\n") do lines[#lines + 1] = line end local i = 1 while i <= #lines do local summary = lines[i]:match("^%-%-%-!%s*(.+)") if summary then local block_start = i i = i + 1 local signature if i <= #lines then signature = lines[i]:match("^%-%-%-@%s*(.+)") end if not signature then warnings[#warnings + 1] = "line " .. block_start .. ": ---! without ---@ on next line" else i = i + 1 local detail = nil if i <= #lines and lines[i]:match("^%-%-%[===%[") then local detail_lines = {} local first_line_content = lines[i]:match("^%-%-%[===%[(.*)$") if first_line_content and first_line_content ~= "" then detail_lines[#detail_lines + 1] = first_line_content end i = i + 1 while i <= #lines do if lines[i]:match("^%]===%]") then i = i + 1 break end detail_lines[#detail_lines + 1] = lines[i] i = i + 1 end if include_details and #detail_lines > 0 then detail = table.concat(detail_lines, "\n") detail = detail:match("^%s*(.-)%s*$") if detail == "" then detail = nil end end end local func_line_idx = i while func_line_idx <= #lines and lines[func_line_idx]:match("^%s*$") do func_line_idx = func_line_idx + 1 end local is_func_def = false if func_line_idx <= #lines then local l = lines[func_line_idx] if l:match("^local%s+[%w_]+%s*$") then func_line_idx = func_line_idx + 1 while func_line_idx <= #lines and lines[func_line_idx]:match("^%s*$") do func_line_idx = func_line_idx + 1 end if func_line_idx <= #lines then l = lines[func_line_idx] end end if l:match("^local%s+%S+%s*=%s*function") or l:match("^local%s+function%s+") or l:match("^[%w_.]+%s*=%s*function") then is_func_def = true end end if not is_func_def then warnings[#warnings + 1] = "line " .. block_start .. ": doc block not followed by function definition" else local name = signature:match("^([%w_:%.]+)%s*%(") or signature:match("^([%w_:%.]+)") functions[#functions + 1] = { name = name or "unknown", summary = summary, signature = signature, detail = detail, } end end else i = i + 1 end end return functions, warnings end return { extract_module_doc = extract_module_doc, extract_function_docs = extract_function_docs, rel_to_module = rel_to_module, }