-- 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