local fs = {}
|
|
local lfs = require "lfs"
|
local utils = require "luacheck.utils"
|
|
local function ensure_dir_sep(path)
|
if path:sub(-1) ~= utils.dir_sep then
|
return path .. utils.dir_sep
|
end
|
|
return path
|
end
|
|
function fs.split_base(path)
|
if utils.is_windows then
|
if path:match("^%a:\\") then
|
return path:sub(1, 3), path:sub(4)
|
else
|
-- Disregard UNC paths and relative paths with drive letter.
|
return "", path
|
end
|
else
|
if path:match("^/") then
|
if path:match("^//") then
|
return "//", path:sub(3)
|
else
|
return "/", path:sub(2)
|
end
|
else
|
return "", path
|
end
|
end
|
end
|
|
function fs.is_absolute(path)
|
return fs.split_base(path) ~= ""
|
end
|
|
function fs.normalize(path)
|
if utils.is_windows then
|
path = path:lower()
|
end
|
local base, rest = fs.split_base(path)
|
rest = rest:gsub("[/\\]", utils.dir_sep)
|
|
local parts = {}
|
|
for part in rest:gmatch("[^"..utils.dir_sep.."]+") do
|
if part ~= "." then
|
if part == ".." and #parts > 0 and parts[#parts] ~= ".." then
|
parts[#parts] = nil
|
else
|
parts[#parts + 1] = part
|
end
|
end
|
end
|
|
if base == "" and #parts == 0 then
|
return "."
|
else
|
return base..table.concat(parts, utils.dir_sep)
|
end
|
end
|
|
local function join_two_paths(base, path)
|
if base == "" or fs.is_absolute(path) then
|
return path
|
else
|
return ensure_dir_sep(base) .. path
|
end
|
end
|
|
function fs.join(base, ...)
|
local res = base
|
|
for i = 1, select("#", ...) do
|
res = join_two_paths(res, select(i, ...))
|
end
|
|
return res
|
end
|
|
function fs.is_subpath(path, subpath)
|
local base1, rest1 = fs.split_base(path)
|
local base2, rest2 = fs.split_base(subpath)
|
|
if base1 ~= base2 then
|
return false
|
end
|
|
if rest2:sub(1, #rest1) ~= rest1 then
|
return false
|
end
|
|
return rest1 == rest2 or rest2:sub(#rest1 + 1, #rest1 + 1) == utils.dir_sep
|
end
|
|
function fs.is_dir(path)
|
return lfs.attributes(path, "mode") == "directory"
|
end
|
|
function fs.is_file(path)
|
return lfs.attributes(path, "mode") == "file"
|
end
|
|
-- Searches for file starting from path, going up until the file
|
-- is found or root directory is reached.
|
-- Path must be absolute.
|
-- Returns absolute and relative paths to directory containing file or nil.
|
function fs.find_file(path, file)
|
if fs.is_absolute(file) then
|
return fs.is_file(file) and path, ""
|
end
|
|
path = fs.normalize(path)
|
local base, rest = fs.split_base(path)
|
local rel_path = ""
|
|
while true do
|
if fs.is_file(fs.join(base..rest, file)) then
|
return base..rest, rel_path
|
elseif rest == "" then
|
return
|
end
|
|
rest = rest:match("^(.*)"..utils.dir_sep..".*$") or ""
|
rel_path = rel_path..".."..utils.dir_sep
|
end
|
end
|
|
-- Returns iterator over directory items or nil, error message.
|
function fs.dir_iter(dir_path)
|
local ok, iter, state, var = pcall(lfs.dir, dir_path)
|
|
if not ok then
|
local err = utils.unprefix(iter, "cannot open " .. dir_path .. ": ")
|
return nil, "couldn't list directory: " .. err
|
end
|
|
return iter, state, var
|
end
|
|
-- Returns list of all files in directory matching pattern.
|
-- Additionally returns a mapping from directory paths that couldn't be expanded
|
-- to error messages.
|
function fs.extract_files(dir_path, pattern)
|
local res = {}
|
local err_map = {}
|
|
local function scan(dir)
|
local iter, state, var = fs.dir_iter(dir)
|
|
if not iter then
|
err_map[dir] = state
|
table.insert(res, dir)
|
return
|
end
|
|
for path in iter, state, var do
|
if path ~= "." and path ~= ".." then
|
local full_path = fs.join(dir, path)
|
|
if fs.is_dir(full_path) then
|
scan(full_path)
|
elseif path:match(pattern) and fs.is_file(full_path) then
|
table.insert(res, full_path)
|
end
|
end
|
end
|
end
|
|
scan(dir_path)
|
table.sort(res)
|
return res, err_map
|
end
|
|
-- Returns modification time for a file.
|
function fs.get_mtime(path)
|
return lfs.attributes(path, "modification")
|
end
|
|
-- Returns absolute path to current working directory, with trailing directory separator.
|
function fs.get_current_dir()
|
return ensure_dir_sep(assert(lfs.currentdir()))
|
end
|
|
return fs
|