446 lines
10 KiB
Lua

-- modules/fs.lua - Comprehensive filesystem utilities
local fs = {}
-- ======================================================================
-- FILE OPERATIONS
-- ======================================================================
function fs.exists(path)
return moonshark.file_exists(path)
end
function fs.size(path)
local size = moonshark.file_size(path)
return size >= 0 and size or nil
end
function fs.is_dir(path)
return moonshark.file_is_dir(path)
end
function fs.is_file(path)
return fs.exists(path) and not fs.is_dir(path)
end
function fs.read(path)
local content, err = moonshark.file_read(path)
if not content then
error("Failed to read file '" .. path .. "': " .. (err or "unknown error"))
end
return content
end
function fs.write(path, content)
local success, err = moonshark.file_write(path, content)
if not success then
error("Failed to write file '" .. path .. "': " .. (err or "unknown error"))
end
return true
end
function fs.append(path, content)
local success, err = moonshark.file_append(path, content)
if not success then
error("Failed to append to file '" .. path .. "': " .. (err or "unknown error"))
end
return true
end
function fs.copy(src, dst)
local success, err = moonshark.file_copy(src, dst)
if not success then
error("Failed to copy '" .. src .. "' to '" .. dst .. "': " .. (err or "unknown error"))
end
return true
end
function fs.move(src, dst)
local success, err = moonshark.file_move(src, dst)
if not success then
error("Failed to move '" .. src .. "' to '" .. dst .. "': " .. (err or "unknown error"))
end
return true
end
function fs.remove(path)
local success, err = moonshark.file_delete(path)
if not success then
error("Failed to remove '" .. path .. "': " .. (err or "unknown error"))
end
return true
end
function fs.mtime(path)
local timestamp = moonshark.file_mtime(path)
return timestamp >= 0 and timestamp or nil
end
function fs.touch(path)
local success, err = moonshark.touch(path)
if not success then
error("Failed to touch '" .. path .. "': " .. (err or "unknown error"))
end
return true
end
function fs.lines(path)
local lines, err = moonshark.file_lines(path)
if not lines then
error("Failed to read lines from '" .. path .. "': " .. (err or "unknown error"))
end
return lines
end
-- ======================================================================
-- DIRECTORY OPERATIONS
-- ======================================================================
function fs.mkdir(path)
local success, err = moonshark.dir_create(path)
if not success then
error("Failed to create directory '" .. path .. "': " .. (err or "unknown error"))
end
return true
end
function fs.rmdir(path)
local success, err = moonshark.dir_remove(path)
if not success then
error("Failed to remove directory '" .. path .. "': " .. (err or "unknown error"))
end
return true
end
function fs.list(path)
local entries, err = moonshark.dir_list(path)
if not entries then
error("Failed to list directory '" .. path .. "': " .. (err or "unknown error"))
end
return entries
end
function fs.list_files(path)
local entries = fs.list(path)
local files = {}
for _, entry in ipairs(entries) do
if not entry.is_dir then
table.insert(files, entry)
end
end
return files
end
function fs.list_dirs(path)
local entries = fs.list(path)
local dirs = {}
for _, entry in ipairs(entries) do
if entry.is_dir then
table.insert(dirs, entry)
end
end
return dirs
end
function fs.list_names(path)
local entries = fs.list(path)
local names = {}
for _, entry in ipairs(entries) do
table.insert(names, entry.name)
end
return names
end
-- ======================================================================
-- PATH OPERATIONS
-- ======================================================================
function fs.join(...)
return moonshark.path_join(...)
end
function fs.dirname(path)
return moonshark.path_dir(path)
end
function fs.basename(path)
return moonshark.path_basename(path)
end
function fs.ext(path)
return moonshark.path_ext(path)
end
function fs.abs(path)
local abs_path, err = moonshark.path_abs(path)
if not abs_path then
error("Failed to get absolute path for '" .. path .. "': " .. (err or "unknown error"))
end
return abs_path
end
function fs.clean(path)
return moonshark.path_clean(path)
end
function fs.split(path)
return moonshark.path_split(path)
end
-- Split path into directory, name, and extension
function fs.splitext(path)
local dir = fs.dirname(path)
local base = fs.basename(path)
local ext = fs.ext(path)
local name = base
if ext ~= "" then
name = base:sub(1, -(#ext + 1))
end
return dir, name, ext
end
-- ======================================================================
-- WORKING DIRECTORY
-- ======================================================================
function fs.getcwd()
local cwd, err = moonshark.getcwd()
if not cwd then
error("Failed to get current directory: " .. (err or "unknown error"))
end
return cwd
end
function fs.chdir(path)
local success, err = moonshark.chdir(path)
if not success then
error("Failed to change directory to '" .. path .. "': " .. (err or "unknown error"))
end
return true
end
-- ======================================================================
-- TEMPORARY FILES
-- ======================================================================
function fs.tempfile(prefix)
local path, err = moonshark.temp_file(prefix)
if not path then
error("Failed to create temporary file: " .. (err or "unknown error"))
end
return path
end
function fs.tempdir(prefix)
local path, err = moonshark.temp_dir(prefix)
if not path then
error("Failed to create temporary directory: " .. (err or "unknown error"))
end
return path
end
-- ======================================================================
-- PATTERN MATCHING
-- ======================================================================
function fs.glob(pattern)
local matches, err = moonshark.glob(pattern)
if not matches then
error("Failed to glob pattern '" .. pattern .. "': " .. (err or "unknown error"))
end
return matches
end
function fs.walk(root)
local files, err = moonshark.walk(root)
if not files then
error("Failed to walk directory '" .. root .. "': " .. (err or "unknown error"))
end
return files
end
-- ======================================================================
-- UTILITY FUNCTIONS
-- ======================================================================
-- Get file extension without dot
function fs.extension(path)
local ext = fs.ext(path)
return ext:sub(2) -- Remove leading dot
end
-- Change file extension
function fs.change_ext(path, new_ext)
local dir, name, _ = fs.splitext(path)
if not new_ext:match("^%.") then
new_ext = "." .. new_ext
end
return fs.join(dir, name .. new_ext)
end
-- Ensure directory exists
function fs.ensure_dir(path)
if not fs.exists(path) then
fs.mkdir(path)
elseif not fs.is_dir(path) then
error("Path exists but is not a directory: " .. path)
end
return true
end
-- Get file size in human readable format
function fs.size_human(path)
local size = fs.size(path)
if not size then return nil end
local units = {"B", "KB", "MB", "GB", "TB"}
local unit_index = 1
local size_float = tonumber(size)
while size_float >= 1024 and unit_index < #units do
size_float = size_float / 1024
unit_index = unit_index + 1
end
if unit_index == 1 then
return string.format("%d %s", size_float, units[unit_index])
else
return string.format("%.1f %s", size_float, units[unit_index])
end
end
-- Check if path is safe (doesn't contain .. or other dangerous patterns)
function fs.is_safe_path(path)
-- Normalize path
path = fs.clean(path)
-- Check for dangerous patterns
if path:match("%.%.") then return false end
if path:match("^/") then return false end -- Absolute paths might be dangerous
if path:match("^~") then return false end -- Home directory references
return true
end
-- Create directory tree
function fs.makedirs(path)
return fs.mkdir(path) -- mkdir already creates parent directories
end
-- Remove directory tree
function fs.removedirs(path)
return fs.rmdir(path) -- rmdir already removes recursively
end
-- Copy directory tree
function fs.copytree(src, dst)
if not fs.exists(src) then
error("Source directory does not exist: " .. src)
end
if not fs.is_dir(src) then
error("Source is not a directory: " .. src)
end
fs.mkdir(dst)
local entries = fs.list(src)
for _, entry in ipairs(entries) do
local src_path = fs.join(src, entry.name)
local dst_path = fs.join(dst, entry.name)
if entry.is_dir then
fs.copytree(src_path, dst_path)
else
fs.copy(src_path, dst_path)
end
end
return true
end
-- Find files by pattern
function fs.find(root, pattern, recursive)
recursive = recursive ~= false
local results = {}
local function search(dir)
local entries = fs.list(dir)
for _, entry in ipairs(entries) do
local full_path = fs.join(dir, entry.name)
if not entry.is_dir and entry.name:match(pattern) then
table.insert(results, full_path)
elseif entry.is_dir and recursive then
search(full_path)
end
end
end
search(root)
return results
end
-- Get directory tree as nested table
function fs.tree(root, max_depth)
max_depth = max_depth or 10
local function build_tree(path, depth)
if depth > max_depth then return nil end
if not fs.exists(path) then return nil end
local node = {
name = fs.basename(path),
path = path,
is_dir = fs.is_dir(path)
}
if node.is_dir then
node.children = {}
local entries = fs.list(path)
for _, entry in ipairs(entries) do
local child_path = fs.join(path, entry.name)
local child = build_tree(child_path, depth + 1)
if child then
table.insert(node.children, child)
end
end
else
node.size = fs.size(path)
node.mtime = fs.mtime(path)
end
return node
end
return build_tree(root, 1)
end
-- Monitor file changes (simplified version)
function fs.watch(path, callback, interval)
interval = interval or 1
if not fs.exists(path) then
error("Path does not exist: " .. path)
end
local last_mtime = fs.mtime(path)
while true do
local current_mtime = fs.mtime(path)
if current_mtime and current_mtime ~= last_mtime then
callback(path, "modified")
last_mtime = current_mtime
end
-- Sleep for interval (this would need a proper sleep function)
-- This is a placeholder - real implementation would need proper timing
local start = os.clock()
while os.clock() - start < interval do end
end
end
return fs