diff --git a/crates/lb/src/fs.rs b/crates/lb/src/fs.rs deleted file mode 100644 index 1fc706e..0000000 --- a/crates/lb/src/fs.rs +++ /dev/null @@ -1,645 +0,0 @@ -//! Filesystem library. -//! -//! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the -//! filesystem. -//! -//! ## Asynchronous by default -//! -//! Filesystem operations are blocking by nature; to provide asynchronicity, luby performs blocking -//! operations in a background thread pool by default. Synchronous complements to all asynchronous -//! functions are always provided. -//! -//! ## Exports -//! -//! See [`lb_fslib`] for items exported by this library. -use luaffi::{cdef, metatype}; -use std::{ - cell::{BorrowError, BorrowMutError, RefCell}, - path::PathBuf, - time::SystemTime, -}; -use thiserror::Error; - -/// Errors that can be thrown by this library. -/// -/// Functions which return this error will **throw**. The error message can be caught by using -/// [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall). -#[derive(Debug, Error)] -pub enum Error { - /// Attempt to access an object while it is being modified. - #[error("cannot access object while it is being modified")] - Borrow(#[from] BorrowError), - /// Attempt to modify an object while it is in use. - #[error("cannot modify object while it is in use")] - BorrowMut(#[from] BorrowMutError), - /// I/O error. - #[error("{0}")] - Io(#[from] std::io::Error), - /// Walk directory error. - #[error("{0}")] - Walk(#[from] walkdir::Error), - /// Glob pattern error. - #[error("{0}")] - Glob(#[from] globset::Error), -} - -type Result = std::result::Result; - -/// Items exported by the `lb:fs` library. -/// -/// This library can be acquired by calling -/// [`require("lb:fs")`](https://www.lua.org/manual/5.1/manual.html#pdf-require). -/// -/// ```lua -/// local fs = require("lb:fs"); -/// ``` -#[cdef(module = "lb:fs")] -pub struct lb_fslib; - -#[metatype] -impl lb_fslib { - #[new] - extern "Lua-C" fn new() -> Self { - Self - } - - /// Reads the entire contents of a file. - /// - /// # Errors - /// - /// This function may throw if the file does not exist or could not be read. - pub async extern "Lua-C" fn read(path: &str) -> Result> { - Ok(tokio::fs::read(path).await?) - } - - /// Reads the entire contents of a file synchronously. - /// - /// This is a synchronous complement to [`read`](Self::read). - /// - /// # Errors - /// - /// This function may throw if the file does not exist or could not be read. - pub extern "Lua-C" fn read_sync(path: &str) -> Result> { - Ok(std::fs::read(path)?) - } - - /// Writes the given contents to a file, replacing its contents if it exists. - /// - /// # Errors - /// - /// This function may throw if the file could not be written. - pub async extern "Lua-C" fn write(path: &str, contents: &[u8]) -> Result<()> { - Ok(tokio::fs::write(path, contents).await?) - } - - /// Writes the given contents to a file synchronously, replacing its contents if it exists. - /// - /// This is a synchronous complement to [`write`](Self::write). - /// - /// # Errors - /// - /// This function may throw if the file could not be written. - pub extern "Lua-C" fn write_sync(path: &str, contents: &[u8]) -> Result<()> { - Ok(std::fs::write(path, contents)?) - } - - /// Reads the entries in a directory. - /// - /// # Errors - /// - /// This function may throw if the directory could not be read. - pub async extern "Lua-C" fn read_dir(path: &str) -> Result { - Ok(lb_read_dir::new(tokio::fs::read_dir(path).await?)) - } - - /// Reads the entries in a directory synchronously. - /// - /// This is a synchronous complement to [`read_dir`](Self::read_dir). - /// - /// # Errors - /// - /// This function may throw if the directory could not be read. - pub extern "Lua-C" fn read_dir_sync(path: &str) -> Result { - Ok(lb_read_dir_sync::new(std::fs::read_dir(path)?)) - } - - /// Recursively walks a directory, yielding all entries within it. - pub extern "Lua-C" fn walk_dir(path: &str) -> lb_walk_dir { - lb_walk_dir::new(walkdir::WalkDir::new(path).into_iter()) - } - - /// Returns an iterator over all files matching a glob pattern in the current directory. - /// - /// # Errors - /// - /// This function may throw if the pattern is invalid. - pub extern "Lua" fn glob(pattern: &str) -> Result { - Self::glob_dir(".", pattern) - } - - /// Returns an iterator over all files matching a glob pattern in the given directory. - /// - /// # Errors - /// - /// This function may throw if the pattern is invalid. - pub extern "Lua-C" fn glob_dir(path: &str, pattern: &str) -> Result { - let prefix = PathBuf::from(path); - let iter = walkdir::WalkDir::new(path).min_depth(1).into_iter(); - let matcher = globset::GlobSet::builder() - .add(globset::Glob::new(pattern)?) - .build()?; - - Ok(lb_glob_dir::new(iter, matcher, prefix)) - } - - /// Creates a new temporary directory. - /// - /// # Errors - /// - /// This function may throw if the temporary directory could not be created. - pub extern "Lua-C" fn temp_dir() -> Result { - Ok(lb_temp_dir::new(tempfile::tempdir()?)) - } - - /// Creates a new temporary directory inside the specified path. - /// - /// # Errors - /// - /// This function may throw if the temporary directory could not be created. - pub extern "Lua-C" fn temp_dir_in(path: &str) -> Result { - Ok(lb_temp_dir::new(tempfile::tempdir_in(path)?)) - } -} - -/// Iterator over the entries in a directory. -#[derive(Debug)] -#[cdef] -pub struct lb_read_dir(#[opaque] RefCell); - -#[metatype] -impl lb_read_dir { - fn new(iter: tokio::fs::ReadDir) -> Self { - Self(RefCell::new(iter)) - } - - /// Returns the next entry in the directory, or `nil` if there are no more entries. - /// - /// # Errors - /// - /// This function may throw if the directory could not be read. - #[call] - pub async extern "Lua-C" fn next(&self) -> Result> { - Ok(self - .0 - .try_borrow_mut()? - .next_entry() - .await? - .map(lb_dir_entry::new)) - } -} - -/// Synchronous version of [`lb_read_dir`]. -#[derive(Debug)] -#[cdef] -pub struct lb_read_dir_sync(#[opaque] RefCell); - -#[metatype] -impl lb_read_dir_sync { - fn new(iter: std::fs::ReadDir) -> Self { - Self(RefCell::new(iter)) - } - - /// Returns the next entry in the directory, or `nil` if there are no more entries. - /// - /// # Errors - /// - /// This function may throw if the directory could not be read. - #[call] - pub extern "Lua-C" fn next(&self) -> Result> { - Ok(self - .0 - .try_borrow_mut()? - .next() - .transpose()? - .map(lb_dir_entry_sync::new)) - } -} - -/// Entry inside of a directory on the filesystem. -#[derive(Debug)] -#[cdef] -pub struct lb_dir_entry(#[opaque] tokio::fs::DirEntry); - -#[metatype] -impl lb_dir_entry { - fn new(entry: tokio::fs::DirEntry) -> Self { - Self(entry) - } - - /// Returns the full path of this entry. - pub extern "Lua-C" fn path(&self) -> String { - self.0.path().to_string_lossy().into() - } - - /// Returns the file name of this entry. - pub extern "Lua-C" fn name(&self) -> String { - self.0.file_name().to_string_lossy().into() - } - - /// Returns the type of this entry. - /// - /// # Errors - /// - /// This function may throw if the file type could not be determined. - pub async extern "Lua-C" fn r#type(&self) -> Result { - Ok(lb_file_type::new(self.0.file_type().await?)) - } - - /// Returns the metadata for this entry. - /// - /// # Errors - /// - /// This function may throw if the metadata could not be retrieved. - pub async extern "Lua-C" fn metadata(&self) -> Result { - Ok(lb_file_meta::new(self.0.metadata().await?)) - } - - /// Returns the inode number for this entry. - #[cfg(unix)] - pub extern "Lua-C" fn ino(&self) -> u64 { - self.0.ino() - } - - /// Returns the full path of this entry. - #[tostring] - pub extern "Lua" fn tostring(&self) -> String { - self.path() - } -} - -/// Synchronous version of [`lb_dir_entry`]. -#[derive(Debug)] -#[cdef] -pub struct lb_dir_entry_sync(#[opaque] std::fs::DirEntry); - -#[metatype] -impl lb_dir_entry_sync { - fn new(entry: std::fs::DirEntry) -> Self { - Self(entry) - } - - /// Returns the full path of this entry. - pub extern "Lua-C" fn path(&self) -> String { - self.0.path().to_string_lossy().into() - } - - /// Returns the file name of this entry. - pub extern "Lua-C" fn name(&self) -> String { - self.0.file_name().to_string_lossy().into() - } - - /// Returns the type of this entry. - /// - /// # Errors - /// - /// This function may throw if the file type could not be determined. - pub extern "Lua-C" fn r#type(&self) -> Result { - Ok(lb_file_type::new(self.0.file_type()?)) - } - - /// Returns the metadata for this entry. - /// - /// # Errors - /// - /// This function may throw if the metadata could not be retrieved. - pub extern "Lua-C" fn metadata(&self) -> Result { - Ok(lb_file_meta::new(self.0.metadata()?)) - } - - /// Returns the inode number for this entry. - #[cfg(unix)] - pub extern "Lua-C" fn ino(&self) -> u64 { - use std::os::unix::fs::DirEntryExt; - self.0.ino() - } - - /// Returns the full path of this entry. - #[tostring] - pub extern "Lua" fn tostring(&self) -> String { - self.path() - } -} - -/// Structure representing the type of a file with accessors for each file type. -#[derive(Debug)] -#[cdef] -pub struct lb_file_type(#[opaque] std::fs::FileType); - -#[metatype] -impl lb_file_type { - fn new(ty: std::fs::FileType) -> Self { - Self(ty) - } - - /// Returns `true` if this file type is a directory. - pub extern "Lua-C" fn is_dir(&self) -> bool { - self.0.is_dir() - } - - /// Returns `true` if this file type is a regular file. - pub extern "Lua-C" fn is_file(&self) -> bool { - self.0.is_file() - } - - /// Returns `true` if this file type is a symbolic link. - pub extern "Lua-C" fn is_symlink(&self) -> bool { - self.0.is_file() - } - - /// Returns the string `"file"` if this is a regular file, `"dir"` if this is a directory, - /// `"symlink"` if this is a symbolic link, or `"other"` if it is some other type of file. - #[tostring] - pub extern "Lua-C" fn tostring(&self) -> String { - if self.0.is_file() { - "file" - } else if self.0.is_dir() { - "dir" - } else if self.0.is_symlink() { - "symlink" - } else { - "other" - } - .into() - } -} - -/// Metadata information about a file. -#[derive(Debug)] -#[cdef] -pub struct lb_file_meta(#[opaque] std::fs::Metadata); - -#[metatype] -impl lb_file_meta { - fn new(meta: std::fs::Metadata) -> Self { - Self(meta) - } - - /// Returns `true` if this file is a directory. - pub extern "Lua-C" fn is_dir(&self) -> bool { - self.0.is_dir() - } - - /// Returns `true` if this file is a regular file. - pub extern "Lua-C" fn is_file(&self) -> bool { - self.0.is_file() - } - - /// Returns `true` if this file is a symbolic link. - pub extern "Lua-C" fn is_symlink(&self) -> bool { - self.0.is_file() - } - - /// Returns the type of this file. - pub extern "Lua-C" fn r#type(&self) -> lb_file_type { - lb_file_type::new(self.0.file_type()) - } - - /// Returns the size of this file in bytes. - pub extern "Lua-C" fn size(&self) -> u64 { - self.0.len() - } - - /// Returns the permissions of this file. - pub extern "Lua-C" fn perms(&self) -> lb_file_perms { - lb_file_perms::new(self.0.permissions()) - } - - /// Returns the creation time of this file as seconds since the Unix epoch. - /// - /// # Errors - /// - /// This function may throw if the creation time could not be retrieved. - pub extern "Lua-C" fn created(&self) -> Result { - Ok(self - .0 - .created()? - .duration_since(SystemTime::UNIX_EPOCH) - .map(|dur| dur.as_secs_f64()) - .unwrap_or(0.)) - } - - /// Returns the modification time of this file as seconds since the Unix epoch. - /// - /// # Errors - /// - /// This function may throw if the modification time could not be retrieved. - pub extern "Lua-C" fn modified(&self) -> Result { - Ok(self - .0 - .modified()? - .duration_since(SystemTime::UNIX_EPOCH) - .map(|dur| dur.as_secs_f64()) - .unwrap_or(0.)) - } - - /// Returns the last access time of this file as seconds since the Unix epoch. - /// - /// # Errors - /// - /// This function may throw if the access time could not be retrieved. - pub extern "Lua-C" fn accessed(&self) -> Result { - Ok(self - .0 - .accessed()? - .duration_since(SystemTime::UNIX_EPOCH) - .map(|dur| dur.as_secs_f64()) - .unwrap_or(0.)) - } - - /// Returns a string representation of this file's metadata. - #[tostring] - pub extern "Lua-C" fn tostring(&self) -> String { - let ty = self.0.file_type(); - if ty.is_file() { - format!("file {}", self.0.len()) - } else if ty.is_dir() { - "dir".into() - } else if ty.is_symlink() { - "symlink".into() - } else { - "other".into() - } - } -} - -/// Representation of the various permissions on a file. -#[derive(Debug)] -#[cdef] -pub struct lb_file_perms(#[opaque] RefCell); - -#[metatype] -impl lb_file_perms { - fn new(perms: std::fs::Permissions) -> Self { - Self(RefCell::new(perms)) - } - - /// Returns `true` if the readonly flag is set. - pub extern "Lua-C" fn readonly(&self) -> bool { - self.0.borrow().readonly() - } - - /// Sets the readonly flag. - pub extern "Lua-C" fn set_readonly(&self, readonly: bool) { - self.0.borrow_mut().set_readonly(readonly); - } -} - -/// Iterator for recursively descending into a directory. -#[derive(Debug)] -#[cdef] -pub struct lb_walk_dir(#[opaque] RefCell); - -#[metatype] -impl lb_walk_dir { - fn new(iter: walkdir::IntoIter) -> Self { - Self(RefCell::new(iter)) - } - - /// Returns the next entry in the walk, or `nil` if there are no more entries. - /// - /// # Errors - /// - /// This function may throw if the directory could not be read. - #[call] - pub extern "Lua-C" fn next(&self) -> Result> { - Ok(self - .0 - .try_borrow_mut()? - .next() - .transpose()? - .map(lb_walk_dir_entry::new)) - } -} - -/// Entry inside of a directory on the filesystem obtained from [`lb_walk_dir`]. -#[derive(Debug)] -#[cdef] -pub struct lb_walk_dir_entry(#[opaque] walkdir::DirEntry); - -#[metatype] -impl lb_walk_dir_entry { - fn new(entry: walkdir::DirEntry) -> Self { - Self(entry) - } - - /// Returns the full path of this entry. - pub extern "Lua-C" fn path(&self) -> String { - self.0.path().to_string_lossy().into() - } - - /// Returns the file name of this entry. - pub extern "Lua-C" fn name(&self) -> String { - self.0.file_name().to_string_lossy().into() - } - - /// Returns the type of this entry. - pub extern "Lua-C" fn r#type(&self) -> lb_file_type { - lb_file_type::new(self.0.file_type()) - } - - /// Returns the metadata for this entry. - /// - /// # Errors - /// - /// This function may throw if the metadata could not be retrieved. - pub extern "Lua-C" fn metadata(&self) -> Result { - Ok(lb_file_meta::new(self.0.metadata()?)) - } - - /// Returns `true` if this entry was created from a symbolic link. - pub extern "Lua-C" fn is_symlink(&self) -> bool { - self.0.path_is_symlink() - } - - /// Returns the depth of this entry in the walk. - pub extern "Lua-C" fn depth(&self) -> u32 { - self.0.depth() as u32 - } - - /// Returns the inode number for this entry. - #[cfg(unix)] - pub extern "Lua-C" fn ino(&self) -> u64 { - use walkdir::DirEntryExt; - self.0.ino() - } - - /// Returns the full path of this entry. - #[tostring] - pub extern "Lua" fn tostring(&self) -> String { - self.path() - } -} - -/// Iterator that yields paths from the filesystem that match a particular pattern. -#[derive(Debug)] -#[cdef] -pub struct lb_glob_dir { - #[opaque] - iter: RefCell, - #[opaque] - matcher: globset::GlobSet, - #[opaque] - prefix: PathBuf, -} - -#[metatype] -impl lb_glob_dir { - fn new(iter: walkdir::IntoIter, matcher: globset::GlobSet, prefix: PathBuf) -> Self { - Self { - iter: RefCell::new(iter), - matcher, - prefix, - } - } - - /// Returns the next entry matching the glob pattern, or `nil` if there are no more entries. - /// - /// # Errors - /// - /// This function may throw if the directory could not be read. - #[call] - pub extern "Lua-C" fn next(&self) -> Result> { - while let Some(res) = self.iter.try_borrow_mut()?.next() { - let entry = res?; - let path = entry.path().strip_prefix(&self.prefix).unwrap(); - if self.matcher.is_match(path) { - return Ok(Some(lb_walk_dir_entry::new(entry))); - } - } - - Ok(None) - } -} - -/// Directory in the filesystem that is automatically deleted when it is garbage-collected. -#[derive(Debug)] -#[cdef] -pub struct lb_temp_dir(#[opaque] tempfile::TempDir); - -#[metatype] -impl lb_temp_dir { - fn new(dir: tempfile::TempDir) -> Self { - Self(dir) - } - - /// Returns the full path of this temporary directory. - pub extern "Lua-C" fn path(&self) -> String { - self.0.path().to_string_lossy().into() - } - - /// Returns the full path of this temporary directory. - #[tostring] - pub extern "Lua" fn tostring(&self) -> String { - self.path() - } -} diff --git a/crates/lb/src/fs/async.rs b/crates/lb/src/fs/async.rs new file mode 100644 index 0000000..a4d3498 --- /dev/null +++ b/crates/lb/src/fs/async.rs @@ -0,0 +1,83 @@ +use super::*; +use luaffi::{cdef, metatype}; +use std::cell::RefCell; +use tokio::fs::{DirEntry, ReadDir}; + +/// Iterator over the entries in a directory. +#[derive(Debug)] +#[cdef] +pub struct lb_read_dir(#[opaque] RefCell); + +#[metatype] +impl lb_read_dir { + pub(super) fn new(iter: ReadDir) -> Self { + Self(RefCell::new(iter)) + } + + /// Returns the next entry in the directory, or `nil` if there are no more entries. + /// + /// # Errors + /// + /// This function may throw if the directory could not be read. + #[call] + pub async extern "Lua-C" fn next(&self) -> Result> { + Ok(self + .0 + .try_borrow_mut()? + .next_entry() + .await? + .map(lb_dir_entry::new)) + } +} + +/// Entry inside of a directory on the filesystem. +#[derive(Debug)] +#[cdef] +pub struct lb_dir_entry(#[opaque] DirEntry); + +#[metatype] +impl lb_dir_entry { + pub(super) fn new(entry: DirEntry) -> Self { + Self(entry) + } + + /// Returns the full path of this entry. + pub extern "Lua-C" fn path(&self) -> String { + self.0.path().to_string_lossy().into() + } + + /// Returns the file name of this entry. + pub extern "Lua-C" fn name(&self) -> String { + self.0.file_name().to_string_lossy().into() + } + + /// Returns the type of this entry. + /// + /// # Errors + /// + /// This function may throw if the file type could not be determined. + pub async extern "Lua-C" fn r#type(&self) -> Result { + Ok(lb_file_type::new(self.0.file_type().await?)) + } + + /// Returns the metadata for this entry. + /// + /// # Errors + /// + /// This function may throw if the metadata could not be retrieved. + pub async extern "Lua-C" fn metadata(&self) -> Result { + Ok(lb_file_meta::new(self.0.metadata().await?)) + } + + /// Returns the inode number for this entry. + #[cfg(unix)] + pub extern "Lua-C" fn ino(&self) -> u64 { + self.0.ino() + } + + /// Returns the full path of this entry. + #[tostring] + pub extern "Lua" fn tostring(&self) -> String { + self.path() + } +} diff --git a/crates/lb/src/fs/glob.rs b/crates/lb/src/fs/glob.rs new file mode 100644 index 0000000..f19cb40 --- /dev/null +++ b/crates/lb/src/fs/glob.rs @@ -0,0 +1,46 @@ +use super::*; +use globset::GlobSet; +use luaffi::{cdef, metatype}; +use std::cell::RefCell; +use walkdir::IntoIter; + +/// Iterator that yields paths from the filesystem that match a particular pattern. +#[derive(Debug)] +#[cdef] +pub struct lb_glob_dir { + #[opaque] + iter: RefCell, + #[opaque] + matcher: GlobSet, + #[opaque] + prefix: PathBuf, +} + +#[metatype] +impl lb_glob_dir { + pub(super) fn new(iter: IntoIter, matcher: GlobSet, prefix: PathBuf) -> Self { + Self { + iter: RefCell::new(iter), + matcher, + prefix, + } + } + + /// Returns the next entry matching the glob pattern, or `nil` if there are no more entries. + /// + /// # Errors + /// + /// This function may throw if the directory could not be read. + #[call] + pub extern "Lua-C" fn next(&self) -> Result> { + while let Some(res) = self.iter.try_borrow_mut()?.next() { + let entry = res?; + let path = entry.path().strip_prefix(&self.prefix).unwrap(); + if self.matcher.is_match(path) { + return Ok(Some(lb_walk_dir_entry::new(entry))); + } + } + + Ok(None) + } +} diff --git a/crates/lb/src/fs/mod.rs b/crates/lb/src/fs/mod.rs new file mode 100644 index 0000000..f697119 --- /dev/null +++ b/crates/lb/src/fs/mod.rs @@ -0,0 +1,190 @@ +//! Filesystem library. +//! +//! The `lb:fs` library provides synchronous and asynchronous utilities for interacting with the +//! filesystem. +//! +//! ## Asynchronous by default +//! +//! Filesystem operations are blocking by nature; to provide asynchronicity, luby performs blocking +//! operations in a background thread pool by default. Synchronous complements to all asynchronous +//! functions are always provided. +//! +//! ## Exports +//! +//! See [`lb_fslib`] for items exported by this library. +use luaffi::{cdef, metatype}; +use std::{ + cell::{BorrowError, BorrowMutError}, + path::PathBuf, +}; +use thiserror::Error; + +mod r#async; +mod glob; +mod sync; +mod temp; +mod walk; + +pub use r#async::*; +pub use glob::*; +pub use sync::*; +pub use temp::*; +pub use walk::*; + +/// Errors that can be thrown by this library. +/// +/// Functions which return this error will **throw**. The error message can be caught by using +/// [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall). +#[derive(Debug, Error)] +pub enum Error { + /// Attempt to access an object while it is being modified. + #[error("cannot access object while it is being modified")] + Borrow(#[from] BorrowError), + /// Attempt to modify an object while it is in use. + #[error("cannot modify object while it is in use")] + BorrowMut(#[from] BorrowMutError), + /// I/O error. + #[error("{0}")] + Io(#[from] std::io::Error), + /// Walk directory error. + #[error("{0}")] + Walk(#[from] walkdir::Error), + /// Glob pattern error. + #[error("{0}")] + Glob(#[from] globset::Error), +} + +type Result = std::result::Result; + +/// Items exported by the `lb:fs` library. +/// +/// This library can be acquired by calling +/// [`require("lb:fs")`](https://www.lua.org/manual/5.1/manual.html#pdf-require). +/// +/// ```lua +/// local fs = require("lb:fs") +/// ``` +#[cdef(module = "lb:fs")] +pub struct lb_fslib; + +#[metatype] +impl lb_fslib { + #[new] + extern "Lua-C" fn new() -> Self { + Self + } + + /// Reads the entire contents of a file. + /// + /// # Errors + /// + /// This function may throw if the file does not exist or could not be read. + pub async extern "Lua-C" fn read(path: &str) -> Result> { + Ok(tokio::fs::read(path).await?) + } + + /// Reads the entire contents of a file synchronously. + /// + /// This is a synchronous complement to [`read`](Self::read). + /// + /// # Errors + /// + /// This function may throw if the file does not exist or could not be read. + pub extern "Lua-C" fn read_sync(path: &str) -> Result> { + Ok(std::fs::read(path)?) + } + + /// Writes the given contents to a file, replacing its contents if it exists. + /// + /// # Errors + /// + /// This function may throw if the file could not be written. + pub async extern "Lua-C" fn write(path: &str, contents: &[u8]) -> Result<()> { + Ok(tokio::fs::write(path, contents).await?) + } + + /// Writes the given contents to a file synchronously, replacing its contents if it exists. + /// + /// This is a synchronous complement to [`write`](Self::write). + /// + /// # Errors + /// + /// This function may throw if the file could not be written. + pub extern "Lua-C" fn write_sync(path: &str, contents: &[u8]) -> Result<()> { + Ok(std::fs::write(path, contents)?) + } + + /// Reads the entries in a directory. + /// + /// # Errors + /// + /// This function may throw if the directory could not be read. + pub async extern "Lua-C" fn read_dir(path: &str) -> Result { + Ok(lb_read_dir::new(tokio::fs::read_dir(path).await?)) + } + + /// Reads the entries in a directory synchronously. + /// + /// This is a synchronous complement to [`read_dir`](Self::read_dir). + /// + /// # Errors + /// + /// This function may throw if the directory could not be read. + pub extern "Lua-C" fn read_dir_sync(path: &str) -> Result { + Ok(lb_read_dir_sync::new(std::fs::read_dir(path)?)) + } + + /// Recursively walks the current directory, yielding all entries within it. + pub extern "Lua" fn walk(pattern: &str) -> Result { + Self::walk_dir(".") + } + + /// Recursively walks a directory, yielding all entries within it. + pub extern "Lua-C" fn walk_dir(path: &str) -> lb_walk_dir { + lb_walk_dir::new(walkdir::WalkDir::new(path).into_iter()) + } + + /// Recursively walks the current directory, yielding all entries within it that match the given + /// glob pattern. + /// + /// # Errors + /// + /// This function may throw if the pattern is invalid. + pub extern "Lua" fn glob(pattern: &str) -> Result { + Self::glob_dir(".", pattern) + } + + /// Recursively walks a directory, yielding all entries within it that match the given glob + /// pattern. + /// + /// # Errors + /// + /// This function may throw if the pattern is invalid. + pub extern "Lua-C" fn glob_dir(path: &str, pattern: &str) -> Result { + let prefix = PathBuf::from(path); + let iter = walkdir::WalkDir::new(path).min_depth(1).into_iter(); + let matcher = globset::GlobSet::builder() + .add(globset::Glob::new(pattern)?) + .build()?; + + Ok(lb_glob_dir::new(iter, matcher, prefix)) + } + + /// Creates a new temporary directory. + /// + /// # Errors + /// + /// This function may throw if the temporary directory could not be created. + pub extern "Lua-C" fn temp_dir() -> Result { + Ok(lb_temp_dir::new(tempfile::tempdir()?)) + } + + /// Creates a new temporary directory inside the specified path. + /// + /// # Errors + /// + /// This function may throw if the temporary directory could not be created. + pub extern "Lua-C" fn temp_dir_in(path: &str) -> Result { + Ok(lb_temp_dir::new(tempfile::tempdir_in(path)?)) + } +} diff --git a/crates/lb/src/fs/sync.rs b/crates/lb/src/fs/sync.rs new file mode 100644 index 0000000..1722df4 --- /dev/null +++ b/crates/lb/src/fs/sync.rs @@ -0,0 +1,251 @@ +use super::*; +use luaffi::{cdef, metatype}; +use std::{ + cell::RefCell, + fs::{DirEntry, FileType, Metadata, Permissions, ReadDir}, + time::SystemTime, +}; + +/// Structure representing the type of a file with accessors for each file type. +#[derive(Debug)] +#[cdef] +pub struct lb_file_type(#[opaque] FileType); + +#[metatype] +impl lb_file_type { + pub(super) fn new(ty: FileType) -> Self { + Self(ty) + } + + /// Returns `true` if this file type is a directory. + pub extern "Lua-C" fn is_dir(&self) -> bool { + self.0.is_dir() + } + + /// Returns `true` if this file type is a regular file. + pub extern "Lua-C" fn is_file(&self) -> bool { + self.0.is_file() + } + + /// Returns `true` if this file type is a symbolic link. + pub extern "Lua-C" fn is_symlink(&self) -> bool { + self.0.is_file() + } + + /// Returns the string `"file"` if this is a regular file, `"dir"` if this is a directory, + /// `"symlink"` if this is a symbolic link, or `"other"` if it is some other type of file. + #[tostring] + pub extern "Lua-C" fn tostring(&self) -> String { + if self.0.is_file() { + "file" + } else if self.0.is_dir() { + "dir" + } else if self.0.is_symlink() { + "symlink" + } else { + "other" + } + .into() + } +} + +/// Metadata information about a file. +#[derive(Debug)] +#[cdef] +pub struct lb_file_meta(#[opaque] Metadata); + +#[metatype] +impl lb_file_meta { + pub(super) fn new(meta: Metadata) -> Self { + Self(meta) + } + + /// Returns `true` if this file is a directory. + pub extern "Lua-C" fn is_dir(&self) -> bool { + self.0.is_dir() + } + + /// Returns `true` if this file is a regular file. + pub extern "Lua-C" fn is_file(&self) -> bool { + self.0.is_file() + } + + /// Returns `true` if this file is a symbolic link. + pub extern "Lua-C" fn is_symlink(&self) -> bool { + self.0.is_file() + } + + /// Returns the type of this file. + pub extern "Lua-C" fn r#type(&self) -> lb_file_type { + lb_file_type::new(self.0.file_type()) + } + + /// Returns the size of this file in bytes. + pub extern "Lua-C" fn size(&self) -> u64 { + self.0.len() + } + + /// Returns the permissions of this file. + pub extern "Lua-C" fn perms(&self) -> lb_file_perms { + lb_file_perms::new(self.0.permissions()) + } + + /// Returns the creation time of this file as seconds since the Unix epoch. + /// + /// # Errors + /// + /// This function may throw if the creation time could not be retrieved. + pub extern "Lua-C" fn created(&self) -> Result { + Ok(self + .0 + .created()? + .duration_since(SystemTime::UNIX_EPOCH) + .map(|dur| dur.as_secs_f64()) + .unwrap_or(0.)) + } + + /// Returns the modification time of this file as seconds since the Unix epoch. + /// + /// # Errors + /// + /// This function may throw if the modification time could not be retrieved. + pub extern "Lua-C" fn modified(&self) -> Result { + Ok(self + .0 + .modified()? + .duration_since(SystemTime::UNIX_EPOCH) + .map(|dur| dur.as_secs_f64()) + .unwrap_or(0.)) + } + + /// Returns the last access time of this file as seconds since the Unix epoch. + /// + /// # Errors + /// + /// This function may throw if the access time could not be retrieved. + pub extern "Lua-C" fn accessed(&self) -> Result { + Ok(self + .0 + .accessed()? + .duration_since(SystemTime::UNIX_EPOCH) + .map(|dur| dur.as_secs_f64()) + .unwrap_or(0.)) + } + + /// Returns a string representation of this file's metadata. + #[tostring] + pub extern "Lua-C" fn tostring(&self) -> String { + let ty = self.0.file_type(); + if ty.is_file() { + format!("file {}", self.0.len()) + } else if ty.is_dir() { + "dir".into() + } else if ty.is_symlink() { + "symlink".into() + } else { + "other".into() + } + } +} + +/// Representation of the various permissions on a file. +#[derive(Debug)] +#[cdef] +pub struct lb_file_perms(#[opaque] RefCell); + +#[metatype] +impl lb_file_perms { + pub(super) fn new(perms: Permissions) -> Self { + Self(RefCell::new(perms)) + } + + /// Returns `true` if the readonly flag is set. + pub extern "Lua-C" fn readonly(&self) -> bool { + self.0.borrow().readonly() + } + + /// Sets the readonly flag. + pub extern "Lua-C" fn set_readonly(&self, readonly: bool) { + self.0.borrow_mut().set_readonly(readonly); + } +} + +/// Synchronous version of [`lb_read_dir`]. +#[derive(Debug)] +#[cdef] +pub struct lb_read_dir_sync(#[opaque] RefCell); + +#[metatype] +impl lb_read_dir_sync { + pub(super) fn new(iter: ReadDir) -> Self { + Self(RefCell::new(iter)) + } + + /// Returns the next entry in the directory, or `nil` if there are no more entries. + /// + /// # Errors + /// + /// This function may throw if the directory could not be read. + #[call] + pub extern "Lua-C" fn next(&self) -> Result> { + Ok(self + .0 + .try_borrow_mut()? + .next() + .transpose()? + .map(lb_dir_entry_sync::new)) + } +} + +/// Synchronous version of [`lb_dir_entry`]. +#[derive(Debug)] +#[cdef] +pub struct lb_dir_entry_sync(#[opaque] DirEntry); + +#[metatype] +impl lb_dir_entry_sync { + pub(super) fn new(entry: DirEntry) -> Self { + Self(entry) + } + + /// Returns the full path of this entry. + pub extern "Lua-C" fn path(&self) -> String { + self.0.path().to_string_lossy().into() + } + + /// Returns the file name of this entry. + pub extern "Lua-C" fn name(&self) -> String { + self.0.file_name().to_string_lossy().into() + } + + /// Returns the type of this entry. + /// + /// # Errors + /// + /// This function may throw if the file type could not be determined. + pub extern "Lua-C" fn r#type(&self) -> Result { + Ok(lb_file_type::new(self.0.file_type()?)) + } + + /// Returns the metadata for this entry. + /// + /// # Errors + /// + /// This function may throw if the metadata could not be retrieved. + pub extern "Lua-C" fn metadata(&self) -> Result { + Ok(lb_file_meta::new(self.0.metadata()?)) + } + + /// Returns the inode number for this entry. + #[cfg(unix)] + pub extern "Lua-C" fn ino(&self) -> u64 { + use std::os::unix::fs::DirEntryExt; + self.0.ino() + } + + /// Returns the full path of this entry. + #[tostring] + pub extern "Lua" fn tostring(&self) -> String { + self.path() + } +} diff --git a/crates/lb/src/fs/temp.rs b/crates/lb/src/fs/temp.rs new file mode 100644 index 0000000..e4c9670 --- /dev/null +++ b/crates/lb/src/fs/temp.rs @@ -0,0 +1,25 @@ +use luaffi::{cdef, metatype}; +use tempfile::TempDir; + +/// Directory in the filesystem that is automatically deleted when it is garbage-collected. +#[derive(Debug)] +#[cdef] +pub struct lb_temp_dir(#[opaque] TempDir); + +#[metatype] +impl lb_temp_dir { + pub(super) fn new(dir: TempDir) -> Self { + Self(dir) + } + + /// Returns the full path of this temporary directory. + pub extern "Lua-C" fn path(&self) -> String { + self.0.path().to_string_lossy().into() + } + + /// Returns the full path of this temporary directory. + #[tostring] + pub extern "Lua" fn tostring(&self) -> String { + self.path() + } +} diff --git a/crates/lb/src/fs/walk.rs b/crates/lb/src/fs/walk.rs new file mode 100644 index 0000000..6f204aa --- /dev/null +++ b/crates/lb/src/fs/walk.rs @@ -0,0 +1,90 @@ +use super::*; +use luaffi::{cdef, metatype}; +use std::cell::RefCell; +use walkdir::{DirEntry, IntoIter}; + +/// Iterator for recursively descending into a directory. +#[derive(Debug)] +#[cdef] +pub struct lb_walk_dir(#[opaque] RefCell); + +#[metatype] +impl lb_walk_dir { + pub(super) fn new(iter: IntoIter) -> Self { + Self(RefCell::new(iter)) + } + + /// Returns the next entry in the walk, or `nil` if there are no more entries. + /// + /// # Errors + /// + /// This function may throw if the directory could not be read. + #[call] + pub extern "Lua-C" fn next(&self) -> Result> { + Ok(self + .0 + .try_borrow_mut()? + .next() + .transpose()? + .map(lb_walk_dir_entry::new)) + } +} + +/// Entry inside of a directory on the filesystem obtained from [`lb_walk_dir`]. +#[derive(Debug)] +#[cdef] +pub struct lb_walk_dir_entry(#[opaque] DirEntry); + +#[metatype] +impl lb_walk_dir_entry { + pub(super) fn new(entry: DirEntry) -> Self { + Self(entry) + } + + /// Returns the full path of this entry. + pub extern "Lua-C" fn path(&self) -> String { + self.0.path().to_string_lossy().into() + } + + /// Returns the file name of this entry. + pub extern "Lua-C" fn name(&self) -> String { + self.0.file_name().to_string_lossy().into() + } + + /// Returns the type of this entry. + pub extern "Lua-C" fn r#type(&self) -> lb_file_type { + lb_file_type::new(self.0.file_type()) + } + + /// Returns the metadata for this entry. + /// + /// # Errors + /// + /// This function may throw if the metadata could not be retrieved. + pub extern "Lua-C" fn metadata(&self) -> Result { + Ok(lb_file_meta::new(self.0.metadata()?)) + } + + /// Returns `true` if this entry was created from a symbolic link. + pub extern "Lua-C" fn is_symlink(&self) -> bool { + self.0.path_is_symlink() + } + + /// Returns the depth of this entry in the walk. + pub extern "Lua-C" fn depth(&self) -> u32 { + self.0.depth() as u32 + } + + /// Returns the inode number for this entry. + #[cfg(unix)] + pub extern "Lua-C" fn ino(&self) -> u64 { + use walkdir::DirEntryExt; + self.0.ino() + } + + /// Returns the full path of this entry. + #[tostring] + pub extern "Lua" fn tostring(&self) -> String { + self.path() + } +} diff --git a/crates/lb_sqlite/src/mod.rs b/crates/lb_sqlite/src/mod.rs index 913e7a1..b83aa0d 100644 --- a/crates/lb_sqlite/src/mod.rs +++ b/crates/lb_sqlite/src/mod.rs @@ -13,7 +13,7 @@ use luaffi::{cdef, metatype}; /// [`require("lb:sqlite")`](https://www.lua.org/manual/5.1/manual.html#pdf-require). /// /// ```lua -/// local sqlite = require("lb:sqlite"); +/// local sqlite = require("lb:sqlite") /// ``` #[cdef(module = "lb:time")] pub struct lb_sqlitelib;