summaryrefslogtreecommitdiff
path: root/src/luarocks/core/util.lua
diff options
context:
space:
mode:
authorMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
committerMike Vink <mike@pionative.com>2025-02-03 21:29:42 +0100
commit5155816b7b925dec5d5feb1568b1d7ceb00938b9 (patch)
treedeca28ea15e79f6f804c3d90d2ba757881638af5 /src/luarocks/core/util.lua
fetch tarballHEADmaster
Diffstat (limited to 'src/luarocks/core/util.lua')
-rw-r--r--src/luarocks/core/util.lua322
1 files changed, 322 insertions, 0 deletions
diff --git a/src/luarocks/core/util.lua b/src/luarocks/core/util.lua
new file mode 100644
index 0000000..e9abdd3
--- /dev/null
+++ b/src/luarocks/core/util.lua
@@ -0,0 +1,322 @@
+
+local util = {}
+
+local require = nil
+--------------------------------------------------------------------------------
+
+local dir_sep = package.config:sub(1, 1)
+
+--- Run a process and read a its output.
+-- Equivalent to io.popen(cmd):read("*l"), except that it
+-- closes the fd right away.
+-- @param cmd string: The command to execute
+-- @param spec string: "*l" by default, to read a single line.
+-- May be used to read more, passing, for instance, "*a".
+-- @return string: the output of the program.
+function util.popen_read(cmd, spec)
+ local tmpfile = (dir_sep == "\\")
+ and (os.getenv("TMP") .. "/luarocks-" .. tostring(math.floor(math.random() * 10000)))
+ or os.tmpname()
+ os.execute(cmd .. " > " .. tmpfile)
+ local fd = io.open(tmpfile, "rb")
+ if not fd then
+ os.remove(tmpfile)
+ return ""
+ end
+ local out = fd:read(spec or "*l")
+ fd:close()
+ os.remove(tmpfile)
+ return out or ""
+end
+
+---
+-- Formats tables with cycles recursively to any depth.
+-- References to other tables are shown as values.
+-- Self references are indicated.
+-- The string returned is "Lua code", which can be processed
+-- (in the case in which indent is composed by spaces or "--").
+-- Userdata and function keys and values are shown as strings,
+-- which logically are exactly not equivalent to the original code.
+-- This routine can serve for pretty formating tables with
+-- proper indentations, apart from printing them:
+-- io.write(table.show(t, "t")) -- a typical use
+-- Written by Julio Manuel Fernandez-Diaz,
+-- Heavily based on "Saving tables with cycles", PIL2, p. 113.
+-- @param t table: is the table.
+-- @param tname string: is the name of the table (optional)
+-- @param top_indent string: is a first indentation (optional).
+-- @return string: the pretty-printed table
+function util.show_table(t, tname, top_indent)
+ local cart -- a container
+ local autoref -- for self references
+
+ local function is_empty_table(tbl) return next(tbl) == nil end
+
+ local function basic_serialize(o)
+ local so = tostring(o)
+ if type(o) == "function" then
+ local info = debug and debug.getinfo(o, "S")
+ if not info then
+ return ("%q"):format(so)
+ end
+ -- info.name is nil because o is not a calling level
+ if info.what == "C" then
+ return ("%q"):format(so .. ", C function")
+ else
+ -- the information is defined through lines
+ return ("%q"):format(so .. ", defined in (" .. info.linedefined .. "-" .. info.lastlinedefined .. ")" .. info.source)
+ end
+ elseif type(o) == "number" then
+ return so
+ else
+ return ("%q"):format(so)
+ end
+ end
+
+ local function add_to_cart(value, name, indent, saved, field)
+ indent = indent or ""
+ saved = saved or {}
+ field = field or name
+
+ cart = cart .. indent .. field
+
+ if type(value) ~= "table" then
+ cart = cart .. " = " .. basic_serialize(value) .. ";\n"
+ else
+ if saved[value] then
+ cart = cart .. " = {}; -- " .. saved[value] .. " (self reference)\n"
+ autoref = autoref .. name .. " = " .. saved[value] .. ";\n"
+ else
+ saved[value] = name
+ if is_empty_table(value) then
+ cart = cart .. " = {};\n"
+ else
+ cart = cart .. " = {\n"
+ for k, v in pairs(value) do
+ k = basic_serialize(k)
+ local fname = ("%s[%s]"):format(name, k)
+ field = ("[%s]"):format(k)
+ -- three spaces between levels
+ add_to_cart(v, fname, indent .. " ", saved, field)
+ end
+ cart = cart .. indent .. "};\n"
+ end
+ end
+ end
+ end
+
+ tname = tname or "__unnamed__"
+ if type(t) ~= "table" then
+ return tname .. " = " .. basic_serialize(t)
+ end
+ cart, autoref = "", ""
+ add_to_cart(t, tname, top_indent)
+ return cart .. autoref
+end
+
+--- Merges contents of src on top of dst's contents
+-- (i.e. if an key from src already exists in dst, replace it).
+-- @param dst Destination table, which will receive src's contents.
+-- @param src Table which provides new contents to dst.
+function util.deep_merge(dst, src)
+ for k, v in pairs(src) do
+ if type(v) == "table" then
+ if dst[k] == nil then
+ dst[k] = {}
+ end
+ if type(dst[k]) == "table" then
+ util.deep_merge(dst[k], v)
+ else
+ dst[k] = v
+ end
+ else
+ dst[k] = v
+ end
+ end
+end
+
+--- Merges contents of src below those of dst's contents
+-- (i.e. if an key from src already exists in dst, do not replace it).
+-- @param dst Destination table, which will receive src's contents.
+-- @param src Table which provides new contents to dst.
+function util.deep_merge_under(dst, src)
+ for k, v in pairs(src) do
+ if type(v) == "table" then
+ if dst[k] == nil then
+ dst[k] = {}
+ end
+ if type(dst[k]) == "table" then
+ util.deep_merge_under(dst[k], v)
+ end
+ elseif dst[k] == nil then
+ dst[k] = v
+ end
+ end
+end
+
+--- Clean up a path-style string ($PATH, $LUA_PATH/package.path, etc.),
+-- removing repeated entries and making sure only the relevant
+-- Lua version is used.
+-- Example: given ("a;b;c;a;b;d", ";"), returns "a;b;c;d".
+-- @param list string: A path string (from $PATH or package.path)
+-- @param sep string: The separator
+-- @param lua_version (optional) string: The Lua version to use.
+-- @param keep_first (optional) if true, keep first occurrence in case
+-- of duplicates; otherwise keep last occurrence. The default is false.
+function util.cleanup_path(list, sep, lua_version, keep_first)
+ assert(type(list) == "string")
+ assert(type(sep) == "string")
+
+ list = list:gsub(dir_sep, "/")
+
+ local parts = util.split_string(list, sep)
+ local final, entries = {}, {}
+ local start, stop, step
+
+ if keep_first then
+ start, stop, step = 1, #parts, 1
+ else
+ start, stop, step = #parts, 1, -1
+ end
+
+ for i = start, stop, step do
+ local part = parts[i]:gsub("//", "/")
+ if lua_version then
+ part = part:gsub("/lua/([%d.]+)/", function(part_version)
+ if part_version:sub(1, #lua_version) ~= lua_version then
+ return "/lua/"..lua_version.."/"
+ end
+ end)
+ end
+ if not entries[part] then
+ local at = keep_first and #final+1 or 1
+ table.insert(final, at, part)
+ entries[part] = true
+ end
+ end
+
+ return (table.concat(final, sep):gsub("/", dir_sep))
+end
+
+-- from http://lua-users.org/wiki/SplitJoin
+-- by Philippe Lhoste
+function util.split_string(str, delim, maxNb)
+ -- Eliminate bad cases...
+ if string.find(str, delim) == nil then
+ return { str }
+ end
+ if maxNb == nil or maxNb < 1 then
+ maxNb = 0 -- No limit
+ end
+ local result = {}
+ local pat = "(.-)" .. delim .. "()"
+ local nb = 0
+ local lastPos
+ for part, pos in string.gmatch(str, pat) do
+ nb = nb + 1
+ result[nb] = part
+ lastPos = pos
+ if nb == maxNb then break end
+ end
+ -- Handle the last field
+ if nb ~= maxNb then
+ result[nb + 1] = string.sub(str, lastPos)
+ end
+ return result
+end
+
+--- Return an array of keys of a table.
+-- @param tbl table: The input table.
+-- @return table: The array of keys.
+function util.keys(tbl)
+ local ks = {}
+ for k,_ in pairs(tbl) do
+ table.insert(ks, k)
+ end
+ return ks
+end
+
+--- Print a line to standard error
+function util.printerr(...)
+ io.stderr:write(table.concat({...},"\t"))
+ io.stderr:write("\n")
+end
+
+--- Display a warning message.
+-- @param msg string: the warning message
+function util.warning(msg)
+ util.printerr("Warning: "..msg)
+end
+
+--- Simple sort function used as a default for util.sortedpairs.
+local function default_sort(a, b)
+ local ta = type(a)
+ local tb = type(b)
+ if ta == "number" and tb == "number" then
+ return a < b
+ elseif ta == "number" then
+ return true
+ elseif tb == "number" then
+ return false
+ else
+ return tostring(a) < tostring(b)
+ end
+end
+
+--- A table iterator generator that returns elements sorted by key,
+-- to be used in "for" loops.
+-- @param tbl table: The table to be iterated.
+-- @param sort_function function or table or nil: An optional comparison function
+-- to be used by table.sort when sorting keys, or an array listing an explicit order
+-- for keys. If a value itself is an array, it is taken so that the first element
+-- is a string representing the field name, and the second element is a priority table
+-- for that key, which is returned by the iterator as the third value after the key
+-- and the value.
+-- @return function: the iterator function.
+function util.sortedpairs(tbl, sort_function)
+ sort_function = sort_function or default_sort
+ local keys = util.keys(tbl)
+ local sub_orders = {}
+
+ if type(sort_function) == "function" then
+ table.sort(keys, sort_function)
+ else
+ local order = sort_function
+ local ordered_keys = {}
+ local all_keys = keys
+ keys = {}
+
+ for _, order_entry in ipairs(order) do
+ local key, sub_order
+ if type(order_entry) == "table" then
+ key = order_entry[1]
+ sub_order = order_entry[2]
+ else
+ key = order_entry
+ end
+
+ if tbl[key] then
+ ordered_keys[key] = true
+ sub_orders[key] = sub_order
+ table.insert(keys, key)
+ end
+ end
+
+ table.sort(all_keys, default_sort)
+ for _, key in ipairs(all_keys) do
+ if not ordered_keys[key] then
+ table.insert(keys, key)
+ end
+ end
+ end
+
+ local i = 1
+ return function()
+ local key = keys[i]
+ i = i + 1
+ return key, tbl[key], sub_orders[key]
+ end
+end
+
+return util
+