local global_env = _G --- Utility functions shared by other modules. -- Does not requires modules directly (only as locals -- inside specific functions) to avoid interdependencies, -- as this is used in the bootstrapping stage of luarocks.cfg. module("luarocks.util", package.seeall) local scheduled_functions = {} --- Schedule a function to be executed upon program termination. -- This is useful for actions such as deleting temporary directories -- or failure rollbacks. -- @param f function: Function to be executed. -- @param ... arguments to be passed to function. -- @return table: A token representing the scheduled execution, -- which can be used to remove the item later from the list. function schedule_function(f, ...) assert(type(f) == "function") local item = { fn = f, args = {...} } table.insert(scheduled_functions, item) return item end --- Unschedule a function. -- This is useful for cancelling a rollback of a completed operation. -- @param table: The token representing the scheduled function that was -- returned from the schedule_function call. function remove_scheduled_function(item) for k, v in pairs(scheduled_functions) do if v == item then table.remove(scheduled_functions, k) return end end end --- Execute scheduled functions. -- Some calls create temporary files and/or directories and register -- corresponding cleanup functions. Calling this function will run -- these function, erasing temporaries. -- Functions are executed in the inverse order they were scheduled. function run_scheduled_functions() local fs = require("luarocks.fs") fs.change_dir_to_root() for i = #scheduled_functions, 1, -1 do local item = scheduled_functions[i] item.fn(unpack(item.args)) end end --- Extract flags from an arguments list. -- Given string arguments, extract flag arguments into a flags set. -- For example, given "foo", "--tux=beep", "--bla", "bar", "--baz", -- it would return the following: -- {["bla"] = true, ["tux"] = "beep", ["baz"] = true}, "foo", "bar". function parse_flags(...) local args = {...} local flags = {} for i = #args, 1, -1 do local flag = args[i]:match("^%-%-(.*)") if flag then local var,val = flag:match("([a-z_%-]*)=(.*)") if val then flags[var] = val else flags[flag] = true end table.remove(args, i) end end return flags, unpack(args) end --- Merges contents of src on top of dst's contents. -- @param dst Destination table, which will receive src's contents. -- @param src Table which provides new contents to dst. -- @see platform_overrides function deep_merge(dst, src) for k, v in pairs(src) do if type(v) == "table" then if not dst[k] then dst[k] = {} end deep_merge(dst[k], v) else dst[k] = v end end end --- Perform platform-specific overrides on a table. -- Overrides values of table with the contents of the appropriate -- subset of its "platforms" field. The "platforms" field should -- be a table containing subtables keyed with strings representing -- platform names. Names that match the contents of the global -- cfg.platforms setting are used. For example, if -- cfg.platforms= {"foo"}, then the fields of -- tbl.platforms.foo will overwrite those of tbl with the same -- names. For table values, the operation is performed recursively -- (tbl.platforms.foo.x.y.z overrides tbl.x.y.z; other contents of -- tbl.x are preserved). -- @param tbl table or nil: Table which may contain a "platforms" field; -- if it doesn't (or if nil is passed), this function does nothing. function platform_overrides(tbl) assert(type(tbl) == "table" or not tbl) local cfg = require("luarocks.cfg") if not tbl then return end if tbl.platforms then for _, platform in ipairs(cfg.platforms) do local platform_tbl = tbl.platforms[platform] if platform_tbl then deep_merge(tbl, platform_tbl) end end end tbl.platforms = nil end local var_format_pattern = "%$%((%a[%a%d_]+)%)" --- Display a warning message. -- @param msg string: the warning message function warning(msg) print("Warning: "..msg) end --- Create a new shallow copy of a table: a new table with -- the same keys and values. Keys point to the same objects as -- the original table (ie, does not copy recursively). -- @param tbl table: the input table -- @return table: a new table with the same contents. local function make_shallow_copy(tbl) local copy = {} for k,v in pairs(tbl) do copy[k] = v end return copy end -- Check if a set of needed variables are referenced -- somewher in a list of definitions, warning the user -- about any unused ones. Each key in needed_set should -- appear as a $(XYZ) variable at least once as a -- substring of some value of var_defs. -- @param var_defs: a table with string keys and string -- values, containing variable definitions. -- @param needed_set: a set where keys are the names of -- needed variables. -- @param msg string: the warning message to display. function warn_if_not_used(var_defs, needed_set, msg) needed_set = make_shallow_copy(needed_set) for var,val in pairs(var_defs) do for used in val:gmatch(var_format_pattern) do needed_set[used] = nil end end for var,_ in pairs(needed_set) do warning(msg:format(var)) end end -- Output any entries that might remain in $(XYZ) format, -- warning the user that substitutions have failed. -- @param line string: the input string local function warn_failed_matches(line) local any_failed = false if line:match(var_format_pattern) then for unmatched in line:gmatch(var_format_pattern) do warning("unmatched variable " .. unmatched) any_failed = true end end return any_failed end --- Perform make-style variable substitutions on string values of a table. -- For every string value tbl.x which contains a substring of the format -- "$(XYZ)" will have this substring replaced by vars["XYZ"], if that field -- exists in vars. Only string values are processed; this function -- does not scan subtables recursively. -- @param tbl table: Table to have its string values modified. -- @param vars table: Table containing string-string key-value pairs -- representing variables to replace in the strings values of tbl. function variable_substitutions(tbl, vars) assert(type(tbl) == "table") assert(type(vars) == "table") local updated = {} for k, v in pairs(tbl) do if type(v) == "string" then updated[k] = v:gsub(var_format_pattern, vars) if warn_failed_matches(updated[k]) then updated[k] = updated[k]:gsub(var_format_pattern, "") end end end for k, v in pairs(updated) do tbl[k] = v end end --- Return an array of keys of a table. -- @param tbl table: The input table. -- @return table: The array of keys. function keys(tbl) local ks = {} for k,_ in pairs(tbl) do table.insert(ks, k) end return ks end -- The iterator function used internally by util.sortedpairs. -- @param tbl table: The table to be iterated. -- @param sort_function function or nil: An optional comparison function -- to be used by table.sort when sorting keys. -- @see sortedpairs local function sortedpairs_iterator(tbl, sort_function) local ks = keys(tbl) table.sort(ks, sort_function) for _, k in ipairs(ks) do coroutine.yield(k, tbl[k]) 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 nil: An optional comparison function -- to be used by table.sort when sorting keys. -- @return function: the iterator function. function sortedpairs(tbl, sort_function) return coroutine.wrap(function() sortedpairs_iterator(tbl, sort_function) end) end function starts_with(s, prefix) return s:sub(1,#prefix) == prefix end --[[ Author: Julio Manuel Fernandez-Diaz Date: January 12, 2007 (For Lua 5.1) Formats tables with cycles recursively to any depth. The output is returned as a string. References to other tables are shown as values. Self references are indicated. The string returned is "Lua code", which can be procesed (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: print(table.show(t, "t")) -- a typical use Heavily based on "Saving tables with cycles", PIL2, p. 113. Arguments: t is the table. name is the name of the table (optional) indent is a first indentation (optional). --]] function show_table(t, name, indent) local cart -- a container local autoref -- for self references local function isemptytable(t) return next(t) == nil end local function basicSerialize (o) local so = tostring(o) if type(o) == "function" then local info = debug.getinfo(o, "S") -- 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 addtocart (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 .. " = " .. basicSerialize(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 tablecount(value) == 0 then if isemptytable(value) then cart = cart .. " = {};\n" else cart = cart .. " = {\n" for k, v in pairs(value) do k = basicSerialize(k) local fname = ("%s[%s]"):format(name, k) field = ("[%s]"):format(k) -- three spaces between levels addtocart(v, fname, indent .. " ", saved, field) end cart = cart .. indent .. "};\n" end end end end name = name or "__unnamed__" if type(t) ~= "table" then return name .. " = " .. basicSerialize(t) end cart, autoref = "", "" addtocart(t, name, indent) return cart .. autoref end