local unpack = table.unpack or unpack -- luacheck: compat
|
|
local utils = {}
|
|
utils.dir_sep = package.config:sub(1,1)
|
utils.is_windows = utils.dir_sep == "\\"
|
|
local bom = "\239\187\191"
|
|
-- Returns all contents of file (path or file handler) or nil + error message.
|
function utils.read_file(file)
|
local handler
|
|
if type(file) == "string" then
|
local open_err
|
handler, open_err = io.open(file, "rb")
|
|
if not handler then
|
open_err = utils.unprefix(open_err, file .. ": ")
|
return nil, "couldn't read: " .. open_err
|
end
|
else
|
handler = file
|
end
|
|
local res, read_err = handler:read("*a")
|
handler:close()
|
|
if not res then
|
return nil, "couldn't read: " .. read_err
|
end
|
|
-- Use :len() instead of # operator because in some environments
|
-- string library is patched to handle UTF.
|
if res:sub(1, bom:len()) == bom then
|
res = res:sub(bom:len() + 1)
|
end
|
|
return res
|
end
|
|
-- luacheck: push
|
-- luacheck: compat
|
if _VERSION:find "5.1" then
|
-- Loads Lua source string in an environment, returns function or nil, error.
|
function utils.load(src, env, chunkname)
|
local func, err = loadstring(src, chunkname)
|
|
if func then
|
if env then
|
setfenv(func, env)
|
end
|
|
return func
|
else
|
return nil, err
|
end
|
end
|
else
|
-- Loads Lua source string in an environment, returns function or nil, error.
|
function utils.load(src, env, chunkname)
|
return load(src, chunkname, "t", env or _ENV)
|
end
|
end
|
-- luacheck: pop
|
|
-- Loads config containing assignments to global variables from path.
|
-- Returns config table and return value of config or nil and error type
|
-- ("I/O" or "syntax" or "runtime") and error message.
|
function utils.load_config(path, env)
|
env = env or {}
|
local src, read_err = utils.read_file(path)
|
|
if not src then
|
return nil, "I/O", read_err
|
end
|
|
local func, load_err = utils.load(src, env, "chunk")
|
|
if not func then
|
return nil, "syntax", "line " .. utils.unprefix(load_err, "[string \"chunk\"]:")
|
end
|
|
local ok, res = pcall(func)
|
|
if not ok then
|
return nil, "runtime", "line " .. utils.unprefix(res, "[string \"chunk\"]:")
|
end
|
|
return env, res
|
end
|
|
function utils.array_to_set(array)
|
local set = {}
|
|
for index, value in ipairs(array) do
|
set[value] = index
|
end
|
|
return set
|
end
|
|
function utils.concat_arrays(array)
|
local res = {}
|
|
for _, subarray in ipairs(array) do
|
for _, item in ipairs(subarray) do
|
table.insert(res, item)
|
end
|
end
|
|
return res
|
end
|
|
function utils.update(t1, t2)
|
for k, v in pairs(t2) do
|
t1[k] = v
|
end
|
|
return t1
|
end
|
|
local class_metatable = {}
|
|
function class_metatable.__call(class, ...)
|
local obj = setmetatable({}, class)
|
|
if class.__init then
|
class.__init(obj, ...)
|
end
|
|
return obj
|
end
|
|
function utils.class()
|
local class = setmetatable({}, class_metatable)
|
class.__index = class
|
return class
|
end
|
|
function utils.is_instance(object, class)
|
return rawequal(debug.getmetatable(object), class)
|
end
|
|
utils.Stack = utils.class()
|
|
function utils.Stack:__init()
|
self.size = 0
|
end
|
|
function utils.Stack:push(value)
|
self.size = self.size + 1
|
self[self.size] = value
|
self.top = value
|
end
|
|
function utils.Stack:pop()
|
local value = self[self.size]
|
self[self.size] = nil
|
self.size = self.size - 1
|
self.top = self[self.size]
|
return value
|
end
|
|
local ErrorWrapper = utils.class()
|
|
function ErrorWrapper:__init(err, traceback)
|
self.err = err
|
self.traceback = traceback
|
end
|
|
function ErrorWrapper:__tostring()
|
return tostring(self.err) .. "\n" .. self.traceback
|
end
|
|
local function error_handler(err)
|
if utils.is_instance(err, ErrorWrapper) then
|
return err
|
else
|
return ErrorWrapper(err, debug.traceback())
|
end
|
end
|
|
-- Like pcall, but wraps errors in {err = err, traceback = traceback}
|
-- tables unless already wrapped.
|
function utils.try(f, ...)
|
local args = {...}
|
local num_args = select("#", ...)
|
|
local function task()
|
return f(unpack(args, 1, num_args))
|
end
|
|
return xpcall(task, error_handler)
|
end
|
|
local function ripairs_iterator(array, i)
|
if i == 1 then
|
return nil
|
else
|
i = i - 1
|
return i, array[i]
|
end
|
end
|
|
function utils.ripairs(array)
|
return ripairs_iterator, array, #array + 1
|
end
|
|
function utils.sorted_pairs(t)
|
local keys = {}
|
|
for key in pairs(t) do
|
table.insert(keys, key)
|
end
|
|
table.sort(keys)
|
|
local index = 1
|
|
return function()
|
local key = keys[index]
|
|
if key == nil then
|
return
|
end
|
|
index = index + 1
|
|
return key, t[key]
|
end
|
end
|
|
function utils.unprefix(str, prefix)
|
if str:sub(1, #prefix) == prefix then
|
return str:sub(#prefix + 1)
|
else
|
return str
|
end
|
end
|
|
function utils.after(str, pattern)
|
local _, last_matched_index = str:find(pattern)
|
|
if last_matched_index then
|
return str:sub(last_matched_index + 1)
|
end
|
end
|
|
function utils.strip(str)
|
local _, last_start_space = str:find("^%s*")
|
local first_end_space = str:find("%s*$")
|
return str:sub(last_start_space + 1, first_end_space - 1)
|
end
|
|
-- `sep` must be nil or a single character. Behaves like python's `str.split`.
|
function utils.split(str, sep)
|
local parts = {}
|
local pattern
|
|
if sep then
|
pattern = sep .. "([^" .. sep .. "]*)"
|
str = sep .. str
|
else
|
pattern = "%S+"
|
end
|
|
for part in str:gmatch(pattern) do
|
table.insert(parts, part)
|
end
|
|
return parts
|
end
|
|
utils.InvalidPatternError = utils.class()
|
|
function utils.InvalidPatternError:__init(err, pattern)
|
self.err = err
|
self.pattern = pattern
|
end
|
|
function utils.InvalidPatternError:__tostring()
|
return self.err
|
end
|
|
-- Behaves like string.match, except it normally returns boolean and
|
-- throws an instance of utils.InvalidPatternError on invalid pattern.
|
-- The error object turns into original error when tostring is used on it,
|
-- to ensure behaviour is predictable when luacheck is used as a module.
|
function utils.pmatch(str, pattern)
|
assert(type(str) == "string")
|
assert(type(pattern) == "string")
|
|
local ok, res = pcall(string.match, str, pattern)
|
|
if not ok then
|
error(utils.InvalidPatternError(res, pattern), 0)
|
else
|
return not not res
|
end
|
end
|
|
-- Maps func over array.
|
function utils.map(func, array)
|
local res = {}
|
|
for i, item in ipairs(array) do
|
res[i] = func(item)
|
end
|
|
return res
|
end
|
|
-- Returns validator checking type.
|
function utils.has_type(type_)
|
return function(x)
|
if type(x) == type_ then
|
return true
|
else
|
return false, ("%s expected, got %s"):format(type_, type(x))
|
end
|
end
|
end
|
|
-- Returns validator checking type and allowing false.
|
function utils.has_type_or_false(type_)
|
return function(x)
|
if type(x) == type_ then
|
return true
|
elseif type(x) == "boolean" then
|
if x then
|
return false, ("%s or false expected, got true"):format(type_)
|
else
|
return true
|
end
|
else
|
return false, ("%s or false expected, got %s"):format(type_, type(x))
|
end
|
end
|
end
|
|
-- Returns validator checking two type possibilities.
|
function utils.has_either_type(type1, type2)
|
return function(x)
|
if type(x) == type1 or type(x) == type2 then
|
return true
|
else
|
return false, ("%s or %s expected, got %s"):format(type1, type2, type(x))
|
end
|
end
|
end
|
|
-- Returns validator checking that value is an array with elements of type.
|
function utils.array_of(type_)
|
return function(x)
|
if type(x) ~= "table" then
|
return false, ("array of %ss expected, got %s"):format(type_, type(x))
|
end
|
|
for index, item in ipairs(x) do
|
if type(item) ~= type_ then
|
return false, ("array of %ss expected, got %s at index [%d]"):format(type_, type(item), index)
|
end
|
end
|
|
return true
|
end
|
end
|
|
return utils
|