--[[ ccConfig v3.0.2 [Updated: 27 March 2014] Copyright © 2014 Joshua Asbury a.k.a. theoriginalbit [theoriginalbit@gmail.com] Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, and/or sublicense copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software; - Visible credit is given to the original author; - The software is distributed in a non-profit way; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. --]] local function startsWith(str, prefix) return str:sub( 1, #tostring(prefix)) == tostring(prefix) end local function contains(str, seq) return (str:find(seq, 1)) ~= nil end local function split(str, patt) local t = {} for s in str:gmatch("[^"..patt.."]+") do t[#t+1] = s end return t end local function trim(str) return (str:gsub("^%s*(.-)%s*$", "%1")) end local function assert(cdn, msg, lvl) if not cdn then error(msg or "assertion failed!", (lvl == 0 and 0 or lvl and (lvl + 1) or 2)) end return cdn end local Configuration = {} Configuration.__index = Configuration local function validateParam(param, value) return type(param) == value end local function validateBoolean(b) return (b == "true" or b == "false" or b == "1" or b == "0" or b == 1 or b == 0 or b == true or b == false or b == "yes" or b == "no") end local function validateColor(source) --# parse number inputs if tonumber(source) then local shifted = math.floor(math.log(tonumber(source))/math.log(2)) if shifted >= 0 and shifted <= 15 then return col end return false, "colour out of range" --# parse string inputs elseif type(source) == "string" then source = source:lower() :gsub("%s*", "") :gsub("colou?rs?%.?", "") :gsub("lightb(.+)", "lightB%1") :gsub("lightg(.+)", "lightG%1") source = colors[source] or colours[source] return source, (not source and "invalid string input") end --# reject other inputs return false, "expected number/string, got "..type(source) end local function get(self, key, default) if type(self.properties[key]) == "table" then self.properties[key].default = default return self.properties[key].value end --# the key doesn't exist in the table, create it now self.properties[key] = {} self.properties[key].value = default self.properties[key].default = default return default end local function set(self, key, value) assert(self.properties[key], "Invalid parameter #1: The key '"..tostring(key).."' does not exist in the configuration file.", 3) self.properties[key].value = value end function Configuration:getBoolean(key, default) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) assert(validateBoolean(default), "Invalid parameter #2: Expected boolean, got "..tostring(default).." of type "..type(default), 2) --# get the value from the config local value = get(self, key, default) --# validate the config boolean assert(validateBoolean(value), "Configuration error: No boolean value found under the key \""..key.."\", found "..tostring(value).." of type "..type(value), 2) --# return a boolean return v == "true" or v == true or v == "1" or v == 1 or v == "yes" end function Configuration:setBoolean(key, value) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) assert(validateBoolean(value), "Invalid parameter #2: Expected boolean, got "..tostring(default).." of type "..type(value), 2) --# set the value set(self, key, value) end function Configuration:getNumber(key, default) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) assert(validateParam(default, "number"), "Invalid parameter #2: Expected number, got "..tostring(default).." of type "..type(default), 2) --# get the number from the config local value = tonumber(get(self, key, default)) --# validate the number assert(validateParam(value, "number"), "Configuration error: No number found under the key \""..key.."\", found "..tostring(value).." of type "..type(value), 2) --# return the number return value end function Configuration:setNumber(key, value) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) assert(validateParam(value, "number"), "Invalid parameter #2: Expected number, got "..tostring(default).." of type "..type(value), 2) --# set the value set(self, key, value) end function Configuration:getString(key, default) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) assert(validateParam(default, "string"), "Invalid parameter #2: Expected string, got "..tostring(default).." of type "..type(default), 2) --# get the string from the config local value = get(self, key, default) --# return the string return value end function Configuration:setString(key, value) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) assert(validateParam(value, "string"), "Invalid parameter #2: Expected string, got "..tostring(default).." of type "..type(value), 2) --# set the value set(self, key, value) end function Configuration:getColor(key, default) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) local ok, value = validateColor(default) assert(ok, "Invalid parameter #2: Expected number/color, got "..tostring(default).." of type "..type(default), 2) --# get the colour from the config ok, value = validateColor(get(self, key, default)) --# validate the colour assert(ok, "Configuration error: No number/color found under the key\""..key.."\", found "..tostring(value).." of type "..type(value), 2) --# add the restriction comment self.properties[key].restrict = "A number between 1 and 32768 or the string colors." return value end function Configuration:setColor(key, value) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) local ok, value = validateColor(value) assert(ok, "Invalid parameter #2: Expected number/color, got "..tostring(default).." of type "..type(default), 2) --# set the value set(self, key, value) end --# add the non-English (US) function Configuration.getColour = Configuration.getColor function Configuration:containsKey(key) return (self.properties[key] ~= nil) end function Configuration:load() --# make sure the file exists if not fs.exists(self.path) then return false end --# read the file local h = fs.open(self.path, 'r') assert(h, "Cannot open configuration file \'"..self.path.."\' for read") local contents = h.readAll() h.close() --# make sure there are contents of the file if not contents then return end local count = 1 contents = split(contents, '\n') for i = 1, #contents do local v = trim(contents[i]) if v and v ~= "" and not startsWith(v, "+--") and contains(v, '=') then local prop = split(v, '=') local key = trim(prop[1]) local value = trim(prop[2]) self.properties[key] = {} self.properties[key]["value"] = value self.properties[key]["default"] = value elseif v and v ~= "" and not startsWith(v,"+--") and not contains(v, '=') then error("[\""..self.path.."\"] Cannot parse line #"..i.." in configuration file", 0) end end return true end function Configuration:reset(key) assert(type(self) == "table" and type(self.properties) == "table", "Cannot resetting config, have you forgotten to load the config?", 2) assert((key and self.properties[key]) or not key, "No property for key "..tostring(key)..", have you forgotten to load the config?", 2) if key then self.properties[key].value = self.properties[key].default else for k,_ in pairs(self.properties) do self.properties[k].value = self.properties[k].default end end end function Configuration:save() --# open the config file local h = fs.open(self.path, 'w') --# write the header h.write(string.format("\n+-- %s CONFIGURATION FILE\n+-- Generated by theoriginalbit's ccConfig\n\n\n", self.name)) --# loop through the properties for k,v in pairs(self.properties) do --# write the properties info to file h.write(string.format("+-- %s restrictions: %s; default: %s;\n%s=%s\n\n\n", v.comment or "", v.restrict or "none", tostring(v.default), tostring(k), tostring(v.value))) --# flush the file h.flush() end --# close the file h.close() end function Configuration:addCommentForKey(key, value) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) assert(self.properties[key], "Invalid parameter #1: No property with key \""..tostring(key).."\" to add comment", 2) --# add the comment self.properties[key].comment = value end function Configuration:addRestrictionForKey(key, value) --# validate parameters assert(validateParam(key, "string"), "Invalid parameter #1: Expected string, got "..type(key), 2) assert(self.properties[key], "No property with key \""..tostring(key).."\" to add comment", 2) --# add the restriction comment self.properties[key].restrict = value end function Configuration:debug(toFile) toFile = (toFile == true) local h = toFile and fs.open("debug", 'w') or nil for k,v in pairs(self.properties) do if toFile then h.write(k..":"..v.value.."\n") else print(k..":"..v.value) end end if toFile then h.close() end end local function instantiate(path, name) assert(validateParam(path, "string"), "Invalid parameter #1: Expected string, got "..type(path), 3) assert(validateParam(name, "string"), "Invalid parameter #2: Expected string, got "..type(name), 3) local obj = setmetatable({}, { __newindex = function(self, key, value) if type(value) == "function" then local func = setmetatable({}, { __call = function(_, ...) if rawequal(self, ({...})[1]) then return value(...) end return value(self, ...) end; }) rawset(self, key, func) else rawset(self, key, value) end end; }) obj.name = name:upper() obj.path = path obj.properties = {} for k,v in pairs(Configuration) do obj[k] = v end return obj end function new(path, name) return instantiate(path, name) end function init(path, name) return instantiate(path, name) end