387 lines
14 KiB
Lua
387 lines
14 KiB
Lua
|
-- System functions
|
||
|
|
||
|
module ("dist.sys", package.seeall)
|
||
|
|
||
|
local cfg = require "dist.config"
|
||
|
local utils = require "dist.utils"
|
||
|
local lfs = require "lfs"
|
||
|
|
||
|
-- Return the path separator according to the platform.
|
||
|
function path_separator()
|
||
|
if cfg.arch == "Windows" then
|
||
|
return "\\"
|
||
|
else
|
||
|
return "/"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Return path with wrong separators replaced with the right ones.
|
||
|
function check_separators(path)
|
||
|
assert(type(path) == "string", "sys.check_separators: Argument 'path' is not a string.")
|
||
|
if cfg.arch == "Windows" then
|
||
|
return path:gsub("/", "\\")
|
||
|
else
|
||
|
return path
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Return the path with the unnecessary trailing separator removed.
|
||
|
function remove_trailing(path)
|
||
|
assert(type(path) == "string", "sys.remove_trailing: Argument 'path' is not a string.")
|
||
|
if path:sub(-1) == path_separator() and not is_root(path) then path = path:sub(1,-2) end
|
||
|
return path
|
||
|
end
|
||
|
|
||
|
-- Return the path with the all occurences of '/.' or '\.' (representing
|
||
|
-- the current directory) removed.
|
||
|
function remove_curr_dir_dots(path)
|
||
|
assert(type(path) == "string", "sys.remove_curr_dir_dots: Argument 'path' is not a string.")
|
||
|
while path:match(path_separator() .. "%." .. path_separator()) do -- match("/%./")
|
||
|
path = path:gsub(path_separator() .. "%." .. path_separator(), path_separator()) -- gsub("/%./", "/")
|
||
|
end
|
||
|
return path:gsub(path_separator() .. "%.$", "") -- gsub("/%.$", "")
|
||
|
end
|
||
|
|
||
|
-- Return string argument quoted for a command line usage.
|
||
|
function quote(argument)
|
||
|
assert(type(argument) == "string", "sys.quote: Argument 'argument' is not a string.")
|
||
|
|
||
|
-- TODO: This seems like a not very nice hack. Why is it needed?
|
||
|
-- Wouldn't it be better to fix the problem where it originates?
|
||
|
-- replace '/' path separators for '\' on Windows
|
||
|
if cfg.arch == "Windows" and argument:match("^[%u%U.]?:?[/\\].*") then
|
||
|
argument = argument:gsub("//","\\"):gsub("/","\\")
|
||
|
end
|
||
|
|
||
|
-- Windows doesn't recognize paths starting with two slashes or backslashes
|
||
|
-- so we double every backslash except for the first one
|
||
|
if cfg.arch == "Windows" and argument:match("^[/\\].*") then
|
||
|
local prefix = argument:sub(1,1)
|
||
|
argument = argument:sub(2):gsub("\\", "\\\\")
|
||
|
argument = prefix .. argument
|
||
|
else
|
||
|
argument = argument:gsub("\\", "\\\\")
|
||
|
end
|
||
|
argument = argument:gsub('"', '\\"')
|
||
|
|
||
|
return '"' .. argument .. '"'
|
||
|
end
|
||
|
|
||
|
-- Run the system command (in current directory).
|
||
|
-- Return true on success, nil on fail and log string.
|
||
|
-- When optional 'force_verbose' parameter is true, then the output will be shown
|
||
|
-- even when not in debug or verbose mode.
|
||
|
function exec(command, force_verbose)
|
||
|
force_verbose = force_verbose or false
|
||
|
assert(type(command) == "string", "sys.exec: Argument 'command' is not a string.")
|
||
|
assert(type(force_verbose) == "boolean", "sys.exec: Argument 'force_verbose' is not a boolean.")
|
||
|
|
||
|
if not (cfg.verbose or cfg.debug or force_verbose) then
|
||
|
if cfg.arch == "Windows" then
|
||
|
command = command .. " > NUL 2>&1"
|
||
|
else
|
||
|
command = command .. " > /dev/null 2>&1"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if cfg.debug then print("Executing the command: " .. command) end
|
||
|
local ok, str, status = os.execute(command)
|
||
|
|
||
|
-- os.execute returned values on failure are:
|
||
|
-- nil or true, "exit", n or true, "signal", n for lua >= 5.2
|
||
|
-- status ~= 0 for lua 5.x < 5.2
|
||
|
if ok == nil or (str == "exit" and status ~= 0) or str == "signal" or (ok ~= 0 and ok ~= true) then
|
||
|
return nil, "Error when running the command: " .. command
|
||
|
else
|
||
|
return true, "Sucessfully executed the command: " .. command
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Execute the 'command' and returns its output as a string.
|
||
|
function capture_output(command)
|
||
|
assert(type(command) == "string", "sys.exec: Argument 'command' is not a string.")
|
||
|
|
||
|
local executed, err = io.popen(command, "r")
|
||
|
if not executed then return nil, "Error running the command '" .. command .. "':" .. err end
|
||
|
|
||
|
local captured, err = executed:read("*a")
|
||
|
if not captured then return nil, "Error reading the output of command '" .. command .. "':" .. err end
|
||
|
|
||
|
executed:close()
|
||
|
return captured
|
||
|
end
|
||
|
|
||
|
-- Return whether the path is a root.
|
||
|
function is_root(path)
|
||
|
assert(type(path) == "string", "sys.is_root: Argument 'path' is not a string.")
|
||
|
return utils.to_boolean(path:find("^[a-zA-Z]:[/\\]$") or path:find("^[/\\]$"))
|
||
|
end
|
||
|
|
||
|
-- Return whether the path is absolute.
|
||
|
function is_abs(path)
|
||
|
assert(type(path) == "string", "sys.is_abs: Argument 'path' is not a string.")
|
||
|
return utils.to_boolean(path:find("^[a-zA-Z]:[/\\].*$") or path:find("^[/\\].*$"))
|
||
|
end
|
||
|
|
||
|
-- Return whether the specified file or directory exists.
|
||
|
function exists(path)
|
||
|
assert(type(path) == "string", "sys.exists: Argument 'path' is not a string.")
|
||
|
local attr, err = lfs.attributes(path)
|
||
|
return utils.to_boolean(attr), err
|
||
|
end
|
||
|
|
||
|
-- Return whether the 'file' exists and is a file.
|
||
|
function is_file(file)
|
||
|
assert(type(file) == "string", "sys.is_file: Argument 'file' is not a string.")
|
||
|
return lfs.attributes(file, "mode") == "file"
|
||
|
end
|
||
|
|
||
|
-- Return whether the 'dir' exists and is a directory.
|
||
|
function is_dir(dir)
|
||
|
assert(type(dir) == "string", "sys.is_dir: Argument 'dir' is not a string.")
|
||
|
return lfs.attributes(dir, "mode") == "directory"
|
||
|
end
|
||
|
|
||
|
-- Return the current working directory
|
||
|
function current_dir()
|
||
|
local dir, err = lfs.currentdir()
|
||
|
if not dir then return nil, err end
|
||
|
return dir
|
||
|
end
|
||
|
|
||
|
-- Return an iterator over the directory 'dir'.
|
||
|
-- If 'dir' doesn't exist or is not a directory, return nil and error message.
|
||
|
function get_directory(dir)
|
||
|
dir = dir or current_dir()
|
||
|
assert(type(dir) == "string", "sys.get_directory: Argument 'dir' is not a string.")
|
||
|
if is_dir(dir) then
|
||
|
return lfs.dir(dir)
|
||
|
else
|
||
|
return nil, "Error: '".. dir .. "' is not a directory."
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Extract file or directory name from its path.
|
||
|
function extract_name(path)
|
||
|
assert(type(path) == "string", "sys.extract_name: Argument 'path' is not a string.")
|
||
|
if is_root(path) then return path end
|
||
|
|
||
|
path = remove_trailing(path)
|
||
|
path = path:gsub("^.*" .. path_separator(), "")
|
||
|
return path
|
||
|
end
|
||
|
|
||
|
-- Return parent directory of the 'path' or nil if there's no parent directory.
|
||
|
-- If 'path' is a path to file, return the directory the file is in.
|
||
|
function parent_dir(path)
|
||
|
assert(type(path) == "string", "sys.parent_dir: Argument 'path' is not a string.")
|
||
|
path = remove_curr_dir_dots(path)
|
||
|
path = remove_trailing(path)
|
||
|
|
||
|
local dir = path:gsub(utils.escape_magic(extract_name(path)) .. "$", "")
|
||
|
if dir == "" then
|
||
|
return nil
|
||
|
else
|
||
|
return make_path(dir)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Returns the table of all parent directories of 'path' up to the directory
|
||
|
-- specified by 'boundary_path' (exclusive).
|
||
|
function parents_up_to(path, boundary_path)
|
||
|
assert(type(path) == "string", "sys.parents_up_to: Argument 'path' is not a string.")
|
||
|
assert(type(boundary_path) == "string", "sys.parents_up_to: Argument 'boundary_path' is not a string.")
|
||
|
boundary_path = remove_trailing(boundary_path)
|
||
|
|
||
|
-- helper function to recursively collect the parent directories
|
||
|
local function collect_parents(_path, _parents)
|
||
|
local _parent = parent_dir(_path)
|
||
|
if _parent and _parent ~= boundary_path then
|
||
|
table.insert(_parents, _parent)
|
||
|
return collect_parents(_parent, _parents)
|
||
|
else
|
||
|
return _parents
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return collect_parents(path, {})
|
||
|
end
|
||
|
|
||
|
-- Compose path composed from specified parts or current
|
||
|
-- working directory when no part specified.
|
||
|
function make_path(...)
|
||
|
-- arg is deprecated in lua 5.2 in favor of table.pack we mimic here
|
||
|
local arg = {n=select('#',...),...}
|
||
|
local parts = arg
|
||
|
assert(type(parts) == "table", "sys.make_path: Argument 'parts' is not a table.")
|
||
|
|
||
|
local path, err
|
||
|
if parts.n == 0 then
|
||
|
path, err = current_dir()
|
||
|
else
|
||
|
path, err = table.concat(parts, path_separator())
|
||
|
end
|
||
|
if not path then return nil, err end
|
||
|
|
||
|
-- squeeze repeated occurences of a file separator
|
||
|
path = path:gsub(path_separator() .. "+", path_separator())
|
||
|
|
||
|
-- remove unnecessary trailing path separator
|
||
|
path = remove_trailing(path)
|
||
|
|
||
|
return path
|
||
|
end
|
||
|
|
||
|
-- Return absolute path from 'path'
|
||
|
function abs_path(path)
|
||
|
assert(type(path) == "string", "sys.get_abs_path: Argument 'path' is not a string.")
|
||
|
if is_abs(path) then return path end
|
||
|
|
||
|
local cur_dir, err = current_dir()
|
||
|
if not cur_dir then return nil, err end
|
||
|
|
||
|
return make_path(cur_dir, path)
|
||
|
end
|
||
|
|
||
|
-- Returns path to the temporary directory of OS.
|
||
|
function tmp_dir()
|
||
|
return os.getenv("TMPDIR") or os.getenv("TEMP") or os.getenv("TMP") or "/tmp"
|
||
|
end
|
||
|
|
||
|
-- Returns temporary file (or directory) path (with optional prefix).
|
||
|
function tmp_name(prefix)
|
||
|
prefix = prefix or ""
|
||
|
assert(type(prefix) == "string", "sys.tmp_name: Argument 'prefix' is not a string.")
|
||
|
return make_path(tmp_dir(), prefix .. "luadist_" .. utils.rand(10000000000))
|
||
|
end
|
||
|
|
||
|
-- Return table of all paths in 'dir'
|
||
|
function get_file_list(dir)
|
||
|
dir = dir or current_dir()
|
||
|
assert(type(dir) == "string", "sys.get_directory: Argument 'dir' is not a string.")
|
||
|
if not exists(dir) then return nil, "Error getting file list of '" .. dir .. "': directory doesn't exist." end
|
||
|
|
||
|
local function collect(path, all_paths)
|
||
|
for item in get_directory(path) do
|
||
|
|
||
|
local item_path = make_path(path, item)
|
||
|
local _, last = item_path:find(dir .. path_separator(), 1, true)
|
||
|
local path_to_insert = item_path:sub(last + 1)
|
||
|
|
||
|
if is_file(item_path) then
|
||
|
table.insert(all_paths, path_to_insert)
|
||
|
elseif is_dir(item_path) and item ~= "." and item ~= ".." then
|
||
|
table.insert(all_paths, path_to_insert)
|
||
|
collect(item_path, all_paths)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local all_paths = {}
|
||
|
collect(dir, all_paths)
|
||
|
|
||
|
return all_paths
|
||
|
end
|
||
|
|
||
|
-- Return time of the last modification of 'file'.
|
||
|
function last_modification_time(file)
|
||
|
assert(type(file) == "string", "sys.last_modification_time: Argument 'file' is not a string.")
|
||
|
return lfs.attributes(file, "modification")
|
||
|
end
|
||
|
|
||
|
-- Return the current time (in seconds since epoch).
|
||
|
function current_time()
|
||
|
return os.time()
|
||
|
end
|
||
|
|
||
|
-- Change the current working directory and return 'true' and previous working
|
||
|
-- directory on success and 'nil' and error message on error.
|
||
|
function change_dir(dir_name)
|
||
|
assert(type(dir_name) == "string", "sys.change_dir: Argument 'dir_name' is not a string.")
|
||
|
local prev_dir = current_dir()
|
||
|
local ok, err = lfs.chdir(dir_name)
|
||
|
if ok then
|
||
|
return ok, prev_dir
|
||
|
else
|
||
|
return nil, err
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Make a new directory, making also all of its parent directories that doesn't exist.
|
||
|
function make_dir(dir_name)
|
||
|
assert(type(dir_name) == "string", "sys.make_dir: Argument 'dir_name' is not a string.")
|
||
|
if exists(dir_name) then
|
||
|
return true
|
||
|
else
|
||
|
local par_dir = parent_dir(dir_name)
|
||
|
if par_dir then
|
||
|
local ok, err = make_dir(par_dir)
|
||
|
if not ok then return nil, err end
|
||
|
end
|
||
|
return lfs.mkdir(dir_name)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Move file (or directory) to the destination directory
|
||
|
function move_to(file_or_dir, dest_dir)
|
||
|
assert(type(file_or_dir) == "string", "sys.move_to: Argument 'file_or_dir' is not a string.")
|
||
|
assert(type(dest_dir) == "string", "sys.move_to: Argument 'dest_dir' is not a string.")
|
||
|
assert(is_dir(dest_dir), "sys.move_to: Destination '" .. dest_dir .."' is not a directory.")
|
||
|
|
||
|
-- Extract file/dir name from its path
|
||
|
local file_or_dir_name = extract_name(file_or_dir)
|
||
|
|
||
|
return os.rename(file_or_dir, make_path(dest_dir, file_or_dir_name))
|
||
|
end
|
||
|
|
||
|
-- rename file (or directory) to the new name.
|
||
|
function rename(file, new_name)
|
||
|
assert(type(file) == "string", "sys.rename: Argument 'file' is not a string.")
|
||
|
assert(type(new_name) == "string", "sys.rename: Argument 'new_name' is not a string.")
|
||
|
assert(not exists(new_name), "sys.rename: desired filename already exists.")
|
||
|
|
||
|
return os.rename(file, new_name)
|
||
|
end
|
||
|
|
||
|
-- Copy 'source' to the destination directory 'dest_dir'.
|
||
|
-- If 'source' is a directory, then recursive copying is used.
|
||
|
-- For non-recursive copying of directories use the make_dir() function.
|
||
|
function copy(source, dest_dir)
|
||
|
assert(type(source) == "string", "sys.copy: Argument 'file_or_dir' is not a string.")
|
||
|
assert(type(dest_dir) == "string", "sys.copy: Argument 'dest_dir' is not a string.")
|
||
|
assert(is_dir(dest_dir), "sys.copy: destination '" .. dest_dir .."' is not a directory.")
|
||
|
|
||
|
if cfg.arch == "Windows" then
|
||
|
if is_dir(source) then
|
||
|
make_dir(make_path(dest_dir, extract_name(source)))
|
||
|
return exec("xcopy /E /I /Y /Q " .. quote(source) .. " " .. quote(dest_dir .. "\\" .. extract_name(source)))
|
||
|
else
|
||
|
return exec("copy /Y " .. quote(source) .. " " .. quote(dest_dir))
|
||
|
end
|
||
|
else
|
||
|
if is_dir(source) then
|
||
|
return exec("cp -fRH " .. quote(source) .. " " .. quote(dest_dir))
|
||
|
else
|
||
|
return exec("cp -fH " .. quote(source) .. " " .. quote(dest_dir))
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Delete the specified file or directory
|
||
|
function delete(path)
|
||
|
assert(type(path) == "string", "sys.delete: Argument 'path' is not a string.")
|
||
|
assert(is_abs(path), "sys.delete: Argument 'path' is not an absolute path.")
|
||
|
|
||
|
if cfg.arch == "Windows" then
|
||
|
if not exists(path) then
|
||
|
return true
|
||
|
elseif is_file(path) then
|
||
|
return os.remove(path)
|
||
|
else
|
||
|
return exec("rd /S /Q " .. quote(path))
|
||
|
end
|
||
|
else
|
||
|
return exec("rm -rf " .. quote(path))
|
||
|
end
|
||
|
end
|