--- MiniConsole with logging capabilities -- @classmod LoggingConsole -- @author Damian Monogue -- @copyright 2020 Damian Monogue -- @license MIT, see LICENSE.lua local homedir = getMudletHomeDir():gsub("\\", "/") local pathOfThisFile = (...):match("(.-)[^%.]+$") local dt = require(pathOfThisFile .. "demontools") local exists, htmlHeader, htmlHeaderPattern = dt.exists, dt.htmlHeader, dt.htmlHeaderPattern local LoggingConsole = {log = true, logFormat = "h", path = "|h/log/consoleLogs/|y/|m/|d/", fileName = "|n.|e"} --- Creates and returns a new LoggingConsole. -- @param cons table of constraints. Includes all the valid Geyser.MiniConsole constraints, plus -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
option namedescriptiondefault
logShould the miniconsole be logging?true
logFormat"h" for html, "t" for plaintext, "l" for log (with ansi)h
pathThe path the file lives in. It is templated.
|h is replaced by the profile homedir.
|y by 4 digit year.
|m by 2 digit month
|d by 2 digit day
|n by the name constraint
|e by the file extension (html for h logType, log for others)
"|h/log/consoleLogs/|y/|m/|d/"
fileNameThe name of the log file. It is templated, same as path above"|n.|e"
-- @param container the container for the console -- @usage -- local LoggingConsole = require("MDK.loggingconsole") -- myLoggingConsole = LoggingConsole:new({ -- name = "my logging console", -- x = 0, -- y = 0, -- height = 200, -- width = 400, -- }) -- just like making a miniconsole, really function LoggingConsole:new(cons, container) cons = cons or {} local consType = type(cons) assert(consType == "table", "LoggingConsole:new(cons, container): cons must be a valid table of constraints. Got: " .. consType) local me = Geyser.MiniConsole:new(cons, container) setmetatable(me, self) self.__index = self return me end --- Returns the file extension of the logfile this console will log to function LoggingConsole:getExtension() local extension = "log" if table.contains({"h", "html"}, self.logFormat) then extension = "html" end return extension end --- Returns a string with all templated items replaced ---@tparam string str The templated string to transform ---@local function LoggingConsole:transformTemplate(str) local ttbl = getTime() local year = ttbl.year local month = string.format("%02d", ttbl.month) local day = string.format("%02d", ttbl.day) local name = self.name local extension = self:getExtension() str = str:gsub("|h", homedir) str = str:gsub("|y", year) str = str:gsub("|m", month) str = str:gsub("|d", day) str = str:gsub("|n", name) str = str:gsub("|e", extension) return str end --- Returns the path to the logfile for this console function LoggingConsole:getPath() local path = self:transformTemplate(self.path) if not path:ends("/") then path = path .. "/" end return path end --- Sets the path to use for the log file. -- @param path the path to put the log file in. It is templated.
|h is replaced by the profile homedir.
|y by 4 digit year.
|m by 2 digit month
|d by 2 digit day
|n by the name constraint
|e by the file extension (html for h logType, log for others) function LoggingConsole:setPath(path) self.path = path end --- Returns the filename for the logfile for this console function LoggingConsole:getFileName() local fileName = self:transformTemplate(self.fileName) fileName = fileName:gsub("[<>:'\"/\\?*]", "_") return fileName end --- Sets the fileName to use for the log file. -- @param fileName the fileName to use for the logfile. It is templated.
|h is replaced by the profile homedir.
|y by 4 digit year.
|m by 2 digit month
|d by 2 digit day
|n by the name constraint
|e by the file extension (html for h logType, log for others) function LoggingConsole:setFileName(fileName) self.fileName = fileName end --- Returns the pull path and filename for the logfile for this console function LoggingConsole:getFullFilename() local path = self:getPath() local fileName = self:getFileName() local fullPath = path .. fileName fullPath = fullPath:gsub("|", "_") return fullPath end --- Turns logging for this console on function LoggingConsole:enableLogging() self.log = true end --- Turns logging for this console off function LoggingConsole:disableLogging() self.log = false end --- Creates the path for the logfile for this console if necessary ---@local function LoggingConsole:createPathIfNotExists() local path = self:transformTemplate(self.path) if not path:ends("/") then path = path .. "/" end if not exists(path) then local ok, err = dt.mkdir_p(path) if not ok then assert(false, "Could not create directory for log files:" .. path .. "\n Reason was: " .. err) end end return true end --- Handles actually writing to the log file ---@local function LoggingConsole:writeToLog(str) local fileName = self:getFullFilename() self:createPathIfNotExists() if self:getExtension() == "html" then if not io.exists(fileName) then str = htmlHeader .. str end str = str end local file, err = io.open(fileName, "a") if not file then echo(err .. "\n") return end file:write(str) file:close() end local parent = Geyser.MiniConsole --- Handler function which does the lifting for c/d/h/echo and appendBuffer to provide the logfile writing functionality ---@param str the string to echo. Use "" for appends ---@param etype the type of echo. Valid are "c", "d", "h", "e", and "a" ---@param log Allows you to override the default behaviour defined by the .log property. Pass true to definitely log, false to skip logging. ---@local function LoggingConsole:xEcho(str, etype, log) if log == nil then log = self.log end local logStr local logType = self.logFormat if logType:find("h") then logType = "h" elseif logType ~= "t" then logType = "l" end if etype == "d" then -- decho if logType == "h" then logStr = dt.decho2html(str) elseif logType == "t" then logStr = dt.decho2string(str) else logStr = dt.decho2ansi(str) end parent.decho(self, str) elseif etype == "c" then -- cecho if logType == "h" then logStr = dt.cecho2html(str) elseif logType == "t" then logStr = dt.cecho2string(str) else logStr = dt.cecho2ansi(str) end parent.cecho(self, str) elseif etype == "h" then -- hecho if logType == "h" then logStr = dt.hecho2html(str) elseif logType == "t" then logStr = dt.hecho2string(str) else logStr = dt.hecho2ansi(str) end parent.hecho(self, str) elseif etype == "a" then -- append str = dt.append2decho() str = str .. "\n" if logType == "h" then logStr = dt.decho2html(str) elseif logType == "t" then logStr = dt.decho2string(str) else logStr = dt.decho2ansi(str) end parent.appendBuffer(self) elseif etype == "e" then -- echo if logType == "h" then logStr = dt.decho2html(str) else logStr = str end parent.echo(self, str) end if log then self:writeToLog(logStr) end end --- Does the actual lifting of echoing links/popups -- @local function LoggingConsole:xEchoLink(text, lType, command, hint, useFormat, log) if log == nil then log = self.log end local logStr = "" if lType:starts("c") then if self.logFormat == "h" then logStr = dt.cecho2html(text) elseif self.logFormat == "l" then logStr = dt.cecho2ansi(text) elseif self.logFormat == "t" then logStr = dt.cecho2string(text) end if lType:ends("p") then parent.cechoPopup(self, text, command, hint, useFormat) else parent.cechoLink(self, text, command, hint, useFormat) end elseif lType:starts("d") then if self.logFormat == "h" then logStr = dt.decho2html(text) elseif self.logFormat == "l" then logStr = dt.decho2ansi(text) elseif self.logFormat == "t" then logStr = dt.decho2string(text) end if lType:ends("p") then parent.dechoPopup(self, text, command, hint, useFormat) else parent.dechoLink(self, text, command, hint, useFormat) end elseif lType:starts("h") then if self.logFormat == "h" then logStr = dt.hecho2html(text) elseif self.logFormat == "l" then logStr = dt.hecho2ansi(text) elseif self.logFormat == "t" then logStr = dt.hecho2string(text) end if lType:ends("p") then parent.hechoPopup(self, text, command, hint, useFormat) else parent.hechoLink(self, text, command, hint, useFormat) end elseif lType:starts("e") then logStr = text if lType:ends("p") then parent.echoPopup(self, text, command, hint, useFormat) else parent.echoLink(self, text, command, hint, useFormat) end end if log then self:writeToLog(logStr) end end --- cechoLink for LoggingConsole -- @param text the text to use for the link -- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]] -- @param hint A tooltip which is displayed when the mouse is over the link -- @param log Should we log this line? Defaults to self.log if not passed. function LoggingConsole:cechoLink(text, command, hint, log) self:xEchoLink(text, "c", command, hint, true, log) end --- dechoLink for LoggingConsole -- @param text the text to use for the link -- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]] -- @param hint A tooltip which is displayed when the mouse is over the link -- @param log Should we log this line? Defaults to self.log if not passed. function LoggingConsole:dechoLink(text, command, hint, log) self:xEchoLink(text, "d", command, hint, true, log) end --- hechoLink for LoggingConsole -- @param text the text to use for the link -- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]] -- @param hint A tooltip which is displayed when the mouse is over the link -- @param log Should we log this line? Defaults to self.log if not passed. function LoggingConsole:hechoLink(text, command, hint, log) self:xEchoLink(text, "h", command, hint, true, log) end --- echoLink for LoggingConsole -- @param text the text to use for the link -- @param command the command to send when the link is clicked, as text. IE [[send("sleep")]] -- @param hint A tooltip which is displayed when the mouse is over the link -- @param useCurrentFormat If set to true, will look like the text around it. If false it will be blue and underline. -- @param log Should we log this line? Defaults to self.log if not passed. If you want to pass this you must pass in useCurrentFormat -- @usage myLoggingConsole:echoLink("This is a link!", [[send("sleep")]], "sleep") -- text "This is a link" will send("sleep") when clicked and be blue w/ underline. Defaut log behaviour (self.log) -- @usage myLoggingConsole:echoLink("This is a link!", [[send("sleep")]], "sleep", false, false) -- same as above, but forces it not to log regardless of self.log setting -- @usage myLoggingConsole:echoLink("This is a link!", [[send("sleep")]], "sleep", true, true) -- same as above, but forces it to log regardless of self.log setting and the text will look like anything else echoed to the console. function LoggingConsole:echoLink(text, command, hint, useCurrentFormat, log) self:xEchoLink(text, "e", command, hint, useCurrentFormat, log) end --- cechoPopup for LoggingConsole -- @param text the text to use for the link -- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]} -- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}} -- @param log Should we log this line? Defaults to self.log if not passed. function LoggingConsole:cechoPopup(text, commands, hints, log) self:xEchoLink(text, "cp", commands, hints, true, log) end --- dechoPopup for LoggingConsole -- @param text the text to use for the link -- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]} -- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}} -- @param log Should we log this line? Defaults to self.log if not passed. function LoggingConsole:dechoPopup(text, commands, hints, log) self:xEchoLink(text, "dp", commands, hints, true, log) end --- hechoPopup for LoggingConsole -- @param text the text to use for the link -- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]} -- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}} -- @param log Should we log this line? Defaults to self.log if not passed. function LoggingConsole:hechoPopup(text, commands, hints, log) self:xEchoLink(text, "hp", commands, hints, true, log) end --- echoPopup for LoggingConsole -- @param text the text to use for the link -- @param commands the commands to send when the popup is activated, as table. IE {[[send("sleep")]], [[send("stand")]]} -- @param hints A tooltip which is displayed when the mouse is over the link. IE {{"sleep", "stand"}} -- @param useCurrentFormat If set to true, will look like the text around it. If false it will be blue and underline. -- @param log Should we log this line? Defaults to self.log if not passed. If you want to pass this you must pass in useCurrentFormat -- @usage myLoggingConsole:echoPopup("This is a link!", {[[send("sleep")]], [[send("stand")]], {"sleep", "stand"}) -- text "This is a link" will send("sleep") when clicked and be blue w/ underline. Defaut log behaviour (self.log) -- @usage myLoggingConsole:echoPopup("This is a link!", {[[send("sleep")]], [[send("stand")]], {"sleep", "stand"}, false, false) -- same as above, but forces it not to log regardless of self.log setting -- @usage myLoggingConsole:echoPopup("This is a link!", {[[send("sleep")]], [[send("stand")]], {"sleep", "stand"}, true, true) -- same as above, but forces it to log regardless of self.log setting and the text will look like anything else echoed to the console. function LoggingConsole:echoPopup(text, commands, hints, useCurrentFormat, log) self:xEchoLink(text, "ep", commands, hints, useCurrentFormat, log) end --- Append copy()ed text to the console -- @param log should we log this? function LoggingConsole:appendBuffer(log) self:xEcho("", "a", log) end --- Append copy()ed text to the console -- @param log should we log this? function LoggingConsole:append(log) self:xEcho("", "a", log) end --- echo's a string to the console. -- @param str the string to echo -- @param log should this be logged? Used to override the .log constraint function LoggingConsole:echo(str, log) self:xEcho(str, "e", log) end --- hecho's a string to the console. -- @param str the string to hecho -- @param log should this be logged? Used to override the .log constraint function LoggingConsole:hecho(str, log) self:xEcho(str, "h", log) end --- decho's a string to the console. -- @param str the string to decho -- @param log should this be logged? Used to override the .log constraint function LoggingConsole:decho(str, log) self:xEcho(str, "d", log) end --- cecho's a string to the console. -- @param str the string to cecho -- @param log should this be logged? Used to override the .log constraint function LoggingConsole:cecho(str, log) self:xEcho(str, "c", log) end --- Replays the last X lines from the console's log file, if it exists -- @param numberOfLines The number of lines to replay from the end of the file function LoggingConsole:replay(numberOfLines) local fileName = self:getFullFilename() if not exists(fileName) then return end local file = io.open(fileName, "r") local lines = file:read("*a") if self:getExtension() == "html" then for _, line in ipairs(htmlHeaderPattern:split("\n")) do if line ~= "" then lines = lines:gsub(line .. "\n", "") end end lines = dt.html2decho(lines) else lines = ansi2decho(lines) end local linesTbl = lines:split("\n") local result if #linesTbl <= numberOfLines then result = lines else result = "" local start = #linesTbl - numberOfLines for index, str in ipairs(linesTbl) do if index >= start then result = string.format("%s\n%s", result, str) end end end self:decho(result, false) end setmetatable(LoggingConsole, parent) return LoggingConsole