Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spinbox callbacks and the revisionator #63

Merged
merged 8 commits into from
May 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ You should maybe also include demontools.lua, as it notes below several other of
* mastermindsolver.lua
* A class which will help you solve Master Mind puzzles. <https://github.com/demonnic/MDK/wiki/MasterMindSolver>

* revisionator.lua
* A class which aims to make upgrading between package versions easier by storing and running patch functions. <https://github.com/demonnic/MDK/wiki/Revisionator>

* sortbox.lua
* SortBox, an alternative to H/VBox which can be either, and also provides options for sorting its contents. Overview at <https://github.com/demonnic/MDK/wiki/SortBox>

Expand Down
1 change: 1 addition & 0 deletions config.ld
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ not_luadoc = true
no_space_before_args = true
style = true
file = "src/resources"
prettify_files = true
2 changes: 1 addition & 1 deletion mfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"package": "MDK",
"version": "2.9.0",
"version": "2.10.0",
"author": "Demonnic",
"title": "Collection of useful objects/classes",
"icon": "computer.png",
Expand Down
199 changes: 199 additions & 0 deletions src/resources/demontools.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1284,4 +1284,203 @@ end
DemonTools.htmlHeader = htmlHeader
DemonTools.htmlHeaderPattern = htmlHeaderPattern

local echoOutputs = {
Color = {
["\27reset"] = "<reset>",
["\27bold"] = "<b>",
["\27boldoff"] = "</b>",
["\27italics"] = "<i>",
["\27italicsoff"] = "</i>",
["\27underline"] = "<u>",
["\27underlineoff"] = "</u>",
["\27strikethrough"] = "<s>",
["\27strikethroughoff"] = "</s>",
["\27overline"] = "<o>",
["\27overlineoff"] = "</o>",
},
Decimal = {
["\27reset"] = "<r>",
["\27bold"] = "<b>",
["\27boldoff"] = "</b>",
["\27italics"] = "<i>",
["\27italicsoff"] = "</i>",
["\27underline"] = "<u>",
["\27underlineoff"] = "</u>",
["\27strikethrough"] = "<s>",
["\27strikethroughoff"] = "</s>",
["\27overline"] = "<o>",
["\27overlineoff"] = "</o>",
},
Hex = {
["\27reset"] = "#r",
["\27bold"] = "#b",
["\27boldoff"] = "#/b",
["\27italics"] = "#i",
["\27italicsoff"] = "#/i",
["\27underline"] = "#u",
["\27underlineoff"] = "#/u",
["\27strikethrough"] = "#s",
["\27strikethroughoff"] = "#/s",
["\27overline"] = "#o",
["\27overlineoff"] = "#/o",
}
}

local echoPatterns = _Echos.Patterns
local echoProcess = _Echos.Process

function DemonTools.toHTML(t, reset)
reset = reset or {
background = { 0, 0, 0 },
bold = false,
foreground = { 255, 255, 255 },
italic = false,
overline = false,
reverse = false,
strikeout = false,
underline = false
}
local format = table.deepcopy(reset)
local result = getHTMLformat(format)
for _,v in ipairs(t) do
local formatChanged = false
if type(v) == "table" then
if v.fg then
format.foreground = {v.fg[1], v.fg[2], v.fg[3]}
formatChanged = true
end
if v.bg then
format.background = {v.bg[1], v.bg[2], v.bg[3]}
formatChanged = true
end
elseif v == "\27bold" then
format.bold = true
formatChanged = true
elseif v == "\27boldoff" then
format.bold = false
formatChanged = true
elseif v == "\27italics" then
format.italic = true
formatChanged = true
elseif v == "\27italicsoff" then
format.italic = false
formatChanged = true
elseif v == "\27underline" then
format.underline = true
formatChanged = true
elseif v == "\27underlineoff" then
format.underline = false
formatChanged = true
elseif v == "\27strikethrough" then
format.strikeout = true
formatChanged = true
elseif v == "\27strikethroughoff" then
format.strikeout = false
formatChanged = true
elseif v == "\27overline" then
format.overline = true
formatChanged = true
elseif v == "\27overlineoff" then
format.overline = false
formatChanged = true
elseif v == "\27reset" then
format = table.deepcopy(reset)
formatChanged = true
end
v = formatChanged and getHTMLformat(format) or v
result = result .. v
end
return result
end

local function toEcho(colorType, colors)
colorType = colorType:lower()
local result
if colorType == "hex" then
local fg,bg = "", ""
if colors.fg then
fg = string.format("%02x%02x%02x", unpack(colors.fg))
end
if colors.bg then
bg = string.format(",%02x%02x%02x", unpack(colors.bg))
end
result = string.format("#%s%s", fg, bg)
elseif colorType == "color" then
local fg,bg = "",""
if colors.fg then
fg = closestColor(colors.fg)
end
if colors.bg then
bg = ":" .. closestColor(colors.bg[1], colors.bg[2], colors.bg[3])
end
result = string.format("<%s%s>", fg, bg)
elseif colorType == "decimal" then
local fg,bg = "", ""
if colors.fg then
fg = string.format("%d,%d,%d", unpack(colors.fg))
end
if colors.bg then
bg = string.format(":%d,%d,%d", unpack(colors.bg))
end
result = string.format("<%s%s>", fg, bg)
end
return result
end

function DemonTools.echoConverter(str, from, to, resetFormat)
local strType, fromType, toType, resetType = type(str), type(from), type(to), type(resetFormat)
local errTemplate = "bad argument #{argNum} type ({argName} as string expected, got {argType})"
local argNum, argName, argType
local err = false
if strType ~= "string" then
argNum = 1
argName = "str"
argType = strType
err = true
elseif fromType ~= "string" then
argNum = 2
argName = "from"
argType = fromType
err = true
elseif toType ~= "string" then
argNum = 3
argName = "to"
argType = toType
err = true
elseif resetFormat and resetType ~= "table" then
argType = resetType
errTemplate = "bad argument #4 type (optional resetFormat as table of formatting options expected, got {argType})"
err = true
end
if err then
printError(f(errTemplate), true, true)
end
from = from:title()
local t = echoProcess(str, from)
if not echoPatterns[from] then
local msg = "argument #4 (from) must be a valid echo type. Valid types are: " .. table.concat(table.keys(echoPatterns), ",")
end
local processed = echoProcess(str, from)
if to:lower() == "html" then
return DemonTools.toHTML(processed, resetFormat)
end
local outputs = echoOutputs[to]
if not outputs then
local msg = "argument #3 (to) must be a valid echo type. Valid types are: " .. table.concat(table.keys(echoOutputs), ",")
printError(msg, true, true)
end
local result = ""
for _, token in ipairs(processed) do
local formatter = outputs[token]
if formatter and token:find("\27") then
result = result .. formatter
elseif type(token) == "table" then
result = result .. toEcho(to, token)
else
result = result .. token
end
end
return result
end

return DemonTools
2 changes: 1 addition & 1 deletion src/resources/mdkversion.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.9.0
2.10.0
141 changes: 141 additions & 0 deletions src/resources/revisionator.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
--- The revisionator provides a standardized way of migrating configurations between revisions
-- for instance, it will track what the currently applied revision number is, and when you tell
-- tell it to migrate, it will apply every individual migration between the currently applied
-- revision and the latest/current revision. This should allow for more seamlessly moving from
-- an older version of a package to a new one.
-- @classmod revisionator
-- @author Damian Monogue <demonnic@gmail.com>
-- @copyright 2023
-- @license MIT, see https://raw.githubusercontent.com/demonnic/MDK/main/src/scripts/LICENSE.lua
local revisionator = {
name = "Revisionator",
patches = {},
}
revisionator.__index = revisionator
local dataDir = getMudletHomeDir() .. "/revisionator"
revisionator.dataDir = dataDir
if not io.exists(dataDir) then
local ok,err = lfs.mkdir(dataDir)
if not ok then
printDebug(f"Error creating the directory for storing applied revisions: {err}", true)
end
end

--- Creates a new revisionator
-- @tparam table options the options to create the revisionator with.
-- <table class="tg">
-- <thead>
-- <tr>
-- <th>option name</th>
-- <th>description</th>
-- <th>default</th>
-- </tr>
-- </thead>
-- <tbody>
-- <tr>
-- <td class="tg-1">name</td>
-- <td class="tg-1">The name of the revisionator. This is absolutely required, as the name is used for tracking the currently applied patch level</td>
-- <td class="tg-1">raises an error if not provided</td>
-- </tr>
-- <tr>
-- <td class="tg-2">patches</td>
-- <td class="tg-2">A table of patch functions. It is traversed using ipairs, so must be in the form of {function1, function2, function3} etc. If you do not provide it, you can add the patches by calling :addPatch for each patch in order.</td>
-- <td class="tg-2">{}</td>
-- </tr>
--</tbody>
--</table>
function revisionator:new(options)
options = options or {}
local optionsType = type(options)
if optionsType ~= "table" then
printError(f"revisionator:new bad argument #1 type, options as table expected, got {optionsType}", true, true)
end
if not options.name then
printError("revisionator:new(options) options must include a 'name' key as this is used as part of tracking the applied patch level.", true, true)
end
local me = table.deepcopy(options)
setmetatable(me, self)
return me
end

--- Get the currently applied revision from file
--- @treturn[1] number the revision number currently applied, or 0 if it can't read a current version
--- @treturn[2] nil nil
--- @treturn[2] string error message
function revisionator:getAppliedPatch()
local fileName = f"{self.dataDir}/{self.name}.txt"
debugc(fileName)
local revision = 0
if io.exists(fileName) then
local file = io.open(fileName, "r")
local fileContents = file:read("*a")
file:close()
local revNumber = tonumber(fileContents)
if revNumber then
revision = revNumber
else
return nil, f"Error while attempting to read current patch version from file: {fileName}\nThe contents of the file are {fileContents} and it was unable to be converted to a revision number"
end
end
return revision
end

--- go through all the patches in order and apply any which are still necessary
--- @treturn boolean true if it successfully applied patches, false if it was already at the latest patch level
--- @error error message
function revisionator:migrate()
local applied,err = self:getAppliedPatch()
if not applied then
printError(err, true, true)
end
local patches = self.patches
if applied >= #patches then
return false
end
for revision, patch in ipairs(patches) do
if applied < revision then
local ok, err = pcall(patch)
if not ok then
self:setAppliedPatch(revision - 1)
return nil, f"Error while running patch #{revision}: {err}"
end
end
end
self:setAppliedPatch(#patches)
return true
end

--- add a patch to the table of patches
--- @tparam function func the function to run as the patch
--- @number[opt] position which patch to insert it as? If not supplied, inserts it as the last patch. Which is usually what you want.
function revisionator:addPatch(func, position)
if position then
table.insert(self.patches, position, func)
else
table.insert(self.patches, func)
end
end

--- Remove a patch from the table of patches
--- this is primarily used for testing
--- @local
--- @number[opt] patchNumber the patch number to remove. Will remove the last item if not provided.
function revisionator:removePatch(patchNumber)
table.remove(self.patches, patchNumber)
end

--- set the currently applied patch number
-- only directly called for testing
--- @local
--- @number patchNumber the patch number to set as the currently applied patch
function revisionator:setAppliedPatch(patchNumber)
local fileName = f"{self.dataDir}/{self.name}.txt"
local revFile, err = io.open(fileName, "w+")
if not revFile then
printError(err, true, true)
end
revFile:write(patchNumber)
revFile:close()
end

return revisionator
Loading
Loading