local Util = { } local fs = _G.fs local http = _G.http local os = _G.os local term = _G.term local textutils = _G.textutils local _sformat = string.format local _srep = string.rep local _ssub = string.sub local _unpack = table.unpack local _bor = bit32.bor local _bxor = bit32.bxor local byteArrayMT byteArrayMT = { __tostring = function(a) return string.char(_unpack(a)) end, __index = { toHex = function(self) return ("%02x"):rep(#self):format(_unpack(self)) end, isEqual = function(self, t) if type(t) ~= "table" then return false end if #self ~= #t then return false end local ret = 0 for i = 1, #self do ret = _bor(ret, _bxor(self[i], t[i])) end return ret == 0 end, sub = function(self, a, b) local len = #self+1 local start = a%len local stop = (b or len-1)%len local ret = {} local i = 1 for j = start, stop, start= timeout end function Util.tryTimes(attempts, f, ...) local result for _ = 1, attempts do result = { f(...) } if result[1] then return _unpack(result) end end return _unpack(result) end function Util.timer() local ct = os.clock() return function() return os.clock() - ct end end Util.Timer = Util.timer -- deprecate function Util.throttle(fn) local ts = os.clock() local timeout = .295 return function(...) local nts = os.clock() if nts > ts + timeout then os.sleep(0) ts = os.clock() if fn then fn(...) end end end end function Util.tostring(pattern, ...) local function serialize(tbl, width) local str = '{\n' for k, v in pairs(tbl) do local value if type(v) == 'table' then value = _sformat('table: %d', Util.size(v)) else value = tostring(v) end str = str .. _sformat(' %s: %s\n', k, value) end --if #str < width then --str = str:gsub('\n', '') .. ' }' --else str = str .. '}' --end return str end if type(pattern) == 'string' then if select('#', ...) == 0 then return pattern end return _sformat(pattern, ...) elseif type(pattern) == 'table' then return serialize(pattern, term.current().getSize()) end return tostring(pattern) end function Util.print(pattern, ...) print(Util.tostring(pattern, ...)) end function Util.getVersion() local version if _G._CC_VERSION then version = tonumber(_G._CC_VERSION:match('[%d]+%.?[%d][%d]')) end if not version and _G._HOST then version = tonumber(_G._HOST:match('[%d]+%.?[%d][%d]')) end return version or 1.7 end function Util.getMinecraftVersion() local mcVersion = _G._MC_VERSION or 'unknown' if _G._HOST then local version = _G._HOST:match('%S+ %S+ %((%S.+)%)') if version then mcVersion = version:match('Minecraft (%S+)') or version end end return mcVersion end function Util.checkMinecraftVersion(minVersion) local version = Util.getMinecraftVersion() local function convert(v) local m1, m2, m3 = v:match('(%d)%.(%d)%.?(%d?)') return tonumber(m1) * 10000 + tonumber(m2) * 100 + (tonumber(m3) or 0) end return convert(version) >= convert(tostring(minVersion)) end function Util.signum(num) if num > 0 then return 1 elseif num < 0 then return -1 else return 0 end end function Util.clamp(num, low, high) return num < low and low or num > high and high or num end -- http://lua-users.org/wiki/SimpleRound function Util.round(num, idp) local mult = 10^(idp or 0) return Util.signum(num) * math.floor(math.abs(num) * mult + 0.5) / mult end function Util.randomFloat(max, min) min = min or 0 max = max or 1 return (max-min) * math.random() + min end --[[ Table functions ]] -- function Util.clear(t) local keys = Util.keys(t) for _,k in pairs(keys) do t[k] = nil end end function Util.empty(t) return not next(t) end function Util.key(t, value) for k,v in pairs(t) do if v == value then return k end end end function Util.keys(t) local keys = { } for k in pairs(t) do keys[#keys+1] = k end return keys end function Util.merge(obj, args) if args then for k,v in pairs(args) do obj[k] = v end end return obj end function Util.deepMerge(obj, args) if args then for k,v in pairs(args) do if type(v) == 'table' then if not obj[k] then obj[k] = { } end Util.deepMerge(obj[k], v) else obj[k] = v end end end end function Util.transpose(t) local tt = { } for k,v in pairs(t) do tt[v] = k end return tt end function Util.contains(t, value) for k,v in pairs(t) do if v == value then return k end end end function Util.find(t, name, value) for k,v in pairs(t) do if v[name] == value then return v, k end end end function Util.findAll(t, name, value) local rt = { } for _,v in pairs(t) do if v[name] == value then table.insert(rt, v) end end return rt end function Util.shallowCopy(t) if not t then error('Util.shallowCopy: invalid table', 2) end local t2 = { } for k,v in pairs(t) do t2[k] = v end return t2 end function Util.deepCopy(t) if type(t) ~= 'table' then return t end --local mt = getmetatable(t) local res = {} for k,v in pairs(t) do if type(v) == 'table' then v = Util.deepCopy(v) end res[k] = v end --setmetatable(res,mt) return res end -- http://snippets.luacode.org/?p=snippets/Filter_a_table_in-place_119 function Util.filterInplace(t, predicate) local j = 1 for i = 1,#t do local v = t[i] if predicate(v) then t[j] = v j = j + 1 end end while t[j] ~= nil do t[j] = nil j = j + 1 end return t end function Util.filter(it, f) local ot = { } for k,v in pairs(it) do if f(v) then ot[k] = v end end return ot end function Util.reduce(t, fn, acc) acc = acc or 0 for _, v in pairs(t) do acc = fn(acc, v) end return acc end function Util.size(list) if type(list) == 'table' then local length = 0 for _ in pairs(list) do length = length + 1 end return length end return 0 end local function isArray(value) -- dubious return type(value) == "table" and (value[1] or next(value) == nil) end function Util.removeByValue(t, e) for k,v in pairs(t) do if v == e then if isArray(t) then table.remove(t, k) else t[k] = nil end break end end end function Util.any(t, fn) for _,v in pairs(t) do if fn(v) then return true end end end function Util.every(t, fn) for _,v in pairs(t) do if not fn(v) then return false end end return true end function Util.each(list, func) for index, value in pairs(list) do func(value, index, list) end end function Util.rpairs(t) local tkeys = Util.keys(t) local i = #tkeys return function() local key = tkeys[i] local k,v = key, t[key] i = i - 1 if v then return k, v end end end -- http://stackoverflow.com/questions/15706270/sort-a-table-in-lua function Util.spairs(t, order) local keys = Util.keys(t) -- if order function given, sort by it by passing the table and keys a, b, -- otherwise just sort the keys if order then table.sort(keys, function(a,b) return order(t[a], t[b]) end) else table.sort(keys) end -- return the iterator function local i = 0 return function() i = i + 1 if keys[i] then return keys[i], t[keys[i]] end end end function Util.first(t, order) local keys = Util.keys(t) if order then table.sort(keys, function(a,b) return order(t[a], t[b]) end) else table.sort(keys) end return keys[1], t[keys[1]] end --[[ File functions ]]-- function Util.readFile(fname, flags) local f = fs.open(fname, flags or "r") if f then local t = f.readAll() f.close() return t end end function Util.backup(fname) local backup = fname .. '.bak' if backup then fs.delete(backup) end fs.copy(fname, backup) end function Util.writeFile(fname, data) if not fname or not data then error('Util.writeFile: invalid parameters', 2) end if fs.exists(fname) then local diff = #data - fs.getSize(fname) if diff > 0 then if fs.getFreeSpace(fs.getDir(fname)) < diff then error('Insufficient disk space for ' .. fname) end end end local file = io.open(fname, "w") if not file then error('Unable to open ' .. fname, 2) end file:write(data) file:close() end function Util.readLines(fname) local file = fs.open(fname, "r") if file then local t = {} local line = file.readLine() while line do table.insert(t, line) line = file.readLine() end file.close() return t end end function Util.writeLines(fname, lines) local file = fs.open(fname, 'w') if file then for _,line in ipairs(lines) do file.writeLine(line) end file.close() return true end end function Util.readTable(fname) local t = Util.readFile(fname) if t then return textutils.unserialize(t) end end function Util.writeTable(fname, data) Util.writeFile(fname, textutils.serialize(data)) end function Util.loadTable(fname) local fc = Util.readFile(fname) if not fc then return false, 'Unable to read file' end local s, m = loadstring('return ' .. fc, fname) if s then s, m = pcall(s) if s then return m end end return s, m end --[[ loading and running functions ]] -- function Util.httpGet(url, headers, isBinary) local h, msg = http.get(url, headers, isBinary) if h then local contents = h.readAll() h.close() return contents end return h, msg end function Util.download(url, filename) local contents, msg = Util.httpGet(url) if not contents then error(_sformat('Failed to download %s\n%s', url, msg), 2) end if filename then Util.writeFile(filename, contents) end return contents end function Util.loadUrl(url, env) -- loadfile equivalent local c, msg = Util.httpGet(url) if not c then return c, msg end return load(c, url, nil, env) end function Util.runUrl(env, url, ...) -- os.run equivalent setmetatable(env, { __index = _G }) local fn, m = Util.loadUrl(url, env) if fn then return pcall(fn, ...) end return fn, m end function Util.run(env, path, ...) if type(env) ~= 'table' then error('Util.run: env must be a table', 2) end setmetatable(env, { __index = _G }) local fn, m = loadfile(path, env) if fn then return pcall(fn, ...) end return fn, m end function Util.runFunction(env, fn, ...) setfenv(fn, env) setmetatable(env, { __index = _G }) return pcall(fn, ...) end --[[ String functions ]] -- function Util.toBytes(n) if not tonumber(n) then error('Util.toBytes: n must be a number', 2) end if n >= 1000000 or n <= -1000000 then return _sformat('%sM', math.floor(n/1000000 * 10) / 10) elseif n >= 10000 or n <= -10000 then return _sformat('%sK', math.floor(n/1000)) elseif n >= 1000 or n <= -1000 then return _sformat('%sK', math.floor(n/1000 * 10) / 10) end return tostring(n) end function Util.insertString(str, istr, pos) return str:sub(1, pos - 1) .. istr .. str:sub(pos) end function Util.split(str, pattern) if not str or type(str) ~= 'string' then error('Util.split: Invalid parameters', 2) end pattern = pattern or "(.-)\n" local t = {} local function helper(line) table.insert(t, line) return "" end helper((str:gsub(pattern, helper))) return t end function Util.matches(str, pattern) pattern = pattern or '%S+' local t = { } for s in str:gmatch(pattern) do table.insert(t, s) end return t end function Util.startsWith(s, match) return _ssub(s, 1, #match) == match end -- return a fixed length string using specified alignment function Util.widthify(s, len, align) s = s or '' local slen = #s if slen > len then return _ssub(s, 1, len) elseif slen == len then return s elseif align == 'center' then local space = math.floor((len - slen) / 2) s = _srep(' ', space) .. s return s .. _srep(' ', len - #s) elseif align == 'right' then return _srep(' ', len - slen) .. s end return s .. _srep(' ', len - slen) end -- http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76 function Util.trim(s) return s:find('^%s*$') and '' or s:match('^%s*(.*%S)') end -- trim whitespace from left end of string function Util.triml(s) return s:match('^%s*(.*)') end -- trim whitespace from right end of string function Util.trimr(s) return s:find('^%s*$') and '' or s:match('^(.*%S)') end -- end http://snippets.luacode.org/?p=snippets/trim_whitespace_from_string_76 local function wrap(text, max, lines) local index = 1 repeat if #text <= max then table.insert(lines, text) text = '' elseif text:sub(max+1, max+1) == ' ' then table.insert(lines, text:sub(index, max)) text = text:sub(max + 2) else local x = text:sub(1, max) local s = x:match('(.*) ') or x text = text:sub(#s + 1) table.insert(lines, s) end text = text:match('^%s*(.*)') until not text or #text == 0 return lines end function Util.wordWrap(str, limit) local lines = { } for _,line in ipairs(Util.split(str)) do wrap(line, limit, lines) end return lines end -- https://github.com/MightyPirates/OpenComputers function Util.parse(...) local params = table.pack(...) local args = {} local options = {} local doneWithOptions = false for i = 1, params.n do local param = params[i] if not doneWithOptions and type(param) == "string" then if param == "--" then doneWithOptions = true -- stop processing options at `--` elseif param:sub(1, 2) == "--" then local key, value = param:match("%-%-(.-)=(.*)") if not key then key, value = param:sub(3), true end options[key] = value elseif param:sub(1, 1) == "-" and param ~= "-" then for j = 2, string.len(param) do options[string.sub(param, j, j)] = true end else table.insert(args, param) end else table.insert(args, param) end end return args, options end function Util.args(arg) local options, args = { }, { } local k = 1 while k <= #arg do local v = arg[k] if _ssub(v, 1, 1) == '-' then local opt = _ssub(v, 2) options[opt] = arg[k + 1] k = k + 1 else table.insert(args, v) end k = k + 1 end return options, args end -- http://lua-users.org/wiki/AlternativeGetOpt local function getopt( arg, options ) local tab = {} for k, v in ipairs(arg) do if type(v) == 'string' then if _ssub( v, 1, 2) == "--" then local x = string.find( v, "=", 1, true ) if x then tab[ _ssub( v, 3, x-1 ) ] = _ssub( v, x+1 ) else tab[ _ssub( v, 3 ) ] = true end elseif _ssub( v, 1, 1 ) == "-" then local y = 2 local l = string.len(v) local jopt while ( y <= l ) do jopt = _ssub( v, y, y ) if string.find( options, jopt, 1, true ) then if y < l then tab[ jopt ] = _ssub( v, y+1 ) y = l else tab[ jopt ] = arg[ k + 1 ] end else tab[ jopt ] = true end y = y + 1 end end end end return tab end function Util.showOptions(options) print('Arguments: ') for _, v in pairs(options) do print(_sformat('-%s %s', v.arg, v.desc)) end end function Util.getOptions(options, args, ignoreInvalid) local argLetters = '' for _,o in pairs(options) do if o.type ~= 'flag' then argLetters = argLetters .. o.arg end end local rawOptions = getopt(args, argLetters) for k,ro in pairs(rawOptions) do local found = false for _,o in pairs(options) do if o.arg == k then found = true if o.type == 'number' then o.value = tonumber(ro) elseif o.type == 'help' then Util.showOptions(options) return false else o.value = ro end end end if not found and not ignoreInvalid then print('Invalid argument') Util.showOptions(options) return false end end return true, Util.size(rawOptions) end -- https://www.lua.org/pil/9.3.html function Util.permutation(tbl) local function permgen(a, n) if n == 0 then coroutine.yield(a) else for i=1,n do a[n], a[i] = a[i], a[n] permgen(a, n - 1) a[n], a[i] = a[i], a[n] end end end local co = coroutine.create(function() permgen(tbl, #tbl) end) return function() local _, res = coroutine.resume(co) return res end end return Util