Skip to content

Commit ebdbdb4

Browse files
authored
v1.6.0 release (#100)
* build(MDK): ⬆️ upgrade to 2.10.0 (#97) (#98) * build(MDK): ✨ add revisionator (#99) * feat(chat): 🔨 add Revisionator for patching EMCO * docs(emco): 💡 notate current patch level * docs(readme): 📝 update milestones and dependencies
1 parent bba84db commit ebdbdb4

File tree

3 files changed

+185
-6
lines changed

3 files changed

+185
-6
lines changed

MDK/revisionator.lua

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
--- The revisionator provides a standardized way of migrating configurations between revisions
2+
-- for instance, it will track what the currently applied revision number is, and when you tell
3+
-- tell it to migrate, it will apply every individual migration between the currently applied
4+
-- revision and the latest/current revision. This should allow for more seamlessly moving from
5+
-- an older version of a package to a new one.
6+
-- @classmod revisionator
7+
-- @author Damian Monogue <[email protected]>
8+
-- @copyright 2023
9+
-- @license MIT, see https://raw.githubusercontent.com/demonnic/MDK/main/src/scripts/LICENSE.lua
10+
local revisionator = {
11+
name = "Revisionator",
12+
patches = {},
13+
}
14+
revisionator.__index = revisionator
15+
local dataDir = getMudletHomeDir() .. "/revisionator"
16+
revisionator.dataDir = dataDir
17+
if not io.exists(dataDir) then
18+
local ok,err = lfs.mkdir(dataDir)
19+
if not ok then
20+
printDebug(f"Error creating the directory for storing applied revisions: {err}", true)
21+
end
22+
end
23+
24+
--- Creates a new revisionator
25+
-- @tparam table options the options to create the revisionator with.
26+
-- <table class="tg">
27+
-- <thead>
28+
-- <tr>
29+
-- <th>option name</th>
30+
-- <th>description</th>
31+
-- <th>default</th>
32+
-- </tr>
33+
-- </thead>
34+
-- <tbody>
35+
-- <tr>
36+
-- <td class="tg-1">name</td>
37+
-- <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>
38+
-- <td class="tg-1">raises an error if not provided</td>
39+
-- </tr>
40+
-- <tr>
41+
-- <td class="tg-2">patches</td>
42+
-- <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>
43+
-- <td class="tg-2">{}</td>
44+
-- </tr>
45+
--</tbody>
46+
--</table>
47+
function revisionator:new(options)
48+
options = options or {}
49+
local optionsType = type(options)
50+
if optionsType ~= "table" then
51+
printError(f"revisionator:new bad argument #1 type, options as table expected, got {optionsType}", true, true)
52+
end
53+
if not options.name then
54+
printError("revisionator:new(options) options must include a 'name' key as this is used as part of tracking the applied patch level.", true, true)
55+
end
56+
local me = table.deepcopy(options)
57+
setmetatable(me, self)
58+
return me
59+
end
60+
61+
--- Get the currently applied revision from file
62+
--- @treturn[1] number the revision number currently applied, or 0 if it can't read a current version
63+
--- @treturn[2] nil nil
64+
--- @treturn[2] string error message
65+
function revisionator:getAppliedPatch()
66+
local fileName = f"{self.dataDir}/{self.name}.txt"
67+
debugc(fileName)
68+
local revision = 0
69+
if io.exists(fileName) then
70+
local file = io.open(fileName, "r")
71+
local fileContents = file:read("*a")
72+
file:close()
73+
local revNumber = tonumber(fileContents)
74+
if revNumber then
75+
revision = revNumber
76+
else
77+
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"
78+
end
79+
end
80+
return revision
81+
end
82+
83+
--- go through all the patches in order and apply any which are still necessary
84+
--- @treturn boolean true if it successfully applied patches, false if it was already at the latest patch level
85+
--- @error error message
86+
function revisionator:migrate()
87+
local applied,err = self:getAppliedPatch()
88+
if not applied then
89+
printError(err, true, true)
90+
end
91+
local patches = self.patches
92+
if applied >= #patches then
93+
return false
94+
end
95+
for revision, patch in ipairs(patches) do
96+
if applied < revision then
97+
local ok, err = pcall(patch)
98+
if not ok then
99+
self:setAppliedPatch(revision - 1)
100+
return nil, f"Error while running patch #{revision}: {err}"
101+
end
102+
end
103+
end
104+
self:setAppliedPatch(#patches)
105+
return true
106+
end
107+
108+
--- add a patch to the table of patches
109+
--- @tparam function func the function to run as the patch
110+
--- @number[opt] position which patch to insert it as? If not supplied, inserts it as the last patch. Which is usually what you want.
111+
function revisionator:addPatch(func, position)
112+
if position then
113+
table.insert(self.patches, position, func)
114+
else
115+
table.insert(self.patches, func)
116+
end
117+
end
118+
119+
--- Remove a patch from the table of patches
120+
--- this is primarily used for testing
121+
--- @local
122+
--- @number[opt] patchNumber the patch number to remove. Will remove the last item if not provided.
123+
function revisionator:removePatch(patchNumber)
124+
table.remove(self.patches, patchNumber)
125+
end
126+
127+
--- set the currently applied patch number
128+
-- only directly called for testing
129+
--- @local
130+
--- @number patchNumber the patch number to set as the currently applied patch
131+
function revisionator:setAppliedPatch(patchNumber)
132+
local fileName = f"{self.dataDir}/{self.name}.txt"
133+
local revFile, err = io.open(fileName, "w+")
134+
if not revFile then
135+
printError(err, true, true)
136+
end
137+
revFile:write(patchNumber)
138+
revFile:close()
139+
end
140+
141+
return revisionator

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Mudlet Forum Topic: [https://forums.mudlet.org/viewtopic.php?f=6&t=23126]
5757

5858
#### Milestones
5959

60+
- v1.6.0 - Clickable URLs, EMCO Timestamps, and Revisionator Patching
6061
- v1.5.0 - Auto-Install, Auto-Update, and Discord Rich Presence Integration
6162
- v1.2.0 - EMCO Command Line Interface
6263
- v1.1.0 - Management of Instanced Areas
@@ -132,7 +133,7 @@ Upon initial install and/or update, you may need to close and re-open your Proce
132133
<a href="https://www.mudlet.org"><img src="https://www.mudlet.org/wp-content/uploads/2017/08/mudlet-wp-logo.png" alt="Mudlet" width="120" height="auto"></a>
133134

134135
- [Mudlet][mudlet-url] MUD Client application must obviously be installed on your operating system (Windows, MacOS, and Linux)
135-
- [MDK](https://github.com/demonnic/MDK) provides EMCO and SUG support. The required code is already included in the PRS release package, requiring no additional download/install by the user.
136+
- [MDK](https://github.com/demonnic/MDK) provides EMCO, Revisionator, and SUG support. The required code is already included in the PRS release package, requiring no additional download/install by the user.
136137

137138
### Getting the Source
138139

prs-chat.lua

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ PRSchat = PRSchat or {}
22
PRSchat.triggers = PRSchat.triggers or {}
33

44
local EMCO = require("PRS.emco")
5+
local rev = require("PRS.revisionator")
56

67
function PRSchat.tabs()
78
local title_text
@@ -22,10 +23,12 @@ function PRSchat.tabs()
2223
width = "25%",
2324
height = "75%"
2425
})
26+
2527
local stylesheet =
2628
[[background-color: rgb(80,80,80,255); border-width: 1px; border-style: solid; border-color: black; border-radius: 0px;]]
2729
local istylesheet =
2830
[[background-color: rgb(60,60,60,255); border-width: 1px; border-style: solid; border-color: black; border-radius: 0px;]]
31+
2932
PRSchat.EMCO = EMCO:new({
3033
name = "PRSchatTabs",
3134
x = "0",
@@ -51,7 +54,40 @@ function PRSchat.tabs()
5154
timestampFGColor = "dim_gray",
5255
timestampBGColor = "black"
5356
}, PRSchat.UW)
54-
PRSchat.EMCO:load()
57+
58+
local emcoRev = rev:new({
59+
name = "prsRevisionator"
60+
})
61+
62+
emcoRev:addPatch(function()
63+
--- patch for v1.6.0
64+
PRSchatTabs.leftMargin = 2
65+
PRSchatTabs.tabBold = true
66+
PRSchatTabs:setActiveTabFGColor("white")
67+
PRSchatTabs:setinactiveTabFGColor("gray")
68+
PRSchatTabs:enableTimeStamp()
69+
PRSchatTabs:enableCustomTimestampColor()
70+
PRSchatTabs:setTimestampFGColor("dim_gray")
71+
PRSchatTabs:setTimestampBGColor("black")
72+
end)
73+
74+
local function saver(eventName, packageName)
75+
if eventName == "sysExitEvent" or packageName == "PRS" then
76+
PRSchatTabs:save()
77+
end
78+
end
79+
80+
local function loader(eventName, packageName)
81+
if eventName == "sysLoadEvent" or packageName == "PRS" then
82+
PRSchatTabs:load()
83+
-- new stuff below here
84+
local changed = emcoRev:migrate()
85+
if changed then -- save the emco changes back to its own save file
86+
PRSchatTabs:save()
87+
end
88+
end
89+
end
90+
5591
PRSchat.EMCO:setCmdAction("Chat", function(str)
5692
send("chat " .. str)
5793
end)
@@ -78,9 +114,11 @@ function PRSchat.tabs()
78114
end
79115
end)
80116
PRSchat.EMCO.mc["Tell"]:enableCommandLine()
81-
registerAnonymousEventHandler("sysExitEvent", function()
82-
PRSchat.EMCO:save()
83-
end)
117+
118+
registerNamedEventHandler("PRS", "load", "sysLoadEvent", loader)
119+
registerNamedEventHandler("PRS", "install", "sysInstall", loader)
120+
registerNamedEventHandler("PRS", "exit", "sysExitEvent", saver)
121+
registerNamedEventHandler("PRS", "uninstall", "sysUninstall", saver)
84122
end
85123

86124
function PRSchat.stop()
@@ -198,4 +236,3 @@ function PRSchat.initialize()
198236
end
199237
end
200238
PRSchat.initialize()
201-

0 commit comments

Comments
 (0)