From 0efd878f6435c4d929c101f7c1388801096c6004 Mon Sep 17 00:00:00 2001 From: V1K1NGbg Date: Tue, 9 Jul 2024 22:25:26 +0300 Subject: halfway commit --- src/luarocks/core/path.tl | 2 +- src/luarocks/core/vers.tl | 2 +- src/luarocks/dir-original.lua | 63 ++++ src/luarocks/dir.lua | 63 ---- src/luarocks/dir.tl | 64 +++++ src/luarocks/fs-original.lua | 148 ++++++++++ src/luarocks/fs.lua | 148 ---------- src/luarocks/fs.tl | 151 ++++++++++ src/luarocks/util-original.lua | 634 ++++++++++++++++++++++++++++++++++++++++ src/luarocks/util.lua | 634 ---------------------------------------- src/luarocks/util.tl | 635 +++++++++++++++++++++++++++++++++++++++++ 11 files changed, 1697 insertions(+), 847 deletions(-) create mode 100644 src/luarocks/dir-original.lua delete mode 100644 src/luarocks/dir.lua create mode 100644 src/luarocks/dir.tl create mode 100644 src/luarocks/fs-original.lua delete mode 100644 src/luarocks/fs.lua create mode 100644 src/luarocks/fs.tl create mode 100644 src/luarocks/util-original.lua delete mode 100644 src/luarocks/util.lua create mode 100644 src/luarocks/util.tl (limited to 'src') diff --git a/src/luarocks/core/path.tl b/src/luarocks/core/path.tl index 35f66e0c..e1bdc8de 100644 --- a/src/luarocks/core/path.tl +++ b/src/luarocks/core/path.tl @@ -28,7 +28,7 @@ end -- @param version string: Rock version -- @return string: a pathname with the same directory parts and a versioned basename. function path.versioned_name(file: string, prefix: string, name: string, version: string): string - assert(not name:match(dir_sep)) --! + assert(not name:match(dir_sep)) local rest = file:sub(#prefix+1):gsub("^" .. dir_sep .. "*", "") local name_version = (name.."_"..version):gsub("%-", "_"):gsub("%.", "_") diff --git a/src/luarocks/core/vers.tl b/src/luarocks/core/vers.tl index b1dcd795..554296af 100644 --- a/src/luarocks/core/vers.tl +++ b/src/luarocks/core/vers.tl @@ -196,7 +196,7 @@ end -- @param constraints table: An array of constraints in table format. -- @return boolean: True if version satisfies all constraints, -- false otherwise. -function vers.match_constraints(version: Version, constraints: {Constraints}): boolean --! +function vers.match_constraints(version: Version, constraints: {Constraints}): boolean local ok = true setmetatable(version, version_mt) for _, constr in ipairs(constraints) do diff --git a/src/luarocks/dir-original.lua b/src/luarocks/dir-original.lua new file mode 100644 index 00000000..be89e37b --- /dev/null +++ b/src/luarocks/dir-original.lua @@ -0,0 +1,63 @@ + +--- Generic utilities for handling pathnames. +local dir = {} + +local core = require("luarocks.core.dir") + +dir.path = core.path +dir.split_url = core.split_url +dir.normalize = core.normalize + +local dir_sep = package.config:sub(1, 1) + +--- Strip the path off a path+filename. +-- @param pathname string: A path+name, such as "/a/b/c" +-- or "\a\b\c". +-- @return string: The filename without its path, such as "c". +function dir.base_name(pathname) + assert(type(pathname) == "string") + + local b + b = pathname:gsub("[/\\]", "/") -- canonicalize to forward slashes + b = b:gsub("/*$", "") -- drop trailing slashes + b = b:match(".*[/\\]([^/\\]*)") -- match last component + b = b or pathname -- fallback to original if no slashes + + return b +end + +--- Strip the name off a path+filename. +-- @param pathname string: A path+name, such as "/a/b/c". +-- @return string: The filename without its path, such as "/a/b". +-- For entries such as "/a/b/", "/a" is returned. If there are +-- no directory separators in input, "" is returned. +function dir.dir_name(pathname) + assert(type(pathname) == "string") + + local d + d = pathname:gsub("[/\\]", "/") -- canonicalize to forward slashes + d = d:gsub("/*$", "") -- drop trailing slashes + d = d:match("(.*)[/]+[^/]*") -- match all components but the last + d = d or "" -- switch to "" if there's no match + d = d:gsub("/", dir_sep) -- decanonicalize to native slashes + + return d +end + +--- Returns true if protocol does not require additional tools. +-- @param protocol The protocol name +function dir.is_basic_protocol(protocol) + return protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" +end + +function dir.deduce_base_dir(url) + -- for extensions like foo.tar.gz, "gz" is stripped first + local known_exts = {} + for _, ext in ipairs{"zip", "git", "tgz", "tar", "gz", "bz2"} do + known_exts[ext] = "" + end + local base = dir.base_name(url) + return (base:gsub("%.([^.]*)$", known_exts):gsub("%.tar", "")) +end + +return dir diff --git a/src/luarocks/dir.lua b/src/luarocks/dir.lua deleted file mode 100644 index be89e37b..00000000 --- a/src/luarocks/dir.lua +++ /dev/null @@ -1,63 +0,0 @@ - ---- Generic utilities for handling pathnames. -local dir = {} - -local core = require("luarocks.core.dir") - -dir.path = core.path -dir.split_url = core.split_url -dir.normalize = core.normalize - -local dir_sep = package.config:sub(1, 1) - ---- Strip the path off a path+filename. --- @param pathname string: A path+name, such as "/a/b/c" --- or "\a\b\c". --- @return string: The filename without its path, such as "c". -function dir.base_name(pathname) - assert(type(pathname) == "string") - - local b - b = pathname:gsub("[/\\]", "/") -- canonicalize to forward slashes - b = b:gsub("/*$", "") -- drop trailing slashes - b = b:match(".*[/\\]([^/\\]*)") -- match last component - b = b or pathname -- fallback to original if no slashes - - return b -end - ---- Strip the name off a path+filename. --- @param pathname string: A path+name, such as "/a/b/c". --- @return string: The filename without its path, such as "/a/b". --- For entries such as "/a/b/", "/a" is returned. If there are --- no directory separators in input, "" is returned. -function dir.dir_name(pathname) - assert(type(pathname) == "string") - - local d - d = pathname:gsub("[/\\]", "/") -- canonicalize to forward slashes - d = d:gsub("/*$", "") -- drop trailing slashes - d = d:match("(.*)[/]+[^/]*") -- match all components but the last - d = d or "" -- switch to "" if there's no match - d = d:gsub("/", dir_sep) -- decanonicalize to native slashes - - return d -end - ---- Returns true if protocol does not require additional tools. --- @param protocol The protocol name -function dir.is_basic_protocol(protocol) - return protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" -end - -function dir.deduce_base_dir(url) - -- for extensions like foo.tar.gz, "gz" is stripped first - local known_exts = {} - for _, ext in ipairs{"zip", "git", "tgz", "tar", "gz", "bz2"} do - known_exts[ext] = "" - end - local base = dir.base_name(url) - return (base:gsub("%.([^.]*)$", known_exts):gsub("%.tar", "")) -end - -return dir diff --git a/src/luarocks/dir.tl b/src/luarocks/dir.tl new file mode 100644 index 00000000..86098413 --- /dev/null +++ b/src/luarocks/dir.tl @@ -0,0 +1,64 @@ + +--- Generic utilities for handling pathnames. +local record dir +end + +local core = require("luarocks.core.dir") + +dir.path = core.path --! +dir.split_url = core.split_url +dir.normalize = core.normalize + +local dir_sep = package.config:sub(1, 1) + +--- Strip the path off a path+filename. +-- @param pathname string: A path+name, such as "/a/b/c" +-- or "\a\b\c". +-- @return string: The filename without its path, such as "c". +function dir.base_name(pathname: string): string + assert(type(pathname) == "string") + + local b: string + b = pathname:gsub("[/\\]", "/") -- canonicalize to forward slashes + b = b:gsub("/*$", "") -- drop trailing slashes + b = b:match(".*[/\\]([^/\\]*)") -- match last component + b = b or pathname -- fallback to original if no slashes + + return b +end + +--- Strip the name off a path+filename. +-- @param pathname string: A path+name, such as "/a/b/c". +-- @return string: The filename without its path, such as "/a/b". +-- For entries such as "/a/b/", "/a" is returned. If there are +-- no directory separators in input, "" is returned. +function dir.dir_name(pathname: string): string + assert(type(pathname) == "string") + + local d: string + d = pathname:gsub("[/\\]", "/") -- canonicalize to forward slashes + d = d:gsub("/*$", "") -- drop trailing slashes + d = d:match("(.*)[/]+[^/]*") -- match all components but the last + d = d or "" -- switch to "" if there's no match + d = d:gsub("/", dir_sep) -- decanonicalize to native slashes + + return d +end + +--- Returns true if protocol does not require additional tools. +-- @param protocol The protocol name +function dir.is_basic_protocol(protocol: string): boolean --? extra type (enum) + return protocol == "http" or protocol == "https" or protocol == "ftp" or protocol == "file" +end + +function dir.deduce_base_dir(url: string): string + -- for extensions like foo.tar.gz, "gz" is stripped first + local known_exts = {} + for _, ext in ipairs{"zip", "git", "tgz", "tar", "gz", "bz2"} do + known_exts[ext] = "" + end + local base = dir.base_name(url) + return (base:gsub("%.([^.]*)$", known_exts):gsub("%.tar", "")) +end + +return dir diff --git a/src/luarocks/fs-original.lua b/src/luarocks/fs-original.lua new file mode 100644 index 00000000..a8156a21 --- /dev/null +++ b/src/luarocks/fs-original.lua @@ -0,0 +1,148 @@ + +--- Proxy module for filesystem and platform abstractions. +-- All code using "fs" code should require "luarocks.fs", +-- and not the various platform-specific implementations. +-- However, see the documentation of the implementation +-- for the API reference. + +local pairs = pairs + +local fs = {} +-- To avoid a loop when loading the other fs modules. +package.loaded["luarocks.fs"] = fs + +local cfg = require("luarocks.core.cfg") + +local pack = table.pack or function(...) return { n = select("#", ...), ... } end + +math.randomseed(os.time()) + +local fs_is_verbose = false + +do + local old_popen, old_execute + + -- patch io.popen and os.execute to display commands in verbose mode + function fs.verbose() + fs_is_verbose = true + + if old_popen or old_execute then return end + old_popen = io.popen + -- luacheck: push globals io os + io.popen = function(one, two) + if two == nil then + print("\nio.popen: ", one) + else + print("\nio.popen: ", one, "Mode:", two) + end + return old_popen(one, two) + end + + old_execute = os.execute + os.execute = function(cmd) + -- redact api keys if present + print("\nos.execute: ", (cmd:gsub("(/api/[^/]+/)([^/]+)/", function(cap, key) return cap.."/" end)) ) + local a, b, c = old_execute(cmd) + if type(a) == "boolean" then + print((a and ".........." or "##########") .. ": " .. tostring(c) .. (b == "exit" and "" or " (" .. tostring(b) .. ")")) + elseif type(a) == "number" then + print(((a == 0) and ".........." or "##########") .. ": " .. tostring(a)) + end + return a, b, c + end + -- luacheck: pop + end +end + +do + local skip_verbose_wrap = { + ["current_dir"] = true, + } + + local function load_fns(fs_table, inits) + for name, fn in pairs(fs_table) do + if name ~= "init" and not fs[name] then + if skip_verbose_wrap[name] then + fs[name] = fn + else + fs[name] = function(...) + if fs_is_verbose then + local args = pack(...) + for i=1, args.n do + local arg = args[i] + local pok, v = pcall(string.format, "%q", arg) + args[i] = pok and v or tostring(arg) + end + print("fs." .. name .. "(" .. table.concat(args, ", ") .. ")") + end + return fn(...) + end + end + end + end + if fs_table.init then + table.insert(inits, fs_table.init) + end + end + + local function load_platform_fns(patt, inits) + local each_platform = cfg.each_platform + + -- FIXME A quick hack for the experimental Windows build + if os.getenv("LUAROCKS_CROSS_COMPILING") then + each_platform = function() + local i = 0 + local plats = { "linux", "unix" } + return function() + i = i + 1 + return plats[i] + end + end + end + + for platform in each_platform("most-specific-first") do + local ok, fs_plat = pcall(require, patt:format(platform)) + if ok and fs_plat then + load_fns(fs_plat, inits) + end + end + end + + function fs.init() + local inits = {} + + if fs.current_dir then + -- unload luarocks fs so it can be reloaded using all modules + -- providing extra functionality in the current package paths + for k, _ in pairs(fs) do + if k ~= "init" and k ~= "verbose" then + fs[k] = nil + end + end + for m, _ in pairs(package.loaded) do + if m:match("luarocks%.fs%.") then + package.loaded[m] = nil + end + end + end + + -- Load platform-specific functions + load_platform_fns("luarocks.fs.%s", inits) + + -- Load platform-independent pure-Lua functionality + load_fns(require("luarocks.fs.lua"), inits) + + -- Load platform-specific fallbacks for missing Lua modules + load_platform_fns("luarocks.fs.%s.tools", inits) + + -- Load platform-independent external tool functionality + load_fns(require("luarocks.fs.tools"), inits) + + -- Run platform-specific initializations after everything is loaded + for _, init in ipairs(inits) do + init() + end + end +end + +return fs diff --git a/src/luarocks/fs.lua b/src/luarocks/fs.lua deleted file mode 100644 index a8156a21..00000000 --- a/src/luarocks/fs.lua +++ /dev/null @@ -1,148 +0,0 @@ - ---- Proxy module for filesystem and platform abstractions. --- All code using "fs" code should require "luarocks.fs", --- and not the various platform-specific implementations. --- However, see the documentation of the implementation --- for the API reference. - -local pairs = pairs - -local fs = {} --- To avoid a loop when loading the other fs modules. -package.loaded["luarocks.fs"] = fs - -local cfg = require("luarocks.core.cfg") - -local pack = table.pack or function(...) return { n = select("#", ...), ... } end - -math.randomseed(os.time()) - -local fs_is_verbose = false - -do - local old_popen, old_execute - - -- patch io.popen and os.execute to display commands in verbose mode - function fs.verbose() - fs_is_verbose = true - - if old_popen or old_execute then return end - old_popen = io.popen - -- luacheck: push globals io os - io.popen = function(one, two) - if two == nil then - print("\nio.popen: ", one) - else - print("\nio.popen: ", one, "Mode:", two) - end - return old_popen(one, two) - end - - old_execute = os.execute - os.execute = function(cmd) - -- redact api keys if present - print("\nos.execute: ", (cmd:gsub("(/api/[^/]+/)([^/]+)/", function(cap, key) return cap.."/" end)) ) - local a, b, c = old_execute(cmd) - if type(a) == "boolean" then - print((a and ".........." or "##########") .. ": " .. tostring(c) .. (b == "exit" and "" or " (" .. tostring(b) .. ")")) - elseif type(a) == "number" then - print(((a == 0) and ".........." or "##########") .. ": " .. tostring(a)) - end - return a, b, c - end - -- luacheck: pop - end -end - -do - local skip_verbose_wrap = { - ["current_dir"] = true, - } - - local function load_fns(fs_table, inits) - for name, fn in pairs(fs_table) do - if name ~= "init" and not fs[name] then - if skip_verbose_wrap[name] then - fs[name] = fn - else - fs[name] = function(...) - if fs_is_verbose then - local args = pack(...) - for i=1, args.n do - local arg = args[i] - local pok, v = pcall(string.format, "%q", arg) - args[i] = pok and v or tostring(arg) - end - print("fs." .. name .. "(" .. table.concat(args, ", ") .. ")") - end - return fn(...) - end - end - end - end - if fs_table.init then - table.insert(inits, fs_table.init) - end - end - - local function load_platform_fns(patt, inits) - local each_platform = cfg.each_platform - - -- FIXME A quick hack for the experimental Windows build - if os.getenv("LUAROCKS_CROSS_COMPILING") then - each_platform = function() - local i = 0 - local plats = { "linux", "unix" } - return function() - i = i + 1 - return plats[i] - end - end - end - - for platform in each_platform("most-specific-first") do - local ok, fs_plat = pcall(require, patt:format(platform)) - if ok and fs_plat then - load_fns(fs_plat, inits) - end - end - end - - function fs.init() - local inits = {} - - if fs.current_dir then - -- unload luarocks fs so it can be reloaded using all modules - -- providing extra functionality in the current package paths - for k, _ in pairs(fs) do - if k ~= "init" and k ~= "verbose" then - fs[k] = nil - end - end - for m, _ in pairs(package.loaded) do - if m:match("luarocks%.fs%.") then - package.loaded[m] = nil - end - end - end - - -- Load platform-specific functions - load_platform_fns("luarocks.fs.%s", inits) - - -- Load platform-independent pure-Lua functionality - load_fns(require("luarocks.fs.lua"), inits) - - -- Load platform-specific fallbacks for missing Lua modules - load_platform_fns("luarocks.fs.%s.tools", inits) - - -- Load platform-independent external tool functionality - load_fns(require("luarocks.fs.tools"), inits) - - -- Run platform-specific initializations after everything is loaded - for _, init in ipairs(inits) do - init() - end - end -end - -return fs diff --git a/src/luarocks/fs.tl b/src/luarocks/fs.tl new file mode 100644 index 00000000..c31d3b5a --- /dev/null +++ b/src/luarocks/fs.tl @@ -0,0 +1,151 @@ + +--- Proxy module for filesystem and platform abstractions. +-- All code using "fs" code should require "luarocks.fs", +-- and not the various platform-specific implementations. +-- However, see the documentation of the implementation +-- for the API reference. + +local pairs = pairs --? + +local record fs + +end + +-- To avoid a loop when loading the other fs modules. +package.loaded["luarocks.fs"] = fs + +local cfg = require("luarocks.core.cfg") + +local pack = table.pack or function(...) return { n = select("#", ...), ... } end --! + +math.randomseed(os.time()) + +local fs_is_verbose = false + +do + local old_popen, old_execute: function(string, string): (FILE, string), function(string): (boolean, string, integer) --! () around output to seperate + + -- patch io.popen and os.execute to display commands in verbose mode + function fs.verbose() + fs_is_verbose = true + + if old_popen or old_execute then return end --? == nil! + old_popen = io.popen + -- luacheck: push globals io os + io.popen = function(one: string, two: string): FILE, string + if two == nil then + print("\nio.popen: ", one) + else + print("\nio.popen: ", one, "Mode:", two) + end + return old_popen(one, two) + end + + old_execute = os.execute + os.execute = function(cmd: string): boolean, string, integer + -- redact api keys if present + print("\nos.execute: ", (cmd:gsub("(/api/[^/]+/)([^/]+)/", function(cap: string, key): string return cap.."/" end)) ) --! key is unused + local a, b, c = old_execute(cmd) + if type(a) == "boolean" then + print((a and ".........." or "##########") .. ": " .. tostring(c) .. (b == "exit" and "" or " (" .. tostring(b) .. ")")) + elseif type(a) == "number" then --! "a" needs to be a boolean + print(((a == 0) and ".........." or "##########") .. ": " .. tostring(a)) + end + return a, b, c + end + -- luacheck: pop + end +end + +do + local skip_verbose_wrap = { + ["current_dir"] = true, + } + + local function load_fns(fs_table, inits) + for name, fn in pairs(fs_table) do + if name ~= "init" and not fs[name] then + if skip_verbose_wrap[name] then + fs[name] = fn + else + fs[name] = function(...) + if fs_is_verbose then + local args = pack(...) + for i=1, args.n do + local arg = args[i] + local pok, v = pcall(string.format, "%q", arg) + args[i] = pok and v or tostring(arg) + end + print("fs." .. name .. "(" .. table.concat(args, ", ") .. ")") + end + return fn(...) + end + end + end + end + if fs_table.init then + table.insert(inits, fs_table.init) + end + end + + local function load_platform_fns(patt, inits) + local each_platform = cfg.each_platform + + -- FIXME A quick hack for the experimental Windows build + if os.getenv("LUAROCKS_CROSS_COMPILING") then + each_platform = function() + local i = 0 + local plats = { "linux", "unix" } + return function() + i = i + 1 + return plats[i] + end + end + end + + for platform in each_platform("most-specific-first") do + local ok, fs_plat = pcall(require, patt:format(platform)) + if ok and fs_plat then + load_fns(fs_plat, inits) + end + end + end + + function fs.init() + local inits = {} + + if fs.current_dir then + -- unload luarocks fs so it can be reloaded using all modules + -- providing extra functionality in the current package paths + for k, _ in pairs(fs) do + if k ~= "init" and k ~= "verbose" then + fs[k] = nil + end + end + for m, _ in pairs(package.loaded) do + if m:match("luarocks%.fs%.") then + package.loaded[m] = nil + end + end + end + + -- Load platform-specific functions + load_platform_fns("luarocks.fs.%s", inits) + + -- Load platform-independent pure-Lua functionality + load_fns(require("luarocks.fs.lua"), inits) + + -- Load platform-specific fallbacks for missing Lua modules + load_platform_fns("luarocks.fs.%s.tools", inits) + + -- Load platform-independent external tool functionality + load_fns(require("luarocks.fs.tools"), inits) + + -- Run platform-specific initializations after everything is loaded + for _, init in ipairs(inits) do + init() + end + end +end + +return fs diff --git a/src/luarocks/util-original.lua b/src/luarocks/util-original.lua new file mode 100644 index 00000000..de9157fc --- /dev/null +++ b/src/luarocks/util-original.lua @@ -0,0 +1,634 @@ + +--- Assorted utilities for managing tables, plus a scheduler for rollback functions. +-- Does not requires modules directly (only as locals +-- inside specific functions) to avoid interdependencies, +-- as this is used in the bootstrapping stage of luarocks.core.cfg. + +local util = {} + +local core = require("luarocks.core.util") + +util.cleanup_path = core.cleanup_path +util.split_string = core.split_string +util.sortedpairs = core.sortedpairs +util.deep_merge = core.deep_merge +util.deep_merge_under = core.deep_merge_under +util.popen_read = core.popen_read +util.show_table = core.show_table +util.printerr = core.printerr +util.warning = core.warning +util.keys = core.keys + +local unpack = unpack or table.unpack +local pack = table.pack or function(...) return { n = select("#", ...), ... } end + +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 util.schedule_function(f, ...) + assert(type(f) == "function") + + local item = { fn = f, args = pack(...) } + table.insert(scheduled_functions, item) + return item +end + +--- Unschedule a function. +-- This is useful for cancelling a rollback of a completed operation. +-- @param item table: The token representing the scheduled function that was +-- returned from the schedule_function call. +function util.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 util.run_scheduled_functions() + local fs = require("luarocks.fs") + if fs.change_dir_to_root then + fs.change_dir_to_root() + end + for i = #scheduled_functions, 1, -1 do + local item = scheduled_functions[i] + item.fn(unpack(item.args, 1, item.args.n)) + end +end + +--- Produce a Lua pattern that matches precisely the given string +-- (this is suitable to be concatenating to other patterns, +-- so it does not include beginning- and end-of-string markers (^$) +-- @param s string: The input string +-- @return string: The equivalent pattern +function util.matchquote(s) + return (s:gsub("[?%-+*%[%].%%()$^]","%%%1")) +end + +local var_format_pattern = "%$%((%a[%a%d_]+)%)" + +-- Check if a set of needed variables are referenced +-- somewhere 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 util.warn_if_not_used(var_defs, needed_set, msg) + local seen = {} + for _, val in pairs(var_defs) do + for used in val:gmatch(var_format_pattern) do + seen[used] = true + end + end + for var, _ in pairs(needed_set) do + if not seen[var] then + util.warning(msg:format(var)) + end + 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 + util.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 util.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 + +function util.lua_versions(sort) + local versions = { "5.1", "5.2", "5.3", "5.4" } + local i = 0 + if sort == "descending" then + i = #versions + 1 + return function() + i = i - 1 + return versions[i] + end + else + return function() + i = i + 1 + return versions[i] + end + end +end + +function util.lua_path_variables() + local cfg = require("luarocks.core.cfg") + local lpath_var = "LUA_PATH" + local lcpath_var = "LUA_CPATH" + + local lv = cfg.lua_version:gsub("%.", "_") + if lv ~= "5_1" then + if os.getenv("LUA_PATH_" .. lv) then + lpath_var = "LUA_PATH_" .. lv + end + if os.getenv("LUA_CPATH_" .. lv) then + lcpath_var = "LUA_CPATH_" .. lv + end + end + return lpath_var, lcpath_var +end + +function util.starts_with(s, prefix) + return s:sub(1,#prefix) == prefix +end + +--- Print a line to standard output +function util.printout(...) + io.stdout:write(table.concat({...},"\t")) + io.stdout:write("\n") +end + +function util.title(msg, porcelain, underline) + if porcelain then return end + util.printout() + util.printout(msg) + util.printout((underline or "-"):rep(#msg)) + util.printout() +end + +function util.this_program(default) + local i = 1 + local last, cur = default, default + while i do + local dbg = debug and debug.getinfo(i,"S") + if not dbg then break end + last = cur + cur = dbg.source + i=i+1 + end + local prog = last:sub(1,1) == "@" and last:sub(2) or last + + -- Check if we found the true path of a script that has a wrapper + local lrdir, binpath = prog:match("^(.*)/lib/luarocks/rocks%-[0-9.]*/[^/]+/[^/]+(/bin/[^/]+)$") + if lrdir then + -- Return the wrapper instead + return lrdir .. binpath + end + + return prog +end + +function util.format_rock_name(name, namespace, version) + return (namespace and namespace.."/" or "")..name..(version and " "..version or "") +end + +function util.deps_mode_option(parser, program) + local cfg = require("luarocks.core.cfg") + + parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n".. + "* all - use all trees from the rocks_trees list for finding dependencies\n".. + "* one - use only the current tree (possibly set with --tree)\n".. + "* order - use trees based on order (use the current tree and all ".. + "trees below it on the rocks_trees list)\n".. + "* none - ignore dependencies altogether.\n".. + "The default mode may be set with the deps_mode entry in the configuration file.\n".. + 'The current default is "'..cfg.deps_mode..'".\n'.. + "Type '"..util.this_program(program or "luarocks").."' with no ".. + "arguments to see your list of rocks trees.") + :argname("") + :choices({"all", "one", "order", "none"}) + parser:flag("--nodeps"):hidden(true) +end + +function util.see_help(command, program) + return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'." +end + +function util.see_also(text) + local see_also = "See also:\n" + if text then + see_also = see_also..text.."\n" + end + return see_also.." '"..util.this_program("luarocks").." help' for general options and configuration." +end + +function util.announce_install(rockspec) + local cfg = require("luarocks.core.cfg") + local path = require("luarocks.path") + + local suffix = "" + if rockspec.description and rockspec.description.license then + suffix = " (license: "..rockspec.description.license..")" + end + + util.printout(rockspec.name.." "..rockspec.version.." is now installed in "..path.root_dir(cfg.root_dir)..suffix) + util.printout() +end + +--- Collect rockspecs located in a subdirectory. +-- @param versions table: A table mapping rock names to newest rockspec versions. +-- @param paths table: A table mapping rock names to newest rockspec paths. +-- @param unnamed_paths table: An array of rockspec paths that don't contain rock +-- name and version in regular format. +-- @param subdir string: path to subdirectory. +local function collect_rockspecs(versions, paths, unnamed_paths, subdir) + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + local path = require("luarocks.path") + local vers = require("luarocks.core.vers") + + if fs.is_dir(subdir) then + for file in fs.dir(subdir) do + file = dir.path(subdir, file) + + if file:match("rockspec$") and fs.is_file(file) then + local rock, version = path.parse_name(file) + + if rock then + if not versions[rock] or vers.compare_versions(version, versions[rock]) then + versions[rock] = version + paths[rock] = file + end + else + table.insert(unnamed_paths, file) + end + end + end + end +end + +--- Get default rockspec name for commands that take optional rockspec name. +-- @return string or (nil, string): path to the rockspec or nil and error message. +function util.get_default_rockspec() + local versions, paths, unnamed_paths = {}, {}, {} + -- Look for rockspecs in some common locations. + collect_rockspecs(versions, paths, unnamed_paths, ".") + collect_rockspecs(versions, paths, unnamed_paths, "rockspec") + collect_rockspecs(versions, paths, unnamed_paths, "rockspecs") + + if #unnamed_paths > 0 then + -- There are rockspecs not following "name-version.rockspec" format. + -- More than one are ambiguous. + if #unnamed_paths > 1 then + return nil, "Please specify which rockspec file to use." + else + return unnamed_paths[1] + end + else + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + local basename = dir.base_name(fs.current_dir()) + + if paths[basename] then + return paths[basename] + end + + local rock = next(versions) + + if rock then + -- If there are rockspecs for multiple rocks it's ambiguous. + if next(versions, rock) then + return nil, "Please specify which rockspec file to use." + else + return paths[rock] + end + else + return nil, "Argument missing: please specify a rockspec to use on current directory." + end + end +end + +-- Quote Lua string, analogous to fs.Q. +-- @param s A string, such as "hello" +-- @return string: A quoted string, such as '"hello"' +function util.LQ(s) + return ("%q"):format(s) +end + +-- Split name and namespace of a package name. +-- @param ns_name a name that may be in "namespace/name" format +-- @return string, string? - name and optionally a namespace +function util.split_namespace(ns_name) + local p1, p2 = ns_name:match("^([^/]+)/([^/]+)$") + if p1 then + return p2, p1 + end + return ns_name +end + +--- Argparse action callback for namespaced rock arguments. +function util.namespaced_name_action(args, target, ns_name) + assert(type(args) == "table") + assert(type(target) == "string") + assert(type(ns_name) == "string" or not ns_name) + + if not ns_name then + return + end + + if ns_name:match("%.rockspec$") or ns_name:match("%.rock$") then + args[target] = ns_name + else + local name, namespace = util.split_namespace(ns_name) + args[target] = name:lower() + if namespace then + args.namespace = namespace:lower() + end + end +end + +function util.deep_copy(tbl) + local copy = {} + for k, v in pairs(tbl) do + if type(v) == "table" then + copy[k] = util.deep_copy(v) + else + copy[k] = v + end + end + return copy +end + +-- A portable version of fs.exists that can be used at early startup, +-- before the platform has been determined and luarocks.fs has been +-- initialized. +function util.exists(file) + local fd, _, code = io.open(file, "r") + if code == 13 then + -- code 13 means "Permission denied" on both Unix and Windows + -- io.open on folders always fails with code 13 on Windows + return true + end + if fd then + fd:close() + return true + end + return false +end + +do + local function Q(pathname) + if pathname:match("^.:") then + return pathname:sub(1, 2) .. '"' .. pathname:sub(3) .. '"' + end + return '"' .. pathname .. '"' + end + + function util.check_lua_version(lua, luaver) + if not util.exists(lua) then + return nil + end + local lv, err = util.popen_read(Q(lua) .. ' -e "io.write(_VERSION:sub(5))"') + if lv == "" then + return nil + end + if luaver and luaver ~= lv then + return nil + end + return lv + end + + function util.get_luajit_version() + local cfg = require("luarocks.core.cfg") + if cfg.cache.luajit_version_checked then + return cfg.cache.luajit_version + end + cfg.cache.luajit_version_checked = true + + if not cfg.variables.LUA then + return nil + end + + local ljv + if cfg.lua_version == "5.1" then + -- Ignores extra version info for custom builds, e.g. "LuaJIT 2.1.0-beta3 some-other-version-info" + ljv = util.popen_read(Q(cfg.variables.LUA) .. ' -e "io.write(tostring(jit and jit.version:gsub([[^%S+ (%S+).*]], [[%1]])))"') + if ljv == "nil" then + ljv = nil + end + end + cfg.cache.luajit_version = ljv + return ljv + end + + local find_lua_bindir + do + local exe_suffix = (package.config:sub(1, 1) == "\\" and ".exe" or "") + + local function insert_lua_variants(names, luaver) + local variants = { + "lua" .. luaver .. exe_suffix, + "lua" .. luaver:gsub("%.", "") .. exe_suffix, + "lua-" .. luaver .. exe_suffix, + "lua-" .. luaver:gsub("%.", "") .. exe_suffix, + } + for _, name in ipairs(variants) do + names[name] = luaver + table.insert(names, name) + end + end + + find_lua_bindir = function(prefix, luaver, verbose) + local names = {} + if luaver then + insert_lua_variants(names, luaver) + else + for v in util.lua_versions("descending") do + insert_lua_variants(names, v) + end + end + if luaver == "5.1" or not luaver then + table.insert(names, "luajit" .. exe_suffix) + end + table.insert(names, "lua" .. exe_suffix) + + local tried = {} + local dir_sep = package.config:sub(1, 1) + for _, d in ipairs({ prefix .. dir_sep .. "bin", prefix }) do + for _, name in ipairs(names) do + local lua = d .. dir_sep .. name + local is_wrapper, err = util.lua_is_wrapper(lua) + if is_wrapper == false then + local lv = util.check_lua_version(lua, luaver) + if lv then + return lua, d, lv + end + elseif is_wrapper == true or err == nil then + table.insert(tried, lua) + else + table.insert(tried, string.format("%-13s (%s)", lua, err)) + end + end + end + local interp = luaver + and ("Lua " .. luaver .. " interpreter") + or "Lua interpreter" + return nil, interp .. " not found at " .. prefix .. "\n" .. + (verbose and "Tried:\t" .. table.concat(tried, "\n\t") or "") + end + end + + function util.find_lua(prefix, luaver, verbose) + local lua, bindir + lua, bindir, luaver = find_lua_bindir(prefix, luaver, verbose) + if not lua then + return nil, bindir + end + + return { + lua_version = luaver, + lua = lua, + lua_dir = prefix, + lua_bindir = bindir, + } + end +end + +function util.lua_is_wrapper(interp) + local fd, err = io.open(interp, "r") + if not fd then + return nil, err + end + local data, err = fd:read(1000) + fd:close() + if not data then + return nil, err + end + return not not data:match("LUAROCKS_SYSCONFDIR") +end + +function util.opts_table(type_name, valid_opts) + local opts_mt = {} + + opts_mt.__index = opts_mt + + function opts_mt.type() + return type_name + end + + return function(opts) + for k, v in pairs(opts) do + local tv = type(v) + if not valid_opts[k] then + error("invalid option: "..k) + end + local vo, optional = valid_opts[k]:match("^(.-)(%??)$") + if not (tv == vo or (optional == "?" and tv == nil)) then + error("invalid type option: "..k.." - got "..tv..", expected "..vo) + end + end + for k, v in pairs(valid_opts) do + if (not v:find("?", 1, true)) and opts[k] == nil then + error("missing option: "..k) + end + end + return setmetatable(opts, opts_mt) + end +end + +--- Return a table of modules that are already provided by the VM, which +-- can be specified as dependencies without having to install an actual rock. +-- @param rockspec (optional) a rockspec table, so that rockspec format +-- version compatibility can be checked. If not given, maximum compatibility +-- is assumed. +-- @return a table with rock names as keys and versions and values, +-- specifying modules that are already provided by the VM (including +-- "lua" for the Lua version and, for format 3.0+, "luajit" if detected). +function util.get_rocks_provided(rockspec) + local cfg = require("luarocks.core.cfg") + + if not rockspec and cfg.cache.rocks_provided then + return cfg.cache.rocks_provided + end + + local rocks_provided = {} + + local lv = cfg.lua_version + + rocks_provided["lua"] = lv.."-1" + + if lv == "5.2" then + rocks_provided["bit32"] = lv.."-1" + end + + if lv == "5.3" or lv == "5.4" then + rocks_provided["utf8"] = lv.."-1" + end + + if lv == "5.1" then + local ljv = util.get_luajit_version() + if ljv then + rocks_provided["luabitop"] = ljv.."-1" + if (not rockspec) or rockspec:format_is_at_least("3.0") then + rocks_provided["luajit"] = ljv.."-1" + end + end + end + + if cfg.rocks_provided then + util.deep_merge_under(rocks_provided, cfg.rocks_provided) + end + + if not rockspec then + cfg.cache.rocks_provided = rocks_provided + end + + return rocks_provided +end + +function util.remove_doc_dir(name, version) + local path = require("luarocks.path") + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + + local install_dir = path.install_dir(name, version) + for _, f in ipairs(fs.list_dir(install_dir)) do + local doc_dirs = { "doc", "docs" } + for _, d in ipairs(doc_dirs) do + if f == d then + fs.delete(dir.path(install_dir, f)) + end + end + end +end + +return util diff --git a/src/luarocks/util.lua b/src/luarocks/util.lua deleted file mode 100644 index de9157fc..00000000 --- a/src/luarocks/util.lua +++ /dev/null @@ -1,634 +0,0 @@ - ---- Assorted utilities for managing tables, plus a scheduler for rollback functions. --- Does not requires modules directly (only as locals --- inside specific functions) to avoid interdependencies, --- as this is used in the bootstrapping stage of luarocks.core.cfg. - -local util = {} - -local core = require("luarocks.core.util") - -util.cleanup_path = core.cleanup_path -util.split_string = core.split_string -util.sortedpairs = core.sortedpairs -util.deep_merge = core.deep_merge -util.deep_merge_under = core.deep_merge_under -util.popen_read = core.popen_read -util.show_table = core.show_table -util.printerr = core.printerr -util.warning = core.warning -util.keys = core.keys - -local unpack = unpack or table.unpack -local pack = table.pack or function(...) return { n = select("#", ...), ... } end - -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 util.schedule_function(f, ...) - assert(type(f) == "function") - - local item = { fn = f, args = pack(...) } - table.insert(scheduled_functions, item) - return item -end - ---- Unschedule a function. --- This is useful for cancelling a rollback of a completed operation. --- @param item table: The token representing the scheduled function that was --- returned from the schedule_function call. -function util.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 util.run_scheduled_functions() - local fs = require("luarocks.fs") - if fs.change_dir_to_root then - fs.change_dir_to_root() - end - for i = #scheduled_functions, 1, -1 do - local item = scheduled_functions[i] - item.fn(unpack(item.args, 1, item.args.n)) - end -end - ---- Produce a Lua pattern that matches precisely the given string --- (this is suitable to be concatenating to other patterns, --- so it does not include beginning- and end-of-string markers (^$) --- @param s string: The input string --- @return string: The equivalent pattern -function util.matchquote(s) - return (s:gsub("[?%-+*%[%].%%()$^]","%%%1")) -end - -local var_format_pattern = "%$%((%a[%a%d_]+)%)" - --- Check if a set of needed variables are referenced --- somewhere 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 util.warn_if_not_used(var_defs, needed_set, msg) - local seen = {} - for _, val in pairs(var_defs) do - for used in val:gmatch(var_format_pattern) do - seen[used] = true - end - end - for var, _ in pairs(needed_set) do - if not seen[var] then - util.warning(msg:format(var)) - end - 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 - util.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 util.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 - -function util.lua_versions(sort) - local versions = { "5.1", "5.2", "5.3", "5.4" } - local i = 0 - if sort == "descending" then - i = #versions + 1 - return function() - i = i - 1 - return versions[i] - end - else - return function() - i = i + 1 - return versions[i] - end - end -end - -function util.lua_path_variables() - local cfg = require("luarocks.core.cfg") - local lpath_var = "LUA_PATH" - local lcpath_var = "LUA_CPATH" - - local lv = cfg.lua_version:gsub("%.", "_") - if lv ~= "5_1" then - if os.getenv("LUA_PATH_" .. lv) then - lpath_var = "LUA_PATH_" .. lv - end - if os.getenv("LUA_CPATH_" .. lv) then - lcpath_var = "LUA_CPATH_" .. lv - end - end - return lpath_var, lcpath_var -end - -function util.starts_with(s, prefix) - return s:sub(1,#prefix) == prefix -end - ---- Print a line to standard output -function util.printout(...) - io.stdout:write(table.concat({...},"\t")) - io.stdout:write("\n") -end - -function util.title(msg, porcelain, underline) - if porcelain then return end - util.printout() - util.printout(msg) - util.printout((underline or "-"):rep(#msg)) - util.printout() -end - -function util.this_program(default) - local i = 1 - local last, cur = default, default - while i do - local dbg = debug and debug.getinfo(i,"S") - if not dbg then break end - last = cur - cur = dbg.source - i=i+1 - end - local prog = last:sub(1,1) == "@" and last:sub(2) or last - - -- Check if we found the true path of a script that has a wrapper - local lrdir, binpath = prog:match("^(.*)/lib/luarocks/rocks%-[0-9.]*/[^/]+/[^/]+(/bin/[^/]+)$") - if lrdir then - -- Return the wrapper instead - return lrdir .. binpath - end - - return prog -end - -function util.format_rock_name(name, namespace, version) - return (namespace and namespace.."/" or "")..name..(version and " "..version or "") -end - -function util.deps_mode_option(parser, program) - local cfg = require("luarocks.core.cfg") - - parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n".. - "* all - use all trees from the rocks_trees list for finding dependencies\n".. - "* one - use only the current tree (possibly set with --tree)\n".. - "* order - use trees based on order (use the current tree and all ".. - "trees below it on the rocks_trees list)\n".. - "* none - ignore dependencies altogether.\n".. - "The default mode may be set with the deps_mode entry in the configuration file.\n".. - 'The current default is "'..cfg.deps_mode..'".\n'.. - "Type '"..util.this_program(program or "luarocks").."' with no ".. - "arguments to see your list of rocks trees.") - :argname("") - :choices({"all", "one", "order", "none"}) - parser:flag("--nodeps"):hidden(true) -end - -function util.see_help(command, program) - return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'." -end - -function util.see_also(text) - local see_also = "See also:\n" - if text then - see_also = see_also..text.."\n" - end - return see_also.." '"..util.this_program("luarocks").." help' for general options and configuration." -end - -function util.announce_install(rockspec) - local cfg = require("luarocks.core.cfg") - local path = require("luarocks.path") - - local suffix = "" - if rockspec.description and rockspec.description.license then - suffix = " (license: "..rockspec.description.license..")" - end - - util.printout(rockspec.name.." "..rockspec.version.." is now installed in "..path.root_dir(cfg.root_dir)..suffix) - util.printout() -end - ---- Collect rockspecs located in a subdirectory. --- @param versions table: A table mapping rock names to newest rockspec versions. --- @param paths table: A table mapping rock names to newest rockspec paths. --- @param unnamed_paths table: An array of rockspec paths that don't contain rock --- name and version in regular format. --- @param subdir string: path to subdirectory. -local function collect_rockspecs(versions, paths, unnamed_paths, subdir) - local fs = require("luarocks.fs") - local dir = require("luarocks.dir") - local path = require("luarocks.path") - local vers = require("luarocks.core.vers") - - if fs.is_dir(subdir) then - for file in fs.dir(subdir) do - file = dir.path(subdir, file) - - if file:match("rockspec$") and fs.is_file(file) then - local rock, version = path.parse_name(file) - - if rock then - if not versions[rock] or vers.compare_versions(version, versions[rock]) then - versions[rock] = version - paths[rock] = file - end - else - table.insert(unnamed_paths, file) - end - end - end - end -end - ---- Get default rockspec name for commands that take optional rockspec name. --- @return string or (nil, string): path to the rockspec or nil and error message. -function util.get_default_rockspec() - local versions, paths, unnamed_paths = {}, {}, {} - -- Look for rockspecs in some common locations. - collect_rockspecs(versions, paths, unnamed_paths, ".") - collect_rockspecs(versions, paths, unnamed_paths, "rockspec") - collect_rockspecs(versions, paths, unnamed_paths, "rockspecs") - - if #unnamed_paths > 0 then - -- There are rockspecs not following "name-version.rockspec" format. - -- More than one are ambiguous. - if #unnamed_paths > 1 then - return nil, "Please specify which rockspec file to use." - else - return unnamed_paths[1] - end - else - local fs = require("luarocks.fs") - local dir = require("luarocks.dir") - local basename = dir.base_name(fs.current_dir()) - - if paths[basename] then - return paths[basename] - end - - local rock = next(versions) - - if rock then - -- If there are rockspecs for multiple rocks it's ambiguous. - if next(versions, rock) then - return nil, "Please specify which rockspec file to use." - else - return paths[rock] - end - else - return nil, "Argument missing: please specify a rockspec to use on current directory." - end - end -end - --- Quote Lua string, analogous to fs.Q. --- @param s A string, such as "hello" --- @return string: A quoted string, such as '"hello"' -function util.LQ(s) - return ("%q"):format(s) -end - --- Split name and namespace of a package name. --- @param ns_name a name that may be in "namespace/name" format --- @return string, string? - name and optionally a namespace -function util.split_namespace(ns_name) - local p1, p2 = ns_name:match("^([^/]+)/([^/]+)$") - if p1 then - return p2, p1 - end - return ns_name -end - ---- Argparse action callback for namespaced rock arguments. -function util.namespaced_name_action(args, target, ns_name) - assert(type(args) == "table") - assert(type(target) == "string") - assert(type(ns_name) == "string" or not ns_name) - - if not ns_name then - return - end - - if ns_name:match("%.rockspec$") or ns_name:match("%.rock$") then - args[target] = ns_name - else - local name, namespace = util.split_namespace(ns_name) - args[target] = name:lower() - if namespace then - args.namespace = namespace:lower() - end - end -end - -function util.deep_copy(tbl) - local copy = {} - for k, v in pairs(tbl) do - if type(v) == "table" then - copy[k] = util.deep_copy(v) - else - copy[k] = v - end - end - return copy -end - --- A portable version of fs.exists that can be used at early startup, --- before the platform has been determined and luarocks.fs has been --- initialized. -function util.exists(file) - local fd, _, code = io.open(file, "r") - if code == 13 then - -- code 13 means "Permission denied" on both Unix and Windows - -- io.open on folders always fails with code 13 on Windows - return true - end - if fd then - fd:close() - return true - end - return false -end - -do - local function Q(pathname) - if pathname:match("^.:") then - return pathname:sub(1, 2) .. '"' .. pathname:sub(3) .. '"' - end - return '"' .. pathname .. '"' - end - - function util.check_lua_version(lua, luaver) - if not util.exists(lua) then - return nil - end - local lv, err = util.popen_read(Q(lua) .. ' -e "io.write(_VERSION:sub(5))"') - if lv == "" then - return nil - end - if luaver and luaver ~= lv then - return nil - end - return lv - end - - function util.get_luajit_version() - local cfg = require("luarocks.core.cfg") - if cfg.cache.luajit_version_checked then - return cfg.cache.luajit_version - end - cfg.cache.luajit_version_checked = true - - if not cfg.variables.LUA then - return nil - end - - local ljv - if cfg.lua_version == "5.1" then - -- Ignores extra version info for custom builds, e.g. "LuaJIT 2.1.0-beta3 some-other-version-info" - ljv = util.popen_read(Q(cfg.variables.LUA) .. ' -e "io.write(tostring(jit and jit.version:gsub([[^%S+ (%S+).*]], [[%1]])))"') - if ljv == "nil" then - ljv = nil - end - end - cfg.cache.luajit_version = ljv - return ljv - end - - local find_lua_bindir - do - local exe_suffix = (package.config:sub(1, 1) == "\\" and ".exe" or "") - - local function insert_lua_variants(names, luaver) - local variants = { - "lua" .. luaver .. exe_suffix, - "lua" .. luaver:gsub("%.", "") .. exe_suffix, - "lua-" .. luaver .. exe_suffix, - "lua-" .. luaver:gsub("%.", "") .. exe_suffix, - } - for _, name in ipairs(variants) do - names[name] = luaver - table.insert(names, name) - end - end - - find_lua_bindir = function(prefix, luaver, verbose) - local names = {} - if luaver then - insert_lua_variants(names, luaver) - else - for v in util.lua_versions("descending") do - insert_lua_variants(names, v) - end - end - if luaver == "5.1" or not luaver then - table.insert(names, "luajit" .. exe_suffix) - end - table.insert(names, "lua" .. exe_suffix) - - local tried = {} - local dir_sep = package.config:sub(1, 1) - for _, d in ipairs({ prefix .. dir_sep .. "bin", prefix }) do - for _, name in ipairs(names) do - local lua = d .. dir_sep .. name - local is_wrapper, err = util.lua_is_wrapper(lua) - if is_wrapper == false then - local lv = util.check_lua_version(lua, luaver) - if lv then - return lua, d, lv - end - elseif is_wrapper == true or err == nil then - table.insert(tried, lua) - else - table.insert(tried, string.format("%-13s (%s)", lua, err)) - end - end - end - local interp = luaver - and ("Lua " .. luaver .. " interpreter") - or "Lua interpreter" - return nil, interp .. " not found at " .. prefix .. "\n" .. - (verbose and "Tried:\t" .. table.concat(tried, "\n\t") or "") - end - end - - function util.find_lua(prefix, luaver, verbose) - local lua, bindir - lua, bindir, luaver = find_lua_bindir(prefix, luaver, verbose) - if not lua then - return nil, bindir - end - - return { - lua_version = luaver, - lua = lua, - lua_dir = prefix, - lua_bindir = bindir, - } - end -end - -function util.lua_is_wrapper(interp) - local fd, err = io.open(interp, "r") - if not fd then - return nil, err - end - local data, err = fd:read(1000) - fd:close() - if not data then - return nil, err - end - return not not data:match("LUAROCKS_SYSCONFDIR") -end - -function util.opts_table(type_name, valid_opts) - local opts_mt = {} - - opts_mt.__index = opts_mt - - function opts_mt.type() - return type_name - end - - return function(opts) - for k, v in pairs(opts) do - local tv = type(v) - if not valid_opts[k] then - error("invalid option: "..k) - end - local vo, optional = valid_opts[k]:match("^(.-)(%??)$") - if not (tv == vo or (optional == "?" and tv == nil)) then - error("invalid type option: "..k.." - got "..tv..", expected "..vo) - end - end - for k, v in pairs(valid_opts) do - if (not v:find("?", 1, true)) and opts[k] == nil then - error("missing option: "..k) - end - end - return setmetatable(opts, opts_mt) - end -end - ---- Return a table of modules that are already provided by the VM, which --- can be specified as dependencies without having to install an actual rock. --- @param rockspec (optional) a rockspec table, so that rockspec format --- version compatibility can be checked. If not given, maximum compatibility --- is assumed. --- @return a table with rock names as keys and versions and values, --- specifying modules that are already provided by the VM (including --- "lua" for the Lua version and, for format 3.0+, "luajit" if detected). -function util.get_rocks_provided(rockspec) - local cfg = require("luarocks.core.cfg") - - if not rockspec and cfg.cache.rocks_provided then - return cfg.cache.rocks_provided - end - - local rocks_provided = {} - - local lv = cfg.lua_version - - rocks_provided["lua"] = lv.."-1" - - if lv == "5.2" then - rocks_provided["bit32"] = lv.."-1" - end - - if lv == "5.3" or lv == "5.4" then - rocks_provided["utf8"] = lv.."-1" - end - - if lv == "5.1" then - local ljv = util.get_luajit_version() - if ljv then - rocks_provided["luabitop"] = ljv.."-1" - if (not rockspec) or rockspec:format_is_at_least("3.0") then - rocks_provided["luajit"] = ljv.."-1" - end - end - end - - if cfg.rocks_provided then - util.deep_merge_under(rocks_provided, cfg.rocks_provided) - end - - if not rockspec then - cfg.cache.rocks_provided = rocks_provided - end - - return rocks_provided -end - -function util.remove_doc_dir(name, version) - local path = require("luarocks.path") - local fs = require("luarocks.fs") - local dir = require("luarocks.dir") - - local install_dir = path.install_dir(name, version) - for _, f in ipairs(fs.list_dir(install_dir)) do - local doc_dirs = { "doc", "docs" } - for _, d in ipairs(doc_dirs) do - if f == d then - fs.delete(dir.path(install_dir, f)) - end - end - end -end - -return util diff --git a/src/luarocks/util.tl b/src/luarocks/util.tl new file mode 100644 index 00000000..41855aae --- /dev/null +++ b/src/luarocks/util.tl @@ -0,0 +1,635 @@ + +--- Assorted utilities for managing tables, plus a scheduler for rollback functions. +-- Does not requires modules directly (only as locals +-- inside specific functions) to avoid interdependencies, +-- as this is used in the bootstrapping stage of luarocks.core.cfg. + +local record util +end + +local core = require("luarocks.core.util") + +util.cleanup_path = core.cleanup_path --! +util.split_string = core.split_string +util.sortedpairs = core.sortedpairs +util.deep_merge = core.deep_merge +util.deep_merge_under = core.deep_merge_under +util.popen_read = core.popen_read +util.show_table = core.show_table +util.printerr = core.printerr +util.warning = core.warning +util.keys = core.keys + +local unpack = unpack or table.unpack --! +local pack = table.pack or function(...) return { n = select("#", ...), ... } end --! + +local scheduled_functions: {integer: {string: any}} = {} --? infered from line 48-51 + +--- 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, --? table -> map +-- which can be used to remove the item later from the list. +function util.schedule_function(f: function(), ...:any): {string:any} + assert(type(f) == "function") + + local item = { fn = f, args = pack(...) } + table.insert(scheduled_functions, item) + return item +end + +--- Unschedule a function. +-- This is useful for cancelling a rollback of a completed operation. +-- @param item table: The token representing the scheduled function that was --? table -> map +-- returned from the schedule_function call. +function util.remove_scheduled_function(item: {string: any}) + 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 util.run_scheduled_functions() + local fs = require("luarocks.fs") + if fs.change_dir_to_root then + fs.change_dir_to_root() + end + for i = #scheduled_functions, 1, -1 do + local item = scheduled_functions[i] + item.fn(unpack(item.args, 1, item.args.n)) + end +end + +--- Produce a Lua pattern that matches precisely the given string +-- (this is suitable to be concatenating to other patterns, +-- so it does not include beginning- and end-of-string markers (^$) +-- @param s string: The input string +-- @return string: The equivalent pattern +function util.matchquote(s) + return (s:gsub("[?%-+*%[%].%%()$^]","%%%1")) +end + +local var_format_pattern = "%$%((%a[%a%d_]+)%)" + +-- Check if a set of needed variables are referenced +-- somewhere 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 util.warn_if_not_used(var_defs, needed_set, msg) + local seen = {} + for _, val in pairs(var_defs) do + for used in val:gmatch(var_format_pattern) do + seen[used] = true + end + end + for var, _ in pairs(needed_set) do + if not seen[var] then + util.warning(msg:format(var)) + end + 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 + util.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 util.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 + +function util.lua_versions(sort) + local versions = { "5.1", "5.2", "5.3", "5.4" } + local i = 0 + if sort == "descending" then + i = #versions + 1 + return function() + i = i - 1 + return versions[i] + end + else + return function() + i = i + 1 + return versions[i] + end + end +end + +function util.lua_path_variables() + local cfg = require("luarocks.core.cfg") + local lpath_var = "LUA_PATH" + local lcpath_var = "LUA_CPATH" + + local lv = cfg.lua_version:gsub("%.", "_") + if lv ~= "5_1" then + if os.getenv("LUA_PATH_" .. lv) then + lpath_var = "LUA_PATH_" .. lv + end + if os.getenv("LUA_CPATH_" .. lv) then + lcpath_var = "LUA_CPATH_" .. lv + end + end + return lpath_var, lcpath_var +end + +function util.starts_with(s, prefix) + return s:sub(1,#prefix) == prefix +end + +--- Print a line to standard output +function util.printout(...) + io.stdout:write(table.concat({...},"\t")) + io.stdout:write("\n") +end + +function util.title(msg, porcelain, underline) + if porcelain then return end + util.printout() + util.printout(msg) + util.printout((underline or "-"):rep(#msg)) + util.printout() +end + +function util.this_program(default) + local i = 1 + local last, cur = default, default + while i do + local dbg = debug and debug.getinfo(i,"S") + if not dbg then break end + last = cur + cur = dbg.source + i=i+1 + end + local prog = last:sub(1,1) == "@" and last:sub(2) or last + + -- Check if we found the true path of a script that has a wrapper + local lrdir, binpath = prog:match("^(.*)/lib/luarocks/rocks%-[0-9.]*/[^/]+/[^/]+(/bin/[^/]+)$") + if lrdir then + -- Return the wrapper instead + return lrdir .. binpath + end + + return prog +end + +function util.format_rock_name(name, namespace, version) + return (namespace and namespace.."/" or "")..name..(version and " "..version or "") +end + +function util.deps_mode_option(parser, program) + local cfg = require("luarocks.core.cfg") + + parser:option("--deps-mode", "How to handle dependencies. Four modes are supported:\n".. + "* all - use all trees from the rocks_trees list for finding dependencies\n".. + "* one - use only the current tree (possibly set with --tree)\n".. + "* order - use trees based on order (use the current tree and all ".. + "trees below it on the rocks_trees list)\n".. + "* none - ignore dependencies altogether.\n".. + "The default mode may be set with the deps_mode entry in the configuration file.\n".. + 'The current default is "'..cfg.deps_mode..'".\n'.. + "Type '"..util.this_program(program or "luarocks").."' with no ".. + "arguments to see your list of rocks trees.") + :argname("") + :choices({"all", "one", "order", "none"}) + parser:flag("--nodeps"):hidden(true) +end + +function util.see_help(command, program) + return "See '"..util.this_program(program or "luarocks")..' help'..(command and " "..command or "").."'." +end + +function util.see_also(text) + local see_also = "See also:\n" + if text then + see_also = see_also..text.."\n" + end + return see_also.." '"..util.this_program("luarocks").." help' for general options and configuration." +end + +function util.announce_install(rockspec) + local cfg = require("luarocks.core.cfg") + local path = require("luarocks.path") + + local suffix = "" + if rockspec.description and rockspec.description.license then + suffix = " (license: "..rockspec.description.license..")" + end + + util.printout(rockspec.name.." "..rockspec.version.." is now installed in "..path.root_dir(cfg.root_dir)..suffix) + util.printout() +end + +--- Collect rockspecs located in a subdirectory. +-- @param versions table: A table mapping rock names to newest rockspec versions. +-- @param paths table: A table mapping rock names to newest rockspec paths. +-- @param unnamed_paths table: An array of rockspec paths that don't contain rock +-- name and version in regular format. +-- @param subdir string: path to subdirectory. +local function collect_rockspecs(versions, paths, unnamed_paths, subdir) + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + local path = require("luarocks.path") + local vers = require("luarocks.core.vers") + + if fs.is_dir(subdir) then + for file in fs.dir(subdir) do + file = dir.path(subdir, file) + + if file:match("rockspec$") and fs.is_file(file) then + local rock, version = path.parse_name(file) + + if rock then + if not versions[rock] or vers.compare_versions(version, versions[rock]) then + versions[rock] = version + paths[rock] = file + end + else + table.insert(unnamed_paths, file) + end + end + end + end +end + +--- Get default rockspec name for commands that take optional rockspec name. +-- @return string or (nil, string): path to the rockspec or nil and error message. +function util.get_default_rockspec() + local versions, paths, unnamed_paths = {}, {}, {} + -- Look for rockspecs in some common locations. + collect_rockspecs(versions, paths, unnamed_paths, ".") + collect_rockspecs(versions, paths, unnamed_paths, "rockspec") + collect_rockspecs(versions, paths, unnamed_paths, "rockspecs") + + if #unnamed_paths > 0 then + -- There are rockspecs not following "name-version.rockspec" format. + -- More than one are ambiguous. + if #unnamed_paths > 1 then + return nil, "Please specify which rockspec file to use." + else + return unnamed_paths[1] + end + else + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + local basename = dir.base_name(fs.current_dir()) + + if paths[basename] then + return paths[basename] + end + + local rock = next(versions) + + if rock then + -- If there are rockspecs for multiple rocks it's ambiguous. + if next(versions, rock) then + return nil, "Please specify which rockspec file to use." + else + return paths[rock] + end + else + return nil, "Argument missing: please specify a rockspec to use on current directory." + end + end +end + +-- Quote Lua string, analogous to fs.Q. +-- @param s A string, such as "hello" +-- @return string: A quoted string, such as '"hello"' +function util.LQ(s) + return ("%q"):format(s) +end + +-- Split name and namespace of a package name. +-- @param ns_name a name that may be in "namespace/name" format +-- @return string, string? - name and optionally a namespace +function util.split_namespace(ns_name) + local p1, p2 = ns_name:match("^([^/]+)/([^/]+)$") + if p1 then + return p2, p1 + end + return ns_name +end + +--- Argparse action callback for namespaced rock arguments. +function util.namespaced_name_action(args, target, ns_name) + assert(type(args) == "table") + assert(type(target) == "string") + assert(type(ns_name) == "string" or not ns_name) + + if not ns_name then + return + end + + if ns_name:match("%.rockspec$") or ns_name:match("%.rock$") then + args[target] = ns_name + else + local name, namespace = util.split_namespace(ns_name) + args[target] = name:lower() + if namespace then + args.namespace = namespace:lower() + end + end +end + +function util.deep_copy(tbl) + local copy = {} + for k, v in pairs(tbl) do + if type(v) == "table" then + copy[k] = util.deep_copy(v) + else + copy[k] = v + end + end + return copy +end + +-- A portable version of fs.exists that can be used at early startup, +-- before the platform has been determined and luarocks.fs has been +-- initialized. +function util.exists(file) + local fd, _, code = io.open(file, "r") + if code == 13 then + -- code 13 means "Permission denied" on both Unix and Windows + -- io.open on folders always fails with code 13 on Windows + return true + end + if fd then + fd:close() + return true + end + return false +end + +do + local function Q(pathname) + if pathname:match("^.:") then + return pathname:sub(1, 2) .. '"' .. pathname:sub(3) .. '"' + end + return '"' .. pathname .. '"' + end + + function util.check_lua_version(lua, luaver) + if not util.exists(lua) then + return nil + end + local lv, err = util.popen_read(Q(lua) .. ' -e "io.write(_VERSION:sub(5))"') + if lv == "" then + return nil + end + if luaver and luaver ~= lv then + return nil + end + return lv + end + + function util.get_luajit_version() + local cfg = require("luarocks.core.cfg") + if cfg.cache.luajit_version_checked then + return cfg.cache.luajit_version + end + cfg.cache.luajit_version_checked = true + + if not cfg.variables.LUA then + return nil + end + + local ljv + if cfg.lua_version == "5.1" then + -- Ignores extra version info for custom builds, e.g. "LuaJIT 2.1.0-beta3 some-other-version-info" + ljv = util.popen_read(Q(cfg.variables.LUA) .. ' -e "io.write(tostring(jit and jit.version:gsub([[^%S+ (%S+).*]], [[%1]])))"') + if ljv == "nil" then + ljv = nil + end + end + cfg.cache.luajit_version = ljv + return ljv + end + + local find_lua_bindir + do + local exe_suffix = (package.config:sub(1, 1) == "\\" and ".exe" or "") + + local function insert_lua_variants(names, luaver) + local variants = { + "lua" .. luaver .. exe_suffix, + "lua" .. luaver:gsub("%.", "") .. exe_suffix, + "lua-" .. luaver .. exe_suffix, + "lua-" .. luaver:gsub("%.", "") .. exe_suffix, + } + for _, name in ipairs(variants) do + names[name] = luaver + table.insert(names, name) + end + end + + find_lua_bindir = function(prefix, luaver, verbose) + local names = {} + if luaver then + insert_lua_variants(names, luaver) + else + for v in util.lua_versions("descending") do + insert_lua_variants(names, v) + end + end + if luaver == "5.1" or not luaver then + table.insert(names, "luajit" .. exe_suffix) + end + table.insert(names, "lua" .. exe_suffix) + + local tried = {} + local dir_sep = package.config:sub(1, 1) + for _, d in ipairs({ prefix .. dir_sep .. "bin", prefix }) do + for _, name in ipairs(names) do + local lua = d .. dir_sep .. name + local is_wrapper, err = util.lua_is_wrapper(lua) + if is_wrapper == false then + local lv = util.check_lua_version(lua, luaver) + if lv then + return lua, d, lv + end + elseif is_wrapper == true or err == nil then + table.insert(tried, lua) + else + table.insert(tried, string.format("%-13s (%s)", lua, err)) + end + end + end + local interp = luaver + and ("Lua " .. luaver .. " interpreter") + or "Lua interpreter" + return nil, interp .. " not found at " .. prefix .. "\n" .. + (verbose and "Tried:\t" .. table.concat(tried, "\n\t") or "") + end + end + + function util.find_lua(prefix, luaver, verbose) + local lua, bindir + lua, bindir, luaver = find_lua_bindir(prefix, luaver, verbose) + if not lua then + return nil, bindir + end + + return { + lua_version = luaver, + lua = lua, + lua_dir = prefix, + lua_bindir = bindir, + } + end +end + +function util.lua_is_wrapper(interp) + local fd, err = io.open(interp, "r") + if not fd then + return nil, err + end + local data, err = fd:read(1000) + fd:close() + if not data then + return nil, err + end + return not not data:match("LUAROCKS_SYSCONFDIR") +end + +function util.opts_table(type_name, valid_opts) + local opts_mt = {} + + opts_mt.__index = opts_mt + + function opts_mt.type() + return type_name + end + + return function(opts) + for k, v in pairs(opts) do + local tv = type(v) + if not valid_opts[k] then + error("invalid option: "..k) + end + local vo, optional = valid_opts[k]:match("^(.-)(%??)$") + if not (tv == vo or (optional == "?" and tv == nil)) then + error("invalid type option: "..k.." - got "..tv..", expected "..vo) + end + end + for k, v in pairs(valid_opts) do + if (not v:find("?", 1, true)) and opts[k] == nil then + error("missing option: "..k) + end + end + return setmetatable(opts, opts_mt) + end +end + +--- Return a table of modules that are already provided by the VM, which +-- can be specified as dependencies without having to install an actual rock. +-- @param rockspec (optional) a rockspec table, so that rockspec format +-- version compatibility can be checked. If not given, maximum compatibility +-- is assumed. +-- @return a table with rock names as keys and versions and values, +-- specifying modules that are already provided by the VM (including +-- "lua" for the Lua version and, for format 3.0+, "luajit" if detected). +function util.get_rocks_provided(rockspec) + local cfg = require("luarocks.core.cfg") + + if not rockspec and cfg.cache.rocks_provided then + return cfg.cache.rocks_provided + end + + local rocks_provided = {} + + local lv = cfg.lua_version + + rocks_provided["lua"] = lv.."-1" + + if lv == "5.2" then + rocks_provided["bit32"] = lv.."-1" + end + + if lv == "5.3" or lv == "5.4" then + rocks_provided["utf8"] = lv.."-1" + end + + if lv == "5.1" then + local ljv = util.get_luajit_version() + if ljv then + rocks_provided["luabitop"] = ljv.."-1" + if (not rockspec) or rockspec:format_is_at_least("3.0") then + rocks_provided["luajit"] = ljv.."-1" + end + end + end + + if cfg.rocks_provided then + util.deep_merge_under(rocks_provided, cfg.rocks_provided) + end + + if not rockspec then + cfg.cache.rocks_provided = rocks_provided + end + + return rocks_provided +end + +function util.remove_doc_dir(name, version) + local path = require("luarocks.path") + local fs = require("luarocks.fs") + local dir = require("luarocks.dir") + + local install_dir = path.install_dir(name, version) + for _, f in ipairs(fs.list_dir(install_dir)) do + local doc_dirs = { "doc", "docs" } + for _, d in ipairs(doc_dirs) do + if f == d then + fs.delete(dir.path(install_dir, f)) + end + end + end +end + +return util -- cgit v1.2.3-55-g6feb