Skip to content

Commit d320da7

Browse files
committed
added settings and textutils
1 parent fd09886 commit d320da7

File tree

6 files changed

+373
-2
lines changed

6 files changed

+373
-2
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,4 @@ jobs:
3333
3434
- name: test
3535
run: |
36-
busted vector
36+
busted tests/

files.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ vector.lua
22
http.lua
33
fs.lua
44
json.lua
5-
scm.lua
5+
scm.lua
6+
textutils.lua
7+
settings.lua

settings.lua

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
---@class SettingsManager
2+
SettingsManager = {}
3+
4+
-- TODO:
5+
-- Check how / if defines (Details) are saved
6+
7+
local textutils = require("textutils")
8+
9+
---@alias settingType
10+
---| "string"
11+
---| "number"
12+
---| "boolean"
13+
---| "table"
14+
15+
16+
---@class SettingDetail
17+
---@field default any
18+
---@field description string
19+
---@field type settingType
20+
21+
---@type {[string]: SettingDetail}
22+
local details = {}
23+
24+
---@type {[string]: any}
25+
local settings = {}
26+
27+
local defaultPath = "tmp/.settings"
28+
29+
30+
local function reserialize(value)
31+
if type(value) ~= "table" then return value end
32+
return textutils.unserialize(textutils.serialize(value))
33+
end
34+
35+
36+
---@param name string
37+
---@param options SettingDetail
38+
function SettingsManager.define(name, options)
39+
details[name] = {}
40+
if options ~= nil then
41+
details[name] = options
42+
end
43+
end
44+
45+
---@param name string
46+
function SettingsManager.undefine(name)
47+
details[name] = nil
48+
end
49+
50+
---@param name string
51+
---@param value any
52+
function SettingsManager.set(name, value)
53+
settings[name] = value
54+
end
55+
56+
---@param name string
57+
---@param default any
58+
---@return any
59+
function SettingsManager.get(name, default)
60+
if settings[name] ~= nil then
61+
return settings[name]
62+
end
63+
if default ~= nil then
64+
return default
65+
end
66+
return (details[name] and details[name]["default"]) or nil
67+
end
68+
69+
---@param name string
70+
---@return any
71+
function SettingsManager.getDetails(name)
72+
local tmp = details[name]
73+
if settings[name] ~= nil then
74+
if tmp == nil then
75+
tmp = {}
76+
end
77+
tmp["value"] = settings[name]
78+
end
79+
return tmp
80+
end
81+
82+
---@param name string
83+
function SettingsManager.unset(name)
84+
settings[name] = nil
85+
end
86+
87+
--- clears Settings (values only)
88+
function SettingsManager.clear()
89+
settings = {}
90+
end
91+
92+
--- clears Details (Defines only)
93+
--- does not exist in cc, but might be usefull for testing
94+
function SettingsManager.clearDetails()
95+
details = {}
96+
end
97+
98+
---@return table<string>
99+
function SettingsManager.getNames()
100+
---@source https://github.com/cc-tweaked/CC-Tweaked/blob/876fd8ddb805365c33942afb81d6da7cbd0cbca5/projects/core/src/main/resources/data/computercraft/lua/rom/apis/settings.lua#L199
101+
local result, n = {}, 1
102+
for k in pairs(details) do
103+
result[n], n = k, n + 1
104+
end
105+
for k in pairs(settings) do
106+
if not details[k] then result[n], n = k, n + 1 end
107+
end
108+
table.sort(result)
109+
return result
110+
end
111+
112+
---@param path string | nil
113+
function SettingsManager.load(path)
114+
path = path or defaultPath
115+
local file = io.open(path, "r")
116+
if file == nil then
117+
return false
118+
end
119+
local content = file:read("*all")
120+
file:close()
121+
local result = textutils.unserialize(content)
122+
if not result or (type(result) ~= "table") then
123+
return false
124+
end
125+
settings = result
126+
return true
127+
end
128+
129+
---@param path string | nil
130+
function SettingsManager.save(path)
131+
if path == nil then
132+
path = defaultPath
133+
end
134+
if not os.execute("mkdir -p $(dirname ".. path .. ")") then
135+
error("Could not create the file")
136+
end
137+
local result = textutils.serialize(settings)
138+
local file = io.open(path, "w")
139+
if file == nil then
140+
error("Could not save to File: "..path)
141+
end
142+
file:write(result)
143+
file:close()
144+
return true
145+
146+
end
147+
148+
return SettingsManager

tests/settings_spec.lua

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
---@class are
2+
---@field same function
3+
---@field equal function
4+
---@field equals function
5+
6+
---@class is
7+
---@field truthy function
8+
---@field falsy function
9+
---@field not_true function
10+
---@field not_false function
11+
12+
---@class has
13+
---@field error function
14+
---@field errors function
15+
16+
---@class assert
17+
---@field are are
18+
---@field is is
19+
---@field are_not are
20+
---@field is_not is
21+
---@field has has
22+
---@field has_no has
23+
---@field True function
24+
---@field False function
25+
---@field has_error function
26+
---@field is_false function
27+
---@field is_true function
28+
---@field equal function
29+
assert = assert
30+
31+
32+
---@class SettingsManager
33+
local settings = require("settings")
34+
35+
local settingsFile = "tmp/.settings"
36+
37+
describe('Settings', function()
38+
---@class SettingDetail
39+
local detail
40+
---@type any
41+
local setting
42+
43+
before_each(function ()
44+
settings.clear()
45+
settings.clearDetails()
46+
detail = {
47+
description = "This is a test Setting",
48+
default = {["value"] = 14, ["value2"] = "fourteen"},
49+
type = "table"
50+
}
51+
setting = {["value"] = 15, ["value2"] = "fifteen"}
52+
end)
53+
after_each(function ()
54+
os.execute("rm -f "..settingsFile)
55+
end)
56+
it('define', function()
57+
settings.define("Test01", detail)
58+
local result = settings.getDetails("Test01")
59+
assert.are.equal(detail, result)
60+
end)
61+
it('getNames', function()
62+
settings.define("Test01", detail)
63+
local result = settings.getNames()
64+
assert.are.same({"Test01"}, result)
65+
end)
66+
it('get & getDetails', function()
67+
settings.define("Test01", detail)
68+
local result = settings.get("Test01")
69+
assert.are.same(detail.default, result)
70+
result = settings.getDetails("Test01")
71+
end)
72+
it('save and load', function()
73+
settings.define("Test01", detail)
74+
local result = settings.load(settingsFile)
75+
assert.are.same(false, result)
76+
settings.set("Test01", setting)
77+
settings.save(settingsFile)
78+
settings.clear()
79+
settings.clearDetails()
80+
result = settings.load(settingsFile)
81+
assert.is_true(result)
82+
local get_result = settings.get("Test01")
83+
assert.are.same(setting, get_result)
84+
end)
85+
86+
end)
File renamed without changes.

textutils.lua

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
---@source https://raw.githubusercontent.com/cc-tweaked/CC-Tweaked/876fd8ddb805365c33942afb81d6da7cbd0cbca5/projects/core/src/main/resources/data/computercraft/lua/rom/apis/textutils.lua
2+
3+
local TextUtils = {}
4+
5+
--#region local functions
6+
7+
local g_tLuaKeywords = {
8+
["and"] = true,
9+
["break"] = true,
10+
["do"] = true,
11+
["else"] = true,
12+
["elseif"] = true,
13+
["end"] = true,
14+
["false"] = true,
15+
["for"] = true,
16+
["function"] = true,
17+
["if"] = true,
18+
["in"] = true,
19+
["local"] = true,
20+
["nil"] = true,
21+
["not"] = true,
22+
["or"] = true,
23+
["repeat"] = true,
24+
["return"] = true,
25+
["then"] = true,
26+
["true"] = true,
27+
["until"] = true,
28+
["while"] = true,
29+
}
30+
31+
--- A version of the ipairs iterator which ignores metamethods
32+
local function inext(tbl, i)
33+
i = (i or 0) + 1
34+
local v = rawget(tbl, i)
35+
if v == nil then return nil else return i, v end
36+
end
37+
38+
local serialize_infinity = math.huge
39+
local function serialize_impl(t, tracking, indent, opts)
40+
local sType = type(t)
41+
if sType == "table" then
42+
if tracking[t] ~= nil then
43+
if tracking[t] == false then
44+
error("Cannot serialize table with repeated entries", 0)
45+
else
46+
error("Cannot serialize table with recursive entries", 0)
47+
end
48+
end
49+
tracking[t] = true
50+
51+
local result
52+
if next(t) == nil then
53+
-- Empty tables are simple
54+
result = "{}"
55+
else
56+
-- Other tables take more work
57+
local open, sub_indent, open_key, close_key, equal, comma = "{\n", indent .. " ", "[ ", " ] = ", " = ", ",\n"
58+
if opts.compact then
59+
open, sub_indent, open_key, close_key, equal, comma = "{", "", "[", "]=", "=", ","
60+
end
61+
62+
result = open
63+
local seen_keys = {}
64+
for k, v in inext, t do
65+
seen_keys[k] = true
66+
result = result .. sub_indent .. serialize_impl(v, tracking, sub_indent, opts) .. comma
67+
end
68+
for k, v in next, t do
69+
if not seen_keys[k] then
70+
local sEntry
71+
if type(k) == "string" and not g_tLuaKeywords[k] and string.match(k, "^[%a_][%a%d_]*$") then
72+
sEntry = k .. equal .. serialize_impl(v, tracking, sub_indent, opts) .. comma
73+
else
74+
sEntry = open_key .. serialize_impl(k, tracking, sub_indent, opts) .. close_key .. serialize_impl(v, tracking, sub_indent, opts) .. comma
75+
end
76+
result = result .. sub_indent .. sEntry
77+
end
78+
end
79+
result = result .. indent .. "}"
80+
end
81+
82+
if opts.allow_repetitions then
83+
tracking[t] = nil
84+
else
85+
tracking[t] = false
86+
end
87+
return result
88+
89+
elseif sType == "string" then
90+
return string.format("%q", t)
91+
92+
elseif sType == "number" then
93+
if t ~= t then --nan
94+
return "0/0"
95+
elseif t == serialize_infinity then
96+
return "1/0"
97+
elseif t == -serialize_infinity then
98+
return "-1/0"
99+
else
100+
return tostring(t)
101+
end
102+
103+
elseif sType == "boolean" or sType == "nil" then
104+
return tostring(t)
105+
106+
else
107+
error("Cannot serialize type " .. sType, 0)
108+
109+
end
110+
end
111+
112+
--#endregion
113+
114+
function TextUtils.serialize(t, opts)
115+
local tTracking = {}
116+
return serialize_impl(t, tTracking, "", opts or {})
117+
end
118+
119+
---@param s string
120+
---@return table | nil
121+
function TextUtils.unserialize(s)
122+
if type(s) ~= "string" then
123+
return nil
124+
end
125+
local func = loadstring("return " .. s, "unserialize", "t", {})
126+
if func then
127+
local ok, result = pcall(func)
128+
if ok then
129+
return result
130+
end
131+
end
132+
return nil
133+
end
134+
135+
return TextUtils

0 commit comments

Comments
 (0)