Replacing errors with warns in non-critical areas
This commit is contained in:
@@ -1,4 +1,12 @@
|
||||
--- Standardized error message formatter
|
||||
local ErrorHandler = nil
|
||||
|
||||
--- Initialize ErrorHandler dependency
|
||||
---@param errorHandler table The ErrorHandler module
|
||||
local function initializeErrorHandler(errorHandler)
|
||||
ErrorHandler = errorHandler
|
||||
end
|
||||
|
||||
--- Standardized error message formatter (fallback for when ErrorHandler not available)
|
||||
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
||||
---@param message string
|
||||
---@return string
|
||||
@@ -77,7 +85,14 @@ function Color.fromHex(hexWithTag)
|
||||
local g = tonumber("0x" .. hex:sub(3, 4))
|
||||
local b = tonumber("0x" .. hex:sub(5, 6))
|
||||
if not r or not g or not b then
|
||||
error(formatError("Color", string.format("Invalid hex string format: '%s'. Contains invalid hex digits", hexWithTag)))
|
||||
if ErrorHandler then
|
||||
ErrorHandler.warn("Color", "VAL_004", "Invalid color format", {
|
||||
input = hexWithTag,
|
||||
issue = "invalid hex digits",
|
||||
fallback = "white (#FFFFFF)"
|
||||
})
|
||||
end
|
||||
return Color.new(1, 1, 1, 1) -- Return white as fallback
|
||||
end
|
||||
return Color.new(r / 255, g / 255, b / 255, 1)
|
||||
elseif #hex == 8 then
|
||||
@@ -86,11 +101,26 @@ function Color.fromHex(hexWithTag)
|
||||
local b = tonumber("0x" .. hex:sub(5, 6))
|
||||
local a = tonumber("0x" .. hex:sub(7, 8))
|
||||
if not r or not g or not b or not a then
|
||||
error(formatError("Color", string.format("Invalid hex string format: '%s'. Contains invalid hex digits", hexWithTag)))
|
||||
if ErrorHandler then
|
||||
ErrorHandler.warn("Color", "VAL_004", "Invalid color format", {
|
||||
input = hexWithTag,
|
||||
issue = "invalid hex digits",
|
||||
fallback = "white (#FFFFFFFF)"
|
||||
})
|
||||
end
|
||||
return Color.new(1, 1, 1, 1) -- Return white as fallback
|
||||
end
|
||||
return Color.new(r / 255, g / 255, b / 255, a / 255)
|
||||
else
|
||||
error(formatError("Color", string.format("Invalid hex string format: '%s'. Expected #RRGGBB or #RRGGBBAA", hexWithTag)))
|
||||
if ErrorHandler then
|
||||
ErrorHandler.warn("Color", "VAL_004", "Invalid color format", {
|
||||
input = hexWithTag,
|
||||
expected = "#RRGGBB or #RRGGBBAA",
|
||||
hexLength = #hex,
|
||||
fallback = "white (#FFFFFF)"
|
||||
})
|
||||
end
|
||||
return Color.new(1, 1, 1, 1) -- Return white as fallback
|
||||
end
|
||||
end
|
||||
|
||||
@@ -373,4 +403,7 @@ function Color.parse(value)
|
||||
return Color.sanitizeColor(value, Color.new(0, 0, 0, 1))
|
||||
end
|
||||
|
||||
-- Export ErrorHandler initializer
|
||||
Color.initializeErrorHandler = initializeErrorHandler
|
||||
|
||||
return Color
|
||||
|
||||
@@ -148,7 +148,8 @@ Element.__index = Element
|
||||
---@return Element
|
||||
function Element.new(props, deps)
|
||||
if not deps then
|
||||
error("[Element] deps parameter is required. Pass Element.defaultDependencies from FlexLove.")
|
||||
-- Can't use ErrorHandler yet since deps contains it
|
||||
error("[FlexLove - Element] Error: deps parameter is required. Pass Element.defaultDependencies from FlexLove.")
|
||||
end
|
||||
|
||||
local self = setmetatable({}, Element)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
--- Error code definitions for FlexLove
|
||||
--- Provides centralized error codes, descriptions, and suggested fixes
|
||||
---@class ErrorCodes
|
||||
local ErrorCodes = {}
|
||||
|
||||
|
||||
@@ -1,27 +1,509 @@
|
||||
-- modules/ErrorHandler.lua
|
||||
local ErrorHandler = {}
|
||||
local ErrorCodes = nil -- Will be injected via init
|
||||
|
||||
--- Format an error or warning message
|
||||
local LOG_LEVELS = {
|
||||
CRITICAL = 1,
|
||||
ERROR = 2,
|
||||
WARNING = 3,
|
||||
INFO = 4,
|
||||
DEBUG = 5,
|
||||
}
|
||||
|
||||
local config = {
|
||||
debugMode = false,
|
||||
includeStackTrace = false,
|
||||
logLevel = LOG_LEVELS.WARNING, -- Default: log errors and warnings
|
||||
logTarget = "console", -- Options: "console", "file", "both", "none"
|
||||
logFormat = "human", -- Options: "human", "json"
|
||||
logFile = "flexlove-errors.log",
|
||||
maxLogSize = 10 * 1024 * 1024, -- 10MB default
|
||||
maxLogFiles = 5, -- Keep 5 rotated logs
|
||||
enableRotation = true,
|
||||
}
|
||||
|
||||
-- Internal state
|
||||
local logFileHandle = nil
|
||||
local currentLogSize = 0
|
||||
|
||||
--- Initialize ErrorHandler with dependencies
|
||||
---@param deps table Dependencies table with ErrorCodes
|
||||
function ErrorHandler.init(deps)
|
||||
if deps and deps.ErrorCodes then
|
||||
ErrorCodes = deps.ErrorCodes
|
||||
else
|
||||
-- Try to require if not provided (backward compatibility)
|
||||
local success, module = pcall(require, "modules.ErrorCodes")
|
||||
if success then
|
||||
ErrorCodes = module
|
||||
else
|
||||
-- Create minimal stub if ErrorCodes not available
|
||||
ErrorCodes = {
|
||||
get = function() return nil end,
|
||||
describe = function(code) return code end,
|
||||
getSuggestion = function() return "" end,
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Set debug mode (enables stack traces and verbose output)
|
||||
---@param enabled boolean Enable debug mode
|
||||
function ErrorHandler.setDebugMode(enabled)
|
||||
config.debugMode = enabled
|
||||
config.includeStackTrace = enabled
|
||||
if enabled then
|
||||
config.logLevel = LOG_LEVELS.DEBUG
|
||||
end
|
||||
end
|
||||
|
||||
--- Set whether to include stack traces
|
||||
---@param enabled boolean Enable stack traces
|
||||
function ErrorHandler.setStackTrace(enabled)
|
||||
config.includeStackTrace = enabled
|
||||
end
|
||||
|
||||
--- Set log level (minimum level to log)
|
||||
---@param level string|number Log level ("CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG") or number
|
||||
function ErrorHandler.setLogLevel(level)
|
||||
if type(level) == "string" then
|
||||
config.logLevel = LOG_LEVELS[level:upper()] or LOG_LEVELS.WARNING
|
||||
elseif type(level) == "number" then
|
||||
config.logLevel = level
|
||||
end
|
||||
end
|
||||
|
||||
--- Set log target
|
||||
---@param target string "console", "file", "both", or "none"
|
||||
function ErrorHandler.setLogTarget(target)
|
||||
config.logTarget = target
|
||||
-- Note: File will be opened lazily on first write
|
||||
if target == "console" or target == "none" then
|
||||
-- Close log file if open
|
||||
if logFileHandle then
|
||||
logFileHandle:close()
|
||||
logFileHandle = nil
|
||||
currentLogSize = 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Set log format
|
||||
---@param format string "human" or "json"
|
||||
function ErrorHandler.setLogFormat(format)
|
||||
config.logFormat = format
|
||||
end
|
||||
|
||||
--- Set log file path
|
||||
---@param path string Path to log file
|
||||
function ErrorHandler.setLogFile(path)
|
||||
-- Close existing log file
|
||||
if logFileHandle then
|
||||
logFileHandle:close()
|
||||
logFileHandle = nil
|
||||
end
|
||||
|
||||
config.logFile = path
|
||||
currentLogSize = 0
|
||||
|
||||
-- Note: File will be opened lazily on first write
|
||||
end
|
||||
|
||||
--- Enable/disable log rotation
|
||||
---@param enabled boolean|table Enable rotation or config table
|
||||
function ErrorHandler.enableLogRotation(enabled)
|
||||
if type(enabled) == "boolean" then
|
||||
config.enableRotation = enabled
|
||||
elseif type(enabled) == "table" then
|
||||
config.enableRotation = true
|
||||
if enabled.maxSize then
|
||||
config.maxLogSize = enabled.maxSize
|
||||
end
|
||||
if enabled.maxFiles then
|
||||
config.maxLogFiles = enabled.maxFiles
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Get current timestamp with milliseconds
|
||||
---@return string Formatted timestamp
|
||||
local function getTimestamp()
|
||||
local time = os.time()
|
||||
local date = os.date("%Y-%m-%d %H:%M:%S", time)
|
||||
-- Note: Lua doesn't have millisecond precision by default, so we approximate
|
||||
return date
|
||||
end
|
||||
|
||||
--- Rotate log file if needed
|
||||
local function rotateLogIfNeeded()
|
||||
if not config.enableRotation then
|
||||
return
|
||||
end
|
||||
if currentLogSize < config.maxLogSize then
|
||||
return
|
||||
end
|
||||
|
||||
-- Close current log
|
||||
if logFileHandle then
|
||||
logFileHandle:close()
|
||||
logFileHandle = nil
|
||||
end
|
||||
|
||||
-- Rotate existing logs
|
||||
for i = config.maxLogFiles - 1, 1, -1 do
|
||||
local oldName = config.logFile .. "." .. i
|
||||
local newName = config.logFile .. "." .. (i + 1)
|
||||
os.rename(oldName, newName) -- Will fail silently if file doesn't exist
|
||||
end
|
||||
|
||||
-- Move current log to .1
|
||||
os.rename(config.logFile, config.logFile .. ".1")
|
||||
|
||||
-- Create new log file
|
||||
logFileHandle = io.open(config.logFile, "a")
|
||||
currentLogSize = 0
|
||||
end
|
||||
|
||||
--- Escape string for JSON
|
||||
---@param str string String to escape
|
||||
---@return string Escaped string
|
||||
local function escapeJson(str)
|
||||
str = tostring(str)
|
||||
str = str:gsub("\\", "\\\\")
|
||||
str = str:gsub('"', '\\"')
|
||||
str = str:gsub("\n", "\\n")
|
||||
str = str:gsub("\r", "\\r")
|
||||
str = str:gsub("\t", "\\t")
|
||||
return str
|
||||
end
|
||||
|
||||
--- Format details as JSON object
|
||||
---@param details table|nil Details object
|
||||
---@return string JSON string
|
||||
local function formatDetailsJson(details)
|
||||
if not details or type(details) ~= "table" then
|
||||
return "{}"
|
||||
end
|
||||
|
||||
local parts = {}
|
||||
for key, value in pairs(details) do
|
||||
local jsonKey = escapeJson(tostring(key))
|
||||
local jsonValue = escapeJson(tostring(value))
|
||||
table.insert(parts, string.format('"%s":"%s"', jsonKey, jsonValue))
|
||||
end
|
||||
|
||||
return "{" .. table.concat(parts, ",") .. "}"
|
||||
end
|
||||
|
||||
--- Format details object as readable key-value pairs
|
||||
---@param details table|nil Details object
|
||||
---@return string Formatted details
|
||||
local function formatDetails(details)
|
||||
if not details or type(details) ~= "table" then
|
||||
return ""
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
for key, value in pairs(details) do
|
||||
local formattedKey = tostring(key):gsub("^%l", string.upper)
|
||||
local formattedValue = tostring(value)
|
||||
-- Truncate very long values
|
||||
if #formattedValue > 100 then
|
||||
formattedValue = formattedValue:sub(1, 97) .. "..."
|
||||
end
|
||||
table.insert(lines, string.format(" %s: %s", formattedKey, formattedValue))
|
||||
end
|
||||
|
||||
if #lines > 0 then
|
||||
return "\n\nDetails:\n" .. table.concat(lines, "\n")
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
--- Extract and format stack trace
|
||||
---@param level number Stack level to start from
|
||||
---@return string Formatted stack trace
|
||||
local function formatStackTrace(level)
|
||||
if not config.includeStackTrace then
|
||||
return ""
|
||||
end
|
||||
|
||||
local lines = {}
|
||||
local currentLevel = level or 3
|
||||
|
||||
while true do
|
||||
local info = debug.getinfo(currentLevel, "Sl")
|
||||
if not info then
|
||||
break
|
||||
end
|
||||
|
||||
-- Skip internal Lua files
|
||||
if info.source:match("^@") and not info.source:match("loveStub") then
|
||||
local source = info.source:sub(2) -- Remove @ prefix
|
||||
local location = string.format("%s:%d", source, info.currentline)
|
||||
table.insert(lines, " " .. location)
|
||||
end
|
||||
|
||||
currentLevel = currentLevel + 1
|
||||
if currentLevel > level + 10 then
|
||||
break
|
||||
end -- Limit depth
|
||||
end
|
||||
|
||||
if #lines > 0 then
|
||||
return "\n\nStack trace:\n" .. table.concat(lines, "\n")
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
--- Format an error or warning message with optional error code
|
||||
---@param module string The module name (e.g., "Element", "Units", "Theme")
|
||||
---@param level string "Error" or "Warning"
|
||||
---@param message string The error/warning message
|
||||
---@param codeOrMessage string Error code (e.g., "VAL_001") or message
|
||||
---@param messageOrDetails string|table|nil Message or details object
|
||||
---@param detailsOrSuggestion table|string|nil Details or suggestion
|
||||
---@param suggestionOrNil string|nil Suggestion
|
||||
---@return string Formatted message
|
||||
local function formatMessage(module, level, message)
|
||||
return string.format("[FlexLove - %s] %s: %s", module, level, message)
|
||||
local function formatMessage(module, level, codeOrMessage, messageOrDetails, detailsOrSuggestion, suggestionOrNil)
|
||||
local code = nil
|
||||
local message = codeOrMessage
|
||||
local details = nil
|
||||
local suggestion = nil
|
||||
|
||||
-- Parse arguments (support multiple signatures)
|
||||
if type(codeOrMessage) == "string" and ErrorCodes.get(codeOrMessage) then
|
||||
-- Called with error code
|
||||
code = codeOrMessage
|
||||
message = messageOrDetails or ErrorCodes.describe(code)
|
||||
|
||||
if type(detailsOrSuggestion) == "table" then
|
||||
details = detailsOrSuggestion
|
||||
suggestion = suggestionOrNil or ErrorCodes.getSuggestion(code)
|
||||
elseif type(detailsOrSuggestion) == "string" then
|
||||
suggestion = detailsOrSuggestion
|
||||
else
|
||||
suggestion = ErrorCodes.getSuggestion(code)
|
||||
end
|
||||
else
|
||||
-- Called with message only (backward compatibility)
|
||||
message = codeOrMessage
|
||||
if type(messageOrDetails) == "table" then
|
||||
details = messageOrDetails
|
||||
suggestion = detailsOrSuggestion
|
||||
elseif type(messageOrDetails) == "string" then
|
||||
suggestion = messageOrDetails
|
||||
end
|
||||
end
|
||||
|
||||
-- Build formatted message
|
||||
local parts = {}
|
||||
|
||||
-- Header: [FlexLove - Module] Level [CODE]: Message
|
||||
if code then
|
||||
local codeInfo = ErrorCodes.get(code)
|
||||
table.insert(parts, string.format("[FlexLove - %s] %s [%s]: %s", module, level, codeInfo.code, message))
|
||||
else
|
||||
table.insert(parts, string.format("[FlexLove - %s] %s: %s", module, level, message))
|
||||
end
|
||||
|
||||
-- Details section
|
||||
if details then
|
||||
table.insert(parts, formatDetails(details))
|
||||
end
|
||||
|
||||
-- Suggestion section
|
||||
if suggestion and suggestion ~= "" then
|
||||
table.insert(parts, string.format("\n\nSuggestion: %s", suggestion))
|
||||
end
|
||||
|
||||
return table.concat(parts, "")
|
||||
end
|
||||
|
||||
--- Write log entry to file and/or console
|
||||
---@param level string Log level
|
||||
---@param levelNum number Log level number
|
||||
---@param module string Module name
|
||||
---@param code string|nil Error code
|
||||
---@param message string Message
|
||||
---@param details table|nil Details
|
||||
---@param suggestion string|nil Suggestion
|
||||
local function writeLog(level, levelNum, module, code, message, details, suggestion)
|
||||
-- Check if we should log this level
|
||||
if levelNum > config.logLevel then
|
||||
return
|
||||
end
|
||||
|
||||
local timestamp = getTimestamp()
|
||||
local logEntry
|
||||
|
||||
if config.logFormat == "json" then
|
||||
-- JSON format
|
||||
local jsonParts = {
|
||||
string.format('"timestamp":"%s"', escapeJson(timestamp)),
|
||||
string.format('"level":"%s"', level),
|
||||
string.format('"module":"%s"', escapeJson(module)),
|
||||
string.format('"message":"%s"', escapeJson(message)),
|
||||
}
|
||||
|
||||
if code then
|
||||
table.insert(jsonParts, string.format('"code":"%s"', escapeJson(code)))
|
||||
end
|
||||
|
||||
if details then
|
||||
table.insert(jsonParts, string.format('"details":%s', formatDetailsJson(details)))
|
||||
end
|
||||
|
||||
if suggestion then
|
||||
table.insert(jsonParts, string.format('"suggestion":"%s"', escapeJson(suggestion)))
|
||||
end
|
||||
|
||||
logEntry = "{" .. table.concat(jsonParts, ",") .. "}\n"
|
||||
else
|
||||
-- Human-readable format
|
||||
local parts = {
|
||||
string.format("[%s] [%s] [%s]", timestamp, level, module),
|
||||
}
|
||||
|
||||
if code then
|
||||
table.insert(parts, string.format("[%s]", code))
|
||||
end
|
||||
|
||||
table.insert(parts, message)
|
||||
logEntry = table.concat(parts, " ") .. "\n"
|
||||
|
||||
if details then
|
||||
logEntry = logEntry .. formatDetails(details):gsub("^\n\n", "") .. "\n"
|
||||
end
|
||||
|
||||
if suggestion then
|
||||
logEntry = logEntry .. "Suggestion: " .. suggestion .. "\n"
|
||||
end
|
||||
|
||||
logEntry = logEntry .. "\n"
|
||||
end
|
||||
|
||||
-- Write to console
|
||||
if config.logTarget == "console" or config.logTarget == "both" then
|
||||
io.write(logEntry)
|
||||
io.flush()
|
||||
end
|
||||
|
||||
-- Write to file
|
||||
if config.logTarget == "file" or config.logTarget == "both" then
|
||||
-- Lazy file opening: open on first write
|
||||
if not logFileHandle then
|
||||
logFileHandle = io.open(config.logFile, "a")
|
||||
if logFileHandle then
|
||||
-- Get current file size
|
||||
local currentPos = logFileHandle:seek("end")
|
||||
currentLogSize = currentPos or 0
|
||||
end
|
||||
end
|
||||
|
||||
if logFileHandle then
|
||||
rotateLogIfNeeded()
|
||||
|
||||
-- Reopen if rotation closed it
|
||||
if not logFileHandle then
|
||||
logFileHandle = io.open(config.logFile, "a")
|
||||
end
|
||||
|
||||
if logFileHandle then
|
||||
logFileHandle:write(logEntry)
|
||||
logFileHandle:flush()
|
||||
currentLogSize = currentLogSize + #logEntry
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Throw a critical error (stops execution)
|
||||
---@param module string The module name
|
||||
---@param message string The error message
|
||||
function ErrorHandler.error(module, message)
|
||||
error(formatMessage(module, "Error", message), 2)
|
||||
---@param codeOrMessage string Error code or message
|
||||
---@param messageOrDetails string|table|nil Message or details
|
||||
---@param detailsOrSuggestion table|string|nil Details or suggestion
|
||||
---@param suggestion string|nil Suggestion
|
||||
function ErrorHandler.error(module, codeOrMessage, messageOrDetails, detailsOrSuggestion, suggestion)
|
||||
local formattedMessage = formatMessage(module, "Error", codeOrMessage, messageOrDetails, detailsOrSuggestion, suggestion)
|
||||
|
||||
-- Parse arguments for logging
|
||||
local code = nil
|
||||
local message = codeOrMessage
|
||||
local details = nil
|
||||
local logSuggestion = nil
|
||||
|
||||
if type(codeOrMessage) == "string" and ErrorCodes.get(codeOrMessage) then
|
||||
code = codeOrMessage
|
||||
message = messageOrDetails or ErrorCodes.describe(code)
|
||||
|
||||
if type(detailsOrSuggestion) == "table" then
|
||||
details = detailsOrSuggestion
|
||||
logSuggestion = suggestion or ErrorCodes.getSuggestion(code)
|
||||
elseif type(detailsOrSuggestion) == "string" then
|
||||
logSuggestion = detailsOrSuggestion
|
||||
else
|
||||
logSuggestion = ErrorCodes.getSuggestion(code)
|
||||
end
|
||||
else
|
||||
message = codeOrMessage
|
||||
if type(messageOrDetails) == "table" then
|
||||
details = messageOrDetails
|
||||
logSuggestion = detailsOrSuggestion
|
||||
elseif type(messageOrDetails) == "string" then
|
||||
logSuggestion = messageOrDetails
|
||||
end
|
||||
end
|
||||
|
||||
-- Log the error
|
||||
writeLog("ERROR", LOG_LEVELS.ERROR, module, code, message, details, logSuggestion)
|
||||
|
||||
-- Add stack trace if enabled
|
||||
if config.includeStackTrace then
|
||||
formattedMessage = formattedMessage .. formatStackTrace(3)
|
||||
end
|
||||
|
||||
error(formattedMessage, 2)
|
||||
end
|
||||
|
||||
--- Print a warning (non-critical, continues execution)
|
||||
---@param module string The module name
|
||||
---@param message string The warning message
|
||||
function ErrorHandler.warn(module, message)
|
||||
print(formatMessage(module, "Warning", message))
|
||||
---@param codeOrMessage string Warning code or message
|
||||
---@param messageOrDetails string|table|nil Message or details
|
||||
---@param detailsOrSuggestion table|string|nil Details or suggestion
|
||||
---@param suggestion string|nil Suggestion
|
||||
function ErrorHandler.warn(module, codeOrMessage, messageOrDetails, detailsOrSuggestion, suggestion)
|
||||
-- Parse arguments for logging
|
||||
local code = nil
|
||||
local message = codeOrMessage
|
||||
local details = nil
|
||||
local logSuggestion = nil
|
||||
|
||||
if type(codeOrMessage) == "string" and ErrorCodes.get(codeOrMessage) then
|
||||
code = codeOrMessage
|
||||
message = messageOrDetails or ErrorCodes.describe(code)
|
||||
|
||||
if type(detailsOrSuggestion) == "table" then
|
||||
details = detailsOrSuggestion
|
||||
logSuggestion = suggestion or ErrorCodes.getSuggestion(code)
|
||||
elseif type(detailsOrSuggestion) == "string" then
|
||||
logSuggestion = detailsOrSuggestion
|
||||
else
|
||||
logSuggestion = ErrorCodes.getSuggestion(code)
|
||||
end
|
||||
else
|
||||
message = codeOrMessage
|
||||
if type(messageOrDetails) == "table" then
|
||||
details = messageOrDetails
|
||||
logSuggestion = detailsOrSuggestion
|
||||
elseif type(messageOrDetails) == "string" then
|
||||
logSuggestion = messageOrDetails
|
||||
end
|
||||
end
|
||||
|
||||
-- Log the warning
|
||||
writeLog("WARNING", LOG_LEVELS.WARNING, module, code, message, details, logSuggestion)
|
||||
|
||||
local formattedMessage = formatMessage(module, "Warning", codeOrMessage, messageOrDetails, detailsOrSuggestion, suggestion)
|
||||
print(formattedMessage)
|
||||
end
|
||||
|
||||
--- Validate that a value is not nil
|
||||
@@ -31,7 +513,9 @@ end
|
||||
---@return boolean True if valid
|
||||
function ErrorHandler.assertNotNil(module, value, paramName)
|
||||
if value == nil then
|
||||
ErrorHandler.error(module, string.format("Parameter '%s' cannot be nil", paramName))
|
||||
ErrorHandler.error(module, "VAL_003", "Required parameter missing", {
|
||||
parameter = paramName,
|
||||
})
|
||||
return false
|
||||
end
|
||||
return true
|
||||
@@ -46,10 +530,11 @@ end
|
||||
function ErrorHandler.assertType(module, value, expectedType, paramName)
|
||||
local actualType = type(value)
|
||||
if actualType ~= expectedType then
|
||||
ErrorHandler.error(module, string.format(
|
||||
"Parameter '%s' must be %s, got %s",
|
||||
paramName, expectedType, actualType
|
||||
))
|
||||
ErrorHandler.error(module, "VAL_001", "Invalid property type", {
|
||||
property = paramName,
|
||||
expected = expectedType,
|
||||
got = actualType,
|
||||
})
|
||||
return false
|
||||
end
|
||||
return true
|
||||
@@ -64,10 +549,12 @@ end
|
||||
---@return boolean True if valid
|
||||
function ErrorHandler.assertRange(module, value, min, max, paramName)
|
||||
if value < min or value > max then
|
||||
ErrorHandler.error(module, string.format(
|
||||
"Parameter '%s' must be between %s and %s, got %s",
|
||||
paramName, tostring(min), tostring(max), tostring(value)
|
||||
))
|
||||
ErrorHandler.error(module, "VAL_002", "Property value out of range", {
|
||||
property = paramName,
|
||||
min = tostring(min),
|
||||
max = tostring(max),
|
||||
value = tostring(value),
|
||||
})
|
||||
return false
|
||||
end
|
||||
return true
|
||||
@@ -78,10 +565,7 @@ end
|
||||
---@param oldName string The deprecated name
|
||||
---@param newName string The new name to use
|
||||
function ErrorHandler.warnDeprecated(module, oldName, newName)
|
||||
ErrorHandler.warn(module, string.format(
|
||||
"'%s' is deprecated. Use '%s' instead",
|
||||
oldName, newName
|
||||
))
|
||||
ErrorHandler.warn(module, string.format("'%s' is deprecated. Use '%s' instead", oldName, newName))
|
||||
end
|
||||
|
||||
--- Warn about a common mistake
|
||||
@@ -89,10 +573,7 @@ end
|
||||
---@param issue string Description of the issue
|
||||
---@param suggestion string Suggested fix
|
||||
function ErrorHandler.warnCommonMistake(module, issue, suggestion)
|
||||
ErrorHandler.warn(module, string.format(
|
||||
"%s. Suggestion: %s",
|
||||
issue, suggestion
|
||||
))
|
||||
ErrorHandler.warn(module, string.format("%s. Suggestion: %s", issue, suggestion))
|
||||
end
|
||||
|
||||
return ErrorHandler
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
--- Standardized error message formatter
|
||||
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
||||
---@param message string -- Error message
|
||||
---@return string -- Formatted error message
|
||||
local function formatError(module, message)
|
||||
return string.format("[FlexLove.%s] %s", module, message)
|
||||
end
|
||||
|
||||
local ImageDataReader = {}
|
||||
|
||||
-- ErrorHandler will be injected via init
|
||||
local ErrorHandler = nil
|
||||
|
||||
--- Initialize ImageDataReader with dependencies
|
||||
---@param deps table Dependencies table with ErrorHandler
|
||||
function ImageDataReader.init(deps)
|
||||
if deps and deps.ErrorHandler then
|
||||
ErrorHandler = deps.ErrorHandler
|
||||
end
|
||||
end
|
||||
|
||||
---@param imagePath string
|
||||
---@return love.ImageData
|
||||
function ImageDataReader.loadImageData(imagePath)
|
||||
if not imagePath then
|
||||
error(formatError("ImageDataReader", "Image path cannot be nil"))
|
||||
ErrorHandler.error("ImageDataReader", "VAL_001", "Image path cannot be nil")
|
||||
end
|
||||
|
||||
local success, result = pcall(function()
|
||||
@@ -20,7 +23,10 @@ function ImageDataReader.loadImageData(imagePath)
|
||||
end)
|
||||
|
||||
if not success then
|
||||
error(formatError("ImageDataReader", "Failed to load image data from '" .. imagePath .. "': " .. tostring(result)))
|
||||
ErrorHandler.error("ImageDataReader", "RES_001", "Failed to load image data from '" .. imagePath .. "': " .. tostring(result), {
|
||||
imagePath = imagePath,
|
||||
error = tostring(result),
|
||||
})
|
||||
end
|
||||
|
||||
return result
|
||||
@@ -32,14 +38,17 @@ end
|
||||
---@return table -- Array of {r, g, b, a} values (0-255 range)
|
||||
function ImageDataReader.getRow(imageData, rowIndex)
|
||||
if not imageData then
|
||||
error(formatError("ImageDataReader", "ImageData cannot be nil"))
|
||||
ErrorHandler.error("ImageDataReader", "VAL_001", "ImageData cannot be nil")
|
||||
end
|
||||
|
||||
local width = imageData:getWidth()
|
||||
local height = imageData:getHeight()
|
||||
|
||||
if rowIndex < 0 or rowIndex >= height then
|
||||
error(formatError("ImageDataReader", string.format("Row index %d out of bounds (height: %d)", rowIndex, height)))
|
||||
ErrorHandler.error("ImageDataReader", "VAL_002", string.format("Row index %d out of bounds (height: %d)", rowIndex, height), {
|
||||
rowIndex = rowIndex,
|
||||
height = height,
|
||||
})
|
||||
end
|
||||
|
||||
local pixels = {}
|
||||
@@ -62,14 +71,17 @@ end
|
||||
---@return table -- Array of {r, g, b, a} values (0-255 range)
|
||||
function ImageDataReader.getColumn(imageData, colIndex)
|
||||
if not imageData then
|
||||
error(formatError("ImageDataReader", "ImageData cannot be nil"))
|
||||
ErrorHandler.error("ImageDataReader", "VAL_001", "ImageData cannot be nil")
|
||||
end
|
||||
|
||||
local width = imageData:getWidth()
|
||||
local height = imageData:getHeight()
|
||||
|
||||
if colIndex < 0 or colIndex >= width then
|
||||
error(formatError("ImageDataReader", string.format("Column index %d out of bounds (width: %d)", colIndex, width)))
|
||||
ErrorHandler.error("ImageDataReader", "VAL_002", string.format("Column index %d out of bounds (width: %d)", colIndex, width), {
|
||||
colIndex = colIndex,
|
||||
width = width,
|
||||
})
|
||||
end
|
||||
|
||||
local pixels = {}
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
--- Standardized error message formatter
|
||||
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
||||
---@param message string -- Error message
|
||||
---@return string -- Formatted error message
|
||||
local function formatError(module, message)
|
||||
return string.format("[FlexLove.%s] %s", module, message)
|
||||
end
|
||||
|
||||
---@class ImageRenderer
|
||||
local ImageRenderer = {}
|
||||
|
||||
-- ErrorHandler will be injected via init
|
||||
local ErrorHandler = nil
|
||||
|
||||
--- Initialize ImageRenderer with dependencies
|
||||
---@param deps table Dependencies table with ErrorHandler
|
||||
function ImageRenderer.init(deps)
|
||||
if deps and deps.ErrorHandler then
|
||||
ErrorHandler = deps.ErrorHandler
|
||||
end
|
||||
end
|
||||
|
||||
--- Calculate rendering parameters for object-fit modes
|
||||
--- Returns source and destination rectangles for rendering
|
||||
---@param imageWidth number -- Natural width of the image
|
||||
@@ -23,7 +26,12 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
|
||||
objectPosition = objectPosition or "center center"
|
||||
|
||||
if imageWidth <= 0 or imageHeight <= 0 or boundsWidth <= 0 or boundsHeight <= 0 then
|
||||
error(formatError("ImageRenderer", "Dimensions must be positive"))
|
||||
ErrorHandler.error("ImageRenderer", "VAL_002", "Dimensions must be positive", {
|
||||
imageWidth = imageWidth,
|
||||
imageHeight = imageHeight,
|
||||
boundsWidth = boundsWidth,
|
||||
boundsHeight = boundsHeight,
|
||||
})
|
||||
end
|
||||
|
||||
local result = {
|
||||
@@ -104,7 +112,12 @@ function ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, bounds
|
||||
return ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, boundsHeight, "contain", objectPosition)
|
||||
end
|
||||
else
|
||||
error(formatError("ImageRenderer", string.format("Invalid fit mode: '%s'. Must be one of: fill, contain, cover, scale-down, none", tostring(fitMode))))
|
||||
ErrorHandler.warn("ImageRenderer", "VAL_007", string.format("Invalid fit mode: '%s'. Must be one of: fill, contain, cover, scale-down, none", tostring(fitMode)), {
|
||||
fitMode = fitMode,
|
||||
fallback = "fill"
|
||||
})
|
||||
-- Use 'fill' as fallback
|
||||
return ImageRenderer.calculateFit(imageWidth, imageHeight, boundsWidth, boundsHeight, "fill", objectPosition)
|
||||
end
|
||||
|
||||
return result
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
--- Standardized error message formatter
|
||||
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
||||
---@param message string -- Error message
|
||||
---@return string -- Formatted error message
|
||||
local function formatError(module, message)
|
||||
return string.format("[FlexLove.%s] %s", module, message)
|
||||
end
|
||||
|
||||
-- ====================
|
||||
-- ImageScaler
|
||||
-- ====================
|
||||
|
||||
local ImageScaler = {}
|
||||
|
||||
-- ErrorHandler will be injected via init
|
||||
local ErrorHandler = nil
|
||||
|
||||
--- Initialize ImageScaler with dependencies
|
||||
---@param deps table Dependencies table with ErrorHandler
|
||||
function ImageScaler.init(deps)
|
||||
if deps and deps.ErrorHandler then
|
||||
ErrorHandler = deps.ErrorHandler
|
||||
end
|
||||
end
|
||||
|
||||
--- Scale an ImageData region using nearest-neighbor sampling
|
||||
--- Produces sharp, pixelated scaling - ideal for pixel art
|
||||
---@param sourceImageData love.ImageData -- Source image data
|
||||
@@ -24,11 +27,21 @@ local ImageScaler = {}
|
||||
---@return love.ImageData -- Scaled image data
|
||||
function ImageScaler.scaleNearest(sourceImageData, srcX, srcY, srcW, srcH, destW, destH)
|
||||
if not sourceImageData then
|
||||
error(formatError("ImageScaler", "Source ImageData cannot be nil"))
|
||||
ErrorHandler.error("ImageScaler", "VAL_001", "Source ImageData cannot be nil")
|
||||
end
|
||||
|
||||
if srcW <= 0 or srcH <= 0 or destW <= 0 or destH <= 0 then
|
||||
error(formatError("ImageScaler", "Dimensions must be positive"))
|
||||
ErrorHandler.warn("ImageScaler", "VAL_002", "Dimensions must be positive", {
|
||||
srcW = srcW,
|
||||
srcH = srcH,
|
||||
destW = destW,
|
||||
destH = destH,
|
||||
fallback = "1x1 transparent image"
|
||||
})
|
||||
-- Return a minimal 1x1 transparent image as fallback
|
||||
local fallbackImageData = love.image.newImageData(1, 1)
|
||||
fallbackImageData:setPixel(0, 0, 0, 0, 0, 0)
|
||||
return fallbackImageData
|
||||
end
|
||||
|
||||
-- Create destination ImageData
|
||||
@@ -82,11 +95,21 @@ end
|
||||
---@return love.ImageData -- Scaled image data
|
||||
function ImageScaler.scaleBilinear(sourceImageData, srcX, srcY, srcW, srcH, destW, destH)
|
||||
if not sourceImageData then
|
||||
error(formatError("ImageScaler", "Source ImageData cannot be nil"))
|
||||
ErrorHandler.error("ImageScaler", "VAL_001", "Source ImageData cannot be nil")
|
||||
end
|
||||
|
||||
if srcW <= 0 or srcH <= 0 or destW <= 0 or destH <= 0 then
|
||||
error(formatError("ImageScaler", "Dimensions must be positive"))
|
||||
ErrorHandler.warn("ImageScaler", "VAL_002", "Dimensions must be positive", {
|
||||
srcW = srcW,
|
||||
srcH = srcH,
|
||||
destW = destW,
|
||||
destH = destH,
|
||||
fallback = "1x1 transparent image"
|
||||
})
|
||||
-- Return a minimal 1x1 transparent image as fallback
|
||||
local fallbackImageData = love.image.newImageData(1, 1)
|
||||
fallbackImageData:setPixel(0, 0, 0, 0, 0, 0)
|
||||
return fallbackImageData
|
||||
end
|
||||
|
||||
-- Create destination ImageData
|
||||
|
||||
@@ -1,16 +1,23 @@
|
||||
local modulePath = (...):match("(.-)[^%.]+$")
|
||||
local ImageScaler = require(modulePath .. "ImageScaler")
|
||||
|
||||
--- Standardized error message formatter
|
||||
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
||||
---@param message string -- Error message
|
||||
---@return string -- Formatted error message
|
||||
local function formatError(module, message)
|
||||
return string.format("[FlexLove.%s] %s", module, message)
|
||||
end
|
||||
|
||||
local NinePatch = {}
|
||||
|
||||
-- ErrorHandler will be injected via init
|
||||
local ErrorHandler = nil
|
||||
|
||||
--- Initialize NinePatch with dependencies
|
||||
---@param deps table Dependencies table with ErrorHandler
|
||||
function NinePatch.init(deps)
|
||||
if deps and deps.ErrorHandler then
|
||||
ErrorHandler = deps.ErrorHandler
|
||||
end
|
||||
-- Also initialize ImageScaler since it's a dependency
|
||||
if ImageScaler.init then
|
||||
ImageScaler.init(deps)
|
||||
end
|
||||
end
|
||||
|
||||
--- Draw a 9-patch component using Android-style rendering
|
||||
--- Corners are scaled by scaleCorners multiplier, edges stretch in one dimension only
|
||||
---@param component ThemeComponent
|
||||
@@ -92,7 +99,9 @@ function NinePatch.draw(component, atlas, x, y, width, height, opacity, elementS
|
||||
-- Get ImageData from component (stored during theme loading)
|
||||
local atlasData = component._loadedAtlasData
|
||||
if not atlasData then
|
||||
error(formatError("NinePatch", "No ImageData available for atlas. Image must be loaded with safeLoadImage."))
|
||||
ErrorHandler.error("NinePatch", "REN_007", "No ImageData available for atlas. Image must be loaded with safeLoadImage.", {
|
||||
componentType = component.type,
|
||||
})
|
||||
end
|
||||
|
||||
local scaledData
|
||||
|
||||
@@ -30,6 +30,9 @@
|
||||
local Renderer = {}
|
||||
Renderer.__index = Renderer
|
||||
|
||||
-- Lazy-loaded ErrorHandler
|
||||
local ErrorHandler
|
||||
|
||||
--- Create a new Renderer instance
|
||||
---@param config table Configuration table with rendering properties
|
||||
---@param deps table Dependencies {Color, RoundedRect, NinePatch, ImageRenderer, ImageCache, Theme, Blur, utils}
|
||||
@@ -309,7 +312,12 @@ function Renderer:draw(backdropCanvas)
|
||||
|
||||
-- Element must be initialized before drawing
|
||||
if not self._element then
|
||||
error("Renderer:draw() called before initialize(). Call renderer:initialize(element) first.")
|
||||
if not ErrorHandler then
|
||||
ErrorHandler = require("modules.ErrorHandler")
|
||||
end
|
||||
ErrorHandler.error("Renderer", "SYS_002", "Method called before initialization", {
|
||||
method = "draw"
|
||||
}, "Call renderer:initialize(element) before rendering")
|
||||
end
|
||||
|
||||
local element = self._element
|
||||
|
||||
@@ -26,9 +26,13 @@
|
||||
---@field _scrollbarPressHandled boolean -- Track if scrollbar press was handled this frame
|
||||
---@field _Color table
|
||||
---@field _utils table
|
||||
---@field _ErrorHandler table?
|
||||
local ScrollManager = {}
|
||||
ScrollManager.__index = ScrollManager
|
||||
|
||||
-- Lazy-loaded ErrorHandler
|
||||
local ErrorHandler
|
||||
|
||||
--- Create a new ScrollManager instance
|
||||
---@param config table Configuration options
|
||||
---@param deps table Dependencies {Color: Color module, utils: utils module}
|
||||
@@ -92,7 +96,12 @@ end
|
||||
--- Detect if content overflows container bounds
|
||||
function ScrollManager:detectOverflow()
|
||||
if not self._element then
|
||||
error("ScrollManager:detectOverflow() called before initialize()")
|
||||
if not ErrorHandler then
|
||||
ErrorHandler = require("modules.ErrorHandler")
|
||||
end
|
||||
ErrorHandler.error("ScrollManager", "SYS_002", "Method called before initialization", {
|
||||
method = "detectOverflow"
|
||||
}, "Call scrollManager:initialize(element) before using scroll methods")
|
||||
end
|
||||
|
||||
local element = self._element
|
||||
@@ -219,7 +228,12 @@ end
|
||||
---@return table -- {vertical: {visible, trackHeight, thumbHeight, thumbY}, horizontal: {visible, trackWidth, thumbWidth, thumbX}}
|
||||
function ScrollManager:calculateScrollbarDimensions()
|
||||
if not self._element then
|
||||
error("ScrollManager:calculateScrollbarDimensions() called before initialize()")
|
||||
if not ErrorHandler then
|
||||
ErrorHandler = require("modules.ErrorHandler")
|
||||
end
|
||||
ErrorHandler.error("ScrollManager", "SYS_002", "Method called before initialization", {
|
||||
method = "calculateScrollbarDimensions"
|
||||
}, "Call scrollManager:initialize(element) before using scroll methods")
|
||||
end
|
||||
|
||||
local element = self._element
|
||||
@@ -312,7 +326,12 @@ end
|
||||
---@return table|nil -- {component: "vertical"|"horizontal", region: "thumb"|"track"}
|
||||
function ScrollManager:getScrollbarAtPosition(mouseX, mouseY)
|
||||
if not self._element then
|
||||
error("ScrollManager:getScrollbarAtPosition() called before initialize()")
|
||||
if not ErrorHandler then
|
||||
ErrorHandler = require("modules.ErrorHandler")
|
||||
end
|
||||
ErrorHandler.error("ScrollManager", "SYS_002", "Method called before initialization", {
|
||||
method = "getScrollbarAtPosition"
|
||||
}, "Call scrollManager:initialize(element) before using scroll methods")
|
||||
end
|
||||
|
||||
local element = self._element
|
||||
@@ -381,7 +400,12 @@ end
|
||||
---@return boolean -- True if event was consumed
|
||||
function ScrollManager:handleMousePress(mouseX, mouseY, button)
|
||||
if not self._element then
|
||||
error("ScrollManager:handleMousePress() called before initialize()")
|
||||
if not ErrorHandler then
|
||||
ErrorHandler = require("modules.ErrorHandler")
|
||||
end
|
||||
ErrorHandler.error("ScrollManager", "SYS_002", "Method called before initialization", {
|
||||
method = "handleMousePress"
|
||||
}, "Call scrollManager:initialize(element) before using scroll methods")
|
||||
end
|
||||
|
||||
if button ~= 1 then
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
---@class StateManager
|
||||
local StateManager = {}
|
||||
|
||||
-- Load error handler (loaded lazily since it's in a sibling module)
|
||||
local ErrorHandler
|
||||
|
||||
-- State storage: ID -> state table
|
||||
local stateStore = {}
|
||||
|
||||
@@ -181,7 +184,14 @@ end
|
||||
---@return table state State table for the element
|
||||
function StateManager.getState(id, defaultState)
|
||||
if not id then
|
||||
error("StateManager.getState: id is required")
|
||||
-- Lazy load ErrorHandler
|
||||
if not ErrorHandler then
|
||||
ErrorHandler = require("modules.ErrorHandler")
|
||||
end
|
||||
ErrorHandler.error("StateManager", "SYS_001", "Invalid state ID", {
|
||||
parameter = "id",
|
||||
value = "nil"
|
||||
}, "Provide a valid non-nil ID string to getState()")
|
||||
end
|
||||
|
||||
-- Create state if it doesn't exist
|
||||
@@ -303,7 +313,14 @@ end
|
||||
---@param state table State to store
|
||||
function StateManager.setState(id, state)
|
||||
if not id then
|
||||
error("StateManager.setState: id is required")
|
||||
-- Lazy load ErrorHandler
|
||||
if not ErrorHandler then
|
||||
ErrorHandler = require("modules.ErrorHandler")
|
||||
end
|
||||
ErrorHandler.error("StateManager", "SYS_001", "Invalid state ID", {
|
||||
parameter = "id",
|
||||
value = "nil"
|
||||
}, "Provide a valid non-nil ID string to setState()")
|
||||
end
|
||||
|
||||
stateStore[id] = state
|
||||
|
||||
@@ -6,14 +6,7 @@ end
|
||||
local NinePatchParser = req("NinePatchParser")
|
||||
local Color = req("Color")
|
||||
local utils = req("utils")
|
||||
|
||||
--- Standardized error message formatter
|
||||
---@param module string -- Module name (e.g., "Color", "Theme", "Units")
|
||||
---@param message string -- Error message
|
||||
---@return string -- Formatted error message
|
||||
local function formatError(module, message)
|
||||
return string.format("[FlexLove.%s] %s", module, message)
|
||||
end
|
||||
local ErrorHandler = req("ErrorHandler")
|
||||
|
||||
--- Auto-detect the base path where FlexLove is located
|
||||
---@return string modulePath, string filesystemPath
|
||||
@@ -135,7 +128,9 @@ function Theme.new(definition)
|
||||
-- Validate theme definition
|
||||
local valid, err = validateThemeDefinition(definition)
|
||||
if not valid then
|
||||
error("[FlexLove] Invalid theme definition: " .. tostring(err))
|
||||
ErrorHandler.error("Theme", "THM_001", "Invalid theme definition", {
|
||||
error = tostring(err)
|
||||
})
|
||||
end
|
||||
|
||||
local self = setmetatable({}, Theme)
|
||||
@@ -150,7 +145,11 @@ function Theme.new(definition)
|
||||
self.atlas = image
|
||||
self.atlasData = imageData
|
||||
else
|
||||
print("[FlexLove] Warning: Failed to load global atlas for theme '" .. definition.name .. "'" .. "(" .. loaderr .. ")")
|
||||
ErrorHandler.warn("Theme", "RES_001", "Failed to load global atlas", {
|
||||
theme = definition.name,
|
||||
path = resolvedPath,
|
||||
error = loaderr
|
||||
})
|
||||
end
|
||||
else
|
||||
self.atlas = definition.atlas
|
||||
@@ -174,7 +173,11 @@ function Theme.new(definition)
|
||||
local contentHeight = srcHeight - 2
|
||||
|
||||
if contentWidth <= 0 or contentHeight <= 0 then
|
||||
error(formatError("NinePatch", "Image too small to strip border"))
|
||||
ErrorHandler.error("Theme", "RES_002", "Nine-patch image too small", {
|
||||
width = srcWidth,
|
||||
height = srcHeight,
|
||||
reason = "Image must be larger than 2x2 pixels to have content after stripping 1px border"
|
||||
})
|
||||
end
|
||||
|
||||
-- Create new ImageData for content only
|
||||
@@ -204,7 +207,11 @@ function Theme.new(definition)
|
||||
comp.insets = parseResult.insets
|
||||
comp._ninePatchData = parseResult
|
||||
else
|
||||
print("[FlexLove] Warning: Failed to parse 9-patch " .. errorContext .. ": " .. tostring(parseErr))
|
||||
ErrorHandler.warn("Theme", "RES_003", "Failed to parse nine-patch image", {
|
||||
context = errorContext,
|
||||
path = resolvedPath,
|
||||
error = tostring(parseErr)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -221,7 +228,11 @@ function Theme.new(definition)
|
||||
comp._loadedAtlasData = imageData
|
||||
end
|
||||
else
|
||||
print("[FlexLove] Warning: Failed to load atlas " .. errorContext .. ": " .. tostring(loaderr))
|
||||
ErrorHandler.warn("Theme", "RES_001", "Failed to load atlas", {
|
||||
context = errorContext,
|
||||
path = resolvedPath,
|
||||
error = tostring(loaderr)
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -310,7 +321,13 @@ function Theme.load(path)
|
||||
if success then
|
||||
definition = result
|
||||
else
|
||||
error("Failed to load theme '" .. path .. "'\nTried: " .. themePath .. "\nError: " .. tostring(result))
|
||||
ErrorHandler.warn("Theme", "RES_004", "Failed to load theme file", {
|
||||
theme = path,
|
||||
tried = themePath,
|
||||
error = tostring(result),
|
||||
fallback = "nil (no theme loaded)"
|
||||
}, "Check that the theme file exists in the themes/ directory or provide a valid module path")
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -334,7 +351,12 @@ function Theme.setActive(themeOrName)
|
||||
end
|
||||
|
||||
if not activeTheme then
|
||||
error("Failed to set active theme: " .. tostring(themeOrName))
|
||||
ErrorHandler.warn("Theme", "THM_002", "Failed to set active theme", {
|
||||
theme = tostring(themeOrName),
|
||||
reason = "Theme not found or not loaded",
|
||||
fallback = "current theme unchanged"
|
||||
}, "Ensure the theme is loaded with Theme.load() before setting it active")
|
||||
-- Keep current activeTheme unchanged (fallback behavior)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -24,7 +24,13 @@ function Units.parse(value)
|
||||
|
||||
if type(value) ~= "string" then
|
||||
if ErrorHandler then
|
||||
ErrorHandler.error("Units", string.format("Invalid unit value type. Expected string or number, got %s", type(value)))
|
||||
ErrorHandler.warn("Units", "VAL_001", "Invalid property type", {
|
||||
property = "unit value",
|
||||
expected = "string or number",
|
||||
got = type(value)
|
||||
}, "Using fallback: 0px")
|
||||
else
|
||||
print(string.format("[FlexLove - Units] Warning: Invalid unit value type. Expected string or number, got %s. Using fallback: 0px", type(value)))
|
||||
end
|
||||
return 0, "px"
|
||||
end
|
||||
@@ -33,7 +39,12 @@ function Units.parse(value)
|
||||
local validUnits = { px = true, ["%"] = true, vw = true, vh = true, ew = true, eh = true }
|
||||
if validUnits[value] then
|
||||
if ErrorHandler then
|
||||
ErrorHandler.error("Units", string.format("Missing numeric value before unit '%s'. Use format like '50%s' (e.g., '50px', '10%%', '2vw')", value, value))
|
||||
ErrorHandler.warn("Units", "VAL_005", "Invalid unit format", {
|
||||
input = value,
|
||||
expected = "number + unit (e.g., '50" .. value .. "')"
|
||||
}, string.format("Add a numeric value before '%s', like '50%s'. Using fallback: 0px", value, value))
|
||||
else
|
||||
print(string.format("[FlexLove - Units] Warning: Missing numeric value before unit '%s'. Use format like '50%s'. Using fallback: 0px", value, value))
|
||||
end
|
||||
return 0, "px"
|
||||
end
|
||||
@@ -41,7 +52,12 @@ function Units.parse(value)
|
||||
-- Check for invalid format (space between number and unit)
|
||||
if value:match("%d%s+%a") then
|
||||
if ErrorHandler then
|
||||
ErrorHandler.error("Units", string.format("Invalid unit string '%s' (contains space). Use format like '50px' or '50%%'", value))
|
||||
ErrorHandler.warn("Units", "VAL_005", "Invalid unit format", {
|
||||
input = value,
|
||||
issue = "contains space between number and unit"
|
||||
}, "Remove spaces: use '50px' not '50 px'. Using fallback: 0px")
|
||||
else
|
||||
print(string.format("[FlexLove - Units] Warning: Invalid unit string '%s' (contains space). Use format like '50px' or '50%%'. Using fallback: 0px", value))
|
||||
end
|
||||
return 0, "px"
|
||||
end
|
||||
@@ -50,7 +66,11 @@ function Units.parse(value)
|
||||
local numStr, unit = value:match("^([%-]?[%d%.]+)(.*)$")
|
||||
if not numStr then
|
||||
if ErrorHandler then
|
||||
ErrorHandler.error("Units", string.format("Invalid unit format '%s'. Expected format: number + unit (e.g., '50px', '10%%', '2vw')", value))
|
||||
ErrorHandler.warn("Units", "VAL_005", "Invalid unit format", {
|
||||
input = value
|
||||
}, "Expected format: number + unit (e.g., '50px', '10%', '2vw'). Using fallback: 0px")
|
||||
else
|
||||
print(string.format("[FlexLove - Units] Warning: Invalid unit format '%s'. Expected format: number + unit (e.g., '50px', '10%%', '2vw'). Using fallback: 0px", value))
|
||||
end
|
||||
return 0, "px"
|
||||
end
|
||||
@@ -58,7 +78,12 @@ function Units.parse(value)
|
||||
local num = tonumber(numStr)
|
||||
if not num then
|
||||
if ErrorHandler then
|
||||
ErrorHandler.error("Units", string.format("Invalid numeric value in '%s'", value))
|
||||
ErrorHandler.warn("Units", "VAL_005", "Invalid unit format", {
|
||||
input = value,
|
||||
issue = "numeric value cannot be parsed"
|
||||
}, "Using fallback: 0px")
|
||||
else
|
||||
print(string.format("[FlexLove - Units] Warning: Invalid numeric value in '%s'. Using fallback: 0px", value))
|
||||
end
|
||||
return 0, "px"
|
||||
end
|
||||
@@ -71,7 +96,13 @@ function Units.parse(value)
|
||||
-- validUnits is already defined at the top of the function
|
||||
if not validUnits[unit] then
|
||||
if ErrorHandler then
|
||||
ErrorHandler.error("Units", string.format("Unknown unit '%s' in '%s'. Valid units: px, %%, vw, vh, ew, eh", unit, value))
|
||||
ErrorHandler.warn("Units", "VAL_005", "Invalid unit format", {
|
||||
input = value,
|
||||
unit = unit,
|
||||
validUnits = "px, %, vw, vh, ew, eh"
|
||||
}, string.format("Treating '%s' as pixels", value))
|
||||
else
|
||||
print(string.format("[FlexLove - Units] Warning: Unknown unit '%s' in '%s'. Valid units: px, %%, vw, vh, ew, eh. Treating as pixels", unit, value))
|
||||
end
|
||||
return num, "px"
|
||||
end
|
||||
@@ -93,7 +124,10 @@ function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize)
|
||||
elseif unit == "%" then
|
||||
if not parentSize then
|
||||
if ErrorHandler then
|
||||
ErrorHandler.error("Units", "Percentage units require parent dimension. Element has no parent or parent dimension not available")
|
||||
ErrorHandler.error("Units", "LAY_003", "Invalid dimensions", {
|
||||
unit = "%",
|
||||
issue = "parent dimension not available"
|
||||
}, "Percentage units require a parent element with explicit dimensions")
|
||||
else
|
||||
error("Percentage units require parent dimension")
|
||||
end
|
||||
@@ -105,7 +139,10 @@ function Units.resolve(value, unit, viewportWidth, viewportHeight, parentSize)
|
||||
return (value / 100) * viewportHeight
|
||||
else
|
||||
if ErrorHandler then
|
||||
ErrorHandler.error("Units", string.format("Unknown unit '%s'. Valid units: px, %%, vw, vh, ew, eh", unit))
|
||||
ErrorHandler.error("Units", "VAL_005", "Invalid unit format", {
|
||||
unit = unit,
|
||||
validUnits = "px, %, vw, vh, ew, eh"
|
||||
})
|
||||
else
|
||||
error(string.format("Unknown unit type: '%s'", unit))
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user