- Fixed a container bug

- Added onDone to Program
This commit is contained in:
Robert Jelic
2025-06-20 14:19:05 +02:00
parent ea0f31fe37
commit 8067f23d42
16 changed files with 671 additions and 5 deletions

View File

@@ -102,6 +102,14 @@ end
function Container:init(props, basalt)
VisualElement.init(self, props, basalt)
self.set("type", "Container")
self:observe("width", function()
self.set("childrenSorted", false)
self.set("childrenEventsSorted", false)
end)
self:observe("height", function()
self.set("childrenSorted", false)
self.set("childrenEventsSorted", false)
end)
end
--- Returns whether a child is visible
@@ -339,11 +347,13 @@ end
local function convertMousePosition(self, event, ...)
local args = {...}
if event:find("mouse_") then
local button, absX, absY = ...
local xOffset, yOffset = self.get("offsetX"), self.get("offsetY")
local relX, relY = self:getRelativePosition(absX + xOffset, absY + yOffset)
args = {button, relX, relY}
if event then
if event:find("mouse_") then
local button, absX, absY = ...
local xOffset, yOffset = self.get("offsetX"), self.get("offsetY")
local relX, relY = self:getRelativePosition(absX + xOffset, absY + yOffset)
args = {button, relX, relY}
end
end
return args
end
@@ -680,6 +690,9 @@ end
--- @private
function Container:destroy()
if not self:isType("BaseFrame") then
for _, child in ipairs(self.get("children")) do
child:destroy()
end
self.set("childrenSorted", false)
VisualElement.destroy(self)
return self

View File

@@ -787,6 +787,7 @@ function Flexbox:addChild(element)
return self
end
--- Removes a child element from the flexbox
--- @shortDescription Removes a child element from the flexbox
--- @param element Element The child element to remove
--- @return Flexbox self The flexbox instance

View File

@@ -17,6 +17,8 @@ Program.defineProperty(Program, "path", {default = "", type = "string"})
Program.defineProperty(Program, "running", {default = false, type = "boolean"})
--- @property errorCallback function nil The error callback function
Program.defineProperty(Program, "errorCallback", {default = nil, type = "function"})
--- @property doneCallback function nil The done callback function
Program.defineProperty(Program, "doneCallback", {default = nil, type = "function"})
Program.defineEvent(Program, "*")
@@ -82,6 +84,10 @@ function BasaltProgram:run(path, width, height)
local ok, result = coroutine.resume(self.coroutine)
term.redirect(current)
if not ok then
local doneCallback = self.program.get("doneCallback")
if doneCallback then
doneCallback(self.program, ok, result)
end
local errorCallback = self.program.get("errorCallback")
if errorCallback then
local trace = debug.traceback(self.coroutine, result)
@@ -94,6 +100,14 @@ function BasaltProgram:run(path, width, height)
errorManager.header = "Basalt Program Error ".. path
errorManager.error(result)
end
if coroutine.status(self.coroutine)=="dead" then
self.program.set("running", false)
self.program.set("program", nil)
local doneCallback = self.program.get("doneCallback")
if doneCallback then
doneCallback(self.program, ok, result)
end
end
else
errorManager.header = "Basalt Program Error ".. path
errorManager.error("File not found")
@@ -128,10 +142,23 @@ function BasaltProgram:resume(event, ...)
if ok then
self.filter = result
if coroutine.status(self.coroutine)=="dead" then
self.program.set("running", false)
self.program.set("program", nil)
local doneCallback = self.program.get("doneCallback")
if doneCallback then
doneCallback(self.program, ok, result)
end
end
else
local doneCallback = self.program.get("doneCallback")
if doneCallback then
doneCallback(self.program, ok, result)
end
local errorCallback = self.program.get("errorCallback")
if errorCallback then
local trace = debug.traceback(self.coroutine, result)
trace = trace == nil and "" or trace
local _result = errorCallback(self.program, result, trace:gsub(result, ""))
if(_result==false)then
self.filter = nil
@@ -235,6 +262,15 @@ function Program:onError(fn)
return self
end
--- Registers a callback for the program's done event
--- @shortDescription Registers a callback for the program's done event
--- @param fn function The callback function to register
--- @return Program self The Program instance
function Program:onDone(fn)
self.set("doneCallback", fn)
return self
end
--- @shortDescription Handles all incomming events
--- @param event string The event to handle
--- @param ... any The event arguments

View File

@@ -0,0 +1,48 @@
local elementManager = require("elementManager")
local VisualElement = elementManager.getElement("VisualElement")
local getCenteredPosition = require("libraries/utils").getCenteredPosition
---@cofnigDescription The Button is a standard button element with click handling and state management.
--- The Button is a standard button element with click handling and state management.
---@class Button : VisualElement
local Button = setmetatable({}, VisualElement)
Button.__index = Button
---@property text string Button Button text
Button.defineProperty(Button, "text", {default = "Button", type = "string", canTriggerRender = true})
Button.defineEvent(Button, "mouse_click")
Button.defineEvent(Button, "mouse_up")
--- @shortDescription Creates a new Button instance
--- @return table self The created instance
--- @private
function Button.new()
local self = setmetatable({}, Button):__init()
self.class = Button
self.set("width", 10)
self.set("height", 3)
self.set("z", 5)
return self
end
--- @shortDescription Initializes the Button instance
--- @param props table The properties to initialize the element with
--- @param basalt table The basalt instance
--- @protected
function Button:init(props, basalt)
VisualElement.init(self, props, basalt)
self.set("type", "Button")
end
--- @shortDescription Renders the Button
--- @protected
function Button:render()
VisualElement.render(self)
local text = self.get("text")
text = text:sub(1, self.get("width"))
local xO, yO = getCenteredPosition(text, self.get("width"), self.get("height"))
self:textFg(xO, yO, text, self.get("foreground"))
end
return Button

View File

@@ -0,0 +1,36 @@
# ldoc-markdown-parser
## Overview
`ldoc-markdown-parser` is a simple and extensible Lua documentation parser that converts Lua documentation comments into Markdown format. It supports both single-line and multi-line comments, making it easy to document your Lua code in a structured way.
## Features
- Extracts single-line and multi-line comments from Lua files.
- Parses custom tags such as `@property`, `@shortDescription`, `@param`, and `@return`.
- Converts extracted comments and parsed tags into Markdown format.
- Easy to extend and customize for additional tags or formatting options.
## Installation
To install the `ldoc-markdown-parser`, clone the repository and navigate to the project directory:
```bash
git clone <repository-url>
cd ldoc-markdown-parser
```
## Usage
To use the parser, run the `main.lua` file with the Lua interpreter, providing the path to the Lua file you want to parse:
```bash
lua src/main.lua path/to/your/lua_file.lua
```
The output will be generated in Markdown format and can be found in the specified output directory.
## Example
An example Lua file can be found in the `examples/input/example.lua`, and the expected output Markdown file is located in `examples/output/example.md`.
## Contributing
Contributions are welcome! Please feel free to submit a pull request or open an issue for any enhancements or bug fixes.
## License
This project is licensed under the MIT License. See the LICENSE file for more details.

View File

@@ -0,0 +1,64 @@
local FunctionParser = {}
--- Check if a block represents a function
function FunctionParser.canParse(block)
return block.context and block.context.type == "function"
end
--- Parse a complete function documentation block
function FunctionParser.parse(block)
local functionDoc = {
type = "function",
name = block.context.name,
shortDescription = nil,
params = {},
returns = {},
visibility = "public", -- default
context = block.context,
content = {}
}
for _, line in ipairs(block.comments) do
-- Parse shortDescription
local shortDesc = line:match("^%-*%s*@shortDescription%s+(.*)$")
if shortDesc then
functionDoc.shortDescription = shortDesc
-- Parse param
elseif line:match("^%-*%s*@param") then
local paramName, paramType, paramDesc = line:match("^%-*%s*@param%s+(%S+)%s+(%S+)%s+(.*)$")
if paramName then
table.insert(functionDoc.params, {
name = paramName,
type = paramType,
description = paramDesc or ""
})
end
-- Parse return
elseif line:match("^%-*%s*@return") then
local returnType, returnName, returnDesc = line:match("^%-*%s*@return%s+(%S+)%s+(%S+)%s+(.*)$")
if returnType then
table.insert(functionDoc.returns, {
type = returnType,
name = returnName or "",
description = returnDesc or ""
})
end
-- Parse visibility
elseif line:match("^%-*%s*@private%s*$") then
functionDoc.visibility = "private"
elseif line:match("^%-*%s*@protected%s*$") then
functionDoc.visibility = "protected"
-- Regular content
else
table.insert(functionDoc.content, line)
end
end
return functionDoc
end
return FunctionParser

View File

@@ -0,0 +1,101 @@
local CommentExtractor = {}
--- Extracts comments with their associated code context
-- @param lines table The lines of the Lua file as a table of strings.
-- @return table A table containing comment blocks with context.
function CommentExtractor.extractComments(lines)
local blocks = {}
local currentCommentBlock = {}
local i = 1
while i <= #lines do
local line = lines[i]:match("^%s*(.*)") -- Trim leading whitespace
-- Check if this is a comment line
if line:find("^%-%-%-") or line:find("^%-%-") then
table.insert(currentCommentBlock, line)
elseif #currentCommentBlock > 0 then
-- We have accumulated comments, check if next non-empty line is code
local codeContext = nil
local j = i
-- Skip empty lines to find the actual code
while j <= #lines and lines[j]:match("^%s*$") do
j = j + 1
end
if j <= #lines then
local codeLine = lines[j]:match("^%s*(.*)")
-- Check if it's a function, class, property, etc.
if codeLine:find("^function") or
codeLine:find("^local function") or
codeLine:find("^local%s+%w+%s*=") or
codeLine:find("^%w+%.%w+") then
codeContext = {
type = CommentExtractor.getCodeType(codeLine),
name = CommentExtractor.extractName(codeLine),
line = codeLine,
lineNumber = j
}
end
end
-- Add the comment block with its context
table.insert(blocks, {
comments = currentCommentBlock,
context = codeContext
})
currentCommentBlock = {}
end
i = i + 1
end
-- Handle any remaining comments
if #currentCommentBlock > 0 then
table.insert(blocks, {
comments = currentCommentBlock,
context = nil
})
end
return blocks
end
--- Determines the type of code (function, class, property, etc.)
function CommentExtractor.getCodeType(codeLine)
if codeLine:find("^function") or codeLine:find("^local function") then
return "function"
elseif codeLine:find("^local%s+%w+%s*=%s*setmetatable") then
return "class"
elseif codeLine:find("^local%s+%w+%s*=") then
return "variable"
elseif codeLine:find("^%w+%.defineProperty") then
return "property_definition"
else
return "unknown"
end
end
--- Extracts the name from a code line
function CommentExtractor.extractName(codeLine)
-- Function patterns
local funcName = codeLine:match("^function%s+([%w%.%:]+)")
if funcName then return funcName end
local localFuncName = codeLine:match("^local%s+function%s+([%w%.%:]+)")
if localFuncName then return localFuncName end
-- Variable/class patterns
local varName = codeLine:match("^local%s+([%w_]+)%s*=")
if varName then return varName end
-- Method patterns
local methodName = codeLine:match("^([%w%.%:]+)%s*=")
if methodName then return methodName end
return "unknown"
end
return CommentExtractor

View File

@@ -0,0 +1,29 @@
local CommentExtractor = require("parser.comment_extractor")
local MarkdownGenerator = require("parser.markdown_generator")
local Parser = {}
--- Extract comments and generate markdown
function Parser.extractComments(content)
local lines = {}
for line in content:gmatch("[^\r\n]+") do
table.insert(lines, line)
end
return CommentExtractor.extractComments(lines)
end
--- Generate markdown from comment blocks
function Parser.generateMarkdown(commentBlocks)
local parsedBlocks = {}
-- Parse each block using the appropriate parser
for _, block in ipairs(commentBlocks) do
local parsedBlock = MarkdownGenerator.parseBlock(block)
table.insert(parsedBlocks, parsedBlock)
end
-- Generate markdown from parsed blocks
return MarkdownGenerator.generateMarkdown(parsedBlocks)
end
return Parser

View File

@@ -0,0 +1,142 @@
local markdownGenerator = {}
local TagParserRegistry = require("parser.tag_parser_registry")
--- Determines which block parser should handle a given block
--- @param block table The comment block with context
--- @return string|nil The block type that can handle this block
function markdownGenerator.detectBlockType(block)
local blockParsers = TagParserRegistry.getAllBlocks()
for blockType, parser in pairs(blockParsers) do
if parser.canParse and parser.canParse(block) then
return blockType
end
end
return nil -- No specific block parser found, use generic parsing
end
--- Parses a block using the appropriate block parser
--- @param block table The comment block to parse
--- @return table The parsed block data
function markdownGenerator.parseBlock(block)
local blockType = markdownGenerator.detectBlockType(block)
if blockType then
local parser = TagParserRegistry.getBlock(blockType)
return parser.parse(block)
else
-- Generic parsing using individual tag parsers
return markdownGenerator.parseGenericBlock(block)
end
end
--- Generic block parsing using individual tag parsers
--- @param block table The comment block to parse
--- @return table The parsed block data
function markdownGenerator.parseGenericBlock(block)
local parsedBlock = {
type = "generic",
tags = {},
content = {},
context = block.context
}
for _, line in ipairs(block.comments) do
local parsed = false
local tagParsers = TagParserRegistry.getAllTags()
-- Try each registered tag parser
for tagName, parser in pairs(tagParsers) do
local result = parser.parse(line)
if result then
table.insert(parsedBlock.tags, result)
parsed = true
break
end
end
-- If no tag parser matched, treat as regular content
if not parsed then
table.insert(parsedBlock.content, line)
end
end
return parsedBlock
end
--- Converts parsed blocks to markdown
--- @param parsedBlocks table Array of parsed blocks
--- @return string The generated markdown
function markdownGenerator.generateMarkdown(parsedBlocks)
local markdown = {}
for _, block in ipairs(parsedBlocks) do
if block.type == "function" then
table.insert(markdown, markdownGenerator.generateFunctionMarkdown(block))
else
table.insert(markdown, markdownGenerator.generateGenericMarkdown(block))
end
table.insert(markdown, "") -- Empty line between blocks
end
return table.concat(markdown, "\n")
end
--- Generate markdown for function blocks
--- @param functionBlock table The parsed function block
--- @return string The generated markdown
function markdownGenerator.generateFunctionMarkdown(functionBlock)
local md = {}
table.insert(md, string.format("## Function: %s", functionBlock.name))
if functionBlock.shortDescription then
table.insert(md, string.format("**Description:** %s", functionBlock.shortDescription))
end
if #functionBlock.params > 0 then
table.insert(md, "**Parameters:**")
for _, param in ipairs(functionBlock.params) do
table.insert(md, string.format("- `%s` (%s): %s", param.name, param.type, param.description))
end
end
if #functionBlock.returns > 0 then
table.insert(md, "**Returns:**")
for _, ret in ipairs(functionBlock.returns) do
table.insert(md, string.format("- `%s` (%s): %s", ret.name, ret.type, ret.description))
end
end
if functionBlock.visibility ~= "public" then
table.insert(md, string.format("**Visibility:** %s", functionBlock.visibility))
end
return table.concat(md, "\n")
end
--- Generate markdown for generic blocks
--- @param block table The parsed generic block
--- @return string The generated markdown
function markdownGenerator.generateGenericMarkdown(block)
local md = {}
-- Generate markdown for tags
if #block.tags > 0 then
table.insert(md, "### Tags")
for _, tag in ipairs(block.tags) do
table.insert(md, string.format("- **%s**: %s", tag.name, tag.description))
end
end
-- Generate markdown for content
if #block.content > 0 then
table.insert(md, "### Content")
table.insert(md, table.concat(block.content, "\n"))
end
return table.concat(md, "\n\n")
end
return markdownGenerator

View File

@@ -0,0 +1,19 @@
local tagParser = {}
local function parseTag(tagLine)
local tag, content = tagLine:match("^%s*@(.-)%s*(.*)$")
return tag, content
end
function tagParser.parseTags(commentLines)
local tags = {}
for _, line in ipairs(commentLines) do
local tag, content = parseTag(line)
if tag then
tags[tag] = content
end
end
return tags
end
return tagParser

View File

@@ -0,0 +1,59 @@
local TagParserRegistry = {}
local tagParsers = {}
local blockParsers = {}
--- Register a new tag parser (for individual tags like @param, @return)
function TagParserRegistry.registerTag(tagName, parser)
tagParsers[tagName] = parser
end
--- Register a new block parser (for complete blocks like functions, classes)
function TagParserRegistry.registerBlock(blockType, parser)
blockParsers[blockType] = parser
end
--- Get a specific tag parser
function TagParserRegistry.getTag(tagName)
return tagParsers[tagName]
end
--- Get a specific block parser
function TagParserRegistry.getBlock(blockType)
return blockParsers[blockType]
end
--- Get all registered tag parsers
function TagParserRegistry.getAllTags()
return tagParsers
end
--- Get all registered block parsers
function TagParserRegistry.getAllBlocks()
return blockParsers
end
--- Auto-load all parsers
local function loadAllParsers()
-- Load tag parsers (individual tags)
local tagParsersList = {"param", "return", "property", "private", "protected", "shortDescription"}
for _, parserName in ipairs(tagParsersList) do
local success, parser = pcall(require, "parser.tag_parsers." .. parserName)
if success then
TagParserRegistry.registerTag(parserName, parser)
end
end
-- Load block parsers (complete documentation blocks)
local blockParsersList = {"function", "class", "property_definition"}
for _, parserName in ipairs(blockParsersList) do
local success, parser = pcall(require, "parser.block_parsers." .. parserName)
if success then
TagParserRegistry.registerBlock(parserName, parser)
end
end
end
loadAllParsers()
return TagParserRegistry

View File

@@ -0,0 +1,21 @@
local PropertyParser = {}
--- Parse @property tag
--- Example: ---@property text string Button Button text
function PropertyParser.parse(line)
local pattern = "^%-*%s*@property%s+(%S+)%s+(%S+)%s+(.*)$"
local name, dataType, description = line:match(pattern)
if name and dataType then
return {
type = "property",
name = name,
dataType = dataType,
description = description or ""
}
end
return nil
end
return PropertyParser

View File

@@ -0,0 +1,19 @@
local ShortDescriptionParser = {}
--- Parse @shortDescription tag
--- Example: --- @shortDescription Creates a new Button instance
function ShortDescriptionParser.parse(line)
local pattern = "^%-*%s*@shortDescription%s+(.*)$"
local description = line:match(pattern)
if description then
return {
type = "shortDescription",
description = description
}
end
return nil
end
return ShortDescriptionParser

View File

@@ -0,0 +1,35 @@
local fileReader = {}
--- Read file content
--- @param filePath string Path to the file
--- @return string|nil content File content or nil if error
--- @return string|nil error Error message if any
function fileReader.readFile(filePath)
local file = io.open(filePath, "r")
if not file then
return nil, "Could not open file: " .. filePath
end
local content = file:read("*all")
file:close()
return content, nil
end
--- Write content to file
--- @param filePath string Path to the file
--- @param content string Content to write
--- @return string|nil error Error message if any
function fileReader.writeFile(filePath, content)
local file = io.open(filePath, "w")
if not file then
return "Could not open file for writing: " .. filePath
end
file:write(content)
file:close()
return nil
end
return fileReader

View File

@@ -0,0 +1,16 @@
local string_utils = require("utils.string_utils")
local utils = {}
-- Re-export string utilities
utils.trim = string_utils.trim
utils.is_empty = string_utils.is_empty
utils.starts_with = string_utils.starts_with
utils.ends_with = string_utils.ends_with
utils.split = string_utils.split
function utils.isClassMethod(name)
return name:sub(1, 1):upper() == name:sub(1, 1)
end
return utils

View File

@@ -0,0 +1,27 @@
local string_utils = {}
function string_utils.trim(s)
return s:match("^%s*(.-)%s*$")
end
function string_utils.is_empty(s)
return s == nil or s == ""
end
function string_utils.starts_with(s, prefix)
return s:sub(1, #prefix) == prefix
end
function string_utils.ends_with(s, suffix)
return s:sub(-#suffix) == suffix
end
function string_utils.split(s, delimiter)
local result = {}
for match in (s..delimiter):gmatch("(.-)"..delimiter) do
table.insert(result, match)
end
return result
end
return string_utils