Compare commits
15 Commits
8c406a46b3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
da2598b534
|
|||
|
81cd901ea6
|
|||
|
f2a13df169
|
|||
|
9d7e35094c
|
|||
|
e03ef8a495
|
|||
|
fc4d27abf1
|
|||
|
3de17cb77e
|
|||
|
57f391a950
|
|||
|
99aa11e903
|
|||
|
5c257b0f74
|
|||
|
1808bee82a
|
|||
|
7768c5ec56
|
|||
|
fcdee34b42
|
|||
|
5846220e35
|
|||
|
b0efc9f783
|
68
Cargo.lock
generated
68
Cargo.lock
generated
@@ -579,6 +579,18 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
|
||||
|
||||
[[package]]
|
||||
name = "fallible-streaming-iterator"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@@ -601,6 +613,12 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
@@ -740,6 +758,18 @@ name = "hashbrown"
|
||||
version = "0.15.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hdrhistogram"
|
||||
@@ -1053,6 +1083,7 @@ dependencies = [
|
||||
"derive_more",
|
||||
"globset",
|
||||
"luaffi",
|
||||
"luaify",
|
||||
"luajit",
|
||||
"sysexits",
|
||||
"tempfile",
|
||||
@@ -1061,6 +1092,15 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lb_sqlite"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"lb",
|
||||
"luaffi",
|
||||
"rusqlite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.174"
|
||||
@@ -1099,6 +1139,18 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.34.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91632f3b4fb6bd1d72aa3d78f41ffecfcf2b1a6648d8c241dbe7dbfaf4875e15"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.22"
|
||||
@@ -1168,7 +1220,6 @@ dependencies = [
|
||||
"bstr",
|
||||
"luaffi",
|
||||
"luajit-sys",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1187,6 +1238,7 @@ dependencies = [
|
||||
"clap",
|
||||
"console-subscriber",
|
||||
"lb",
|
||||
"lb_sqlite",
|
||||
"luajit",
|
||||
"mimalloc",
|
||||
"owo-colors",
|
||||
@@ -1554,6 +1606,20 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "rusqlite"
|
||||
version = "0.36.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3de23c3319433716cf134eed225fe9986bc24f63bed9be9f20c329029e672dc7"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
"libsqlite3-sys",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.25"
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
resolver = "3"
|
||||
members = [
|
||||
"crates/lb",
|
||||
"crates/lb_sqlite",
|
||||
"crates/luaffi",
|
||||
"crates/luaffi_impl",
|
||||
"crates/luaify",
|
||||
@@ -35,16 +36,19 @@ name = "main"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
default = ["task", "fs", "net"]
|
||||
default = ["task", "time", "fs", "net", "sqlite"]
|
||||
task = ["lb/task"]
|
||||
time = ["lb/time"]
|
||||
fs = ["lb/fs"]
|
||||
net = ["lb/net"]
|
||||
sqlite = ["dep:lb_sqlite"]
|
||||
tokio-console = ["dep:console-subscriber"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.40", features = ["derive", "env"] }
|
||||
console-subscriber = { version = "0.4.1", optional = true }
|
||||
lb = { path = "crates/lb", features = ["runtime"] }
|
||||
lb_sqlite = { path = "crates/lb_sqlite", optional = true }
|
||||
luajit = { path = "crates/luajit", features = ["runtime"] }
|
||||
mimalloc = "0.1.47"
|
||||
owo-colors = "4.2.1"
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
default_job = "test"
|
||||
|
||||
[jobs.test]
|
||||
command = ["cargo", "test"]
|
||||
watch = ["*.lua"]
|
||||
need_stdout = true
|
||||
background = false
|
||||
|
||||
[jobs.doc]
|
||||
command = ["cargo", "doc", "--workspace"]
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ repository.workspace = true
|
||||
[features]
|
||||
runtime = ["tokio/rt"]
|
||||
task = ["tokio/rt", "tokio/time"]
|
||||
time = []
|
||||
fs = ["tokio/fs", "dep:walkdir", "dep:globset", "dep:tempfile"]
|
||||
net = ["tokio/net", "tokio/io-util"]
|
||||
|
||||
@@ -17,6 +18,7 @@ net = ["tokio/net", "tokio/io-util"]
|
||||
derive_more = { version = "2.0.1", features = ["full"] }
|
||||
globset = { version = "0.4.16", optional = true }
|
||||
luaffi = { path = "../luaffi" }
|
||||
luaify = { path = "../luaify" }
|
||||
luajit = { path = "../luajit" }
|
||||
sysexits = "0.9.0"
|
||||
tempfile = { version = "3.20.0", optional = true }
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
//! # Channel library
|
||||
//!
|
||||
//! The `lb:chan` library provides primitives for asynchronous communication between tasks via
|
||||
//! message passing channels.
|
||||
//!
|
||||
//! ## Exports
|
||||
//!
|
||||
//! See [`lb_chanlib`] for items exported by this library.
|
||||
use luaffi::{cdef, metatype};
|
||||
|
||||
/// Items exported by the `lb:chan` library.
|
||||
///
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:chan")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||
///
|
||||
/// ```lua
|
||||
/// local chan = require("lb:chan");
|
||||
/// ```
|
||||
#[cdef(module = "lb:chan")]
|
||||
pub struct lb_chanlib;
|
||||
|
||||
#[metatype]
|
||||
impl lb_chanlib {
|
||||
#[new]
|
||||
extern "Lua-C" fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
extern "Lua" fn unbounded(self) {
|
||||
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
|
||||
self.__unbounded(send, recv);
|
||||
(send, recv)
|
||||
}
|
||||
|
||||
extern "Lua" fn bounded(self, cap: u32) {
|
||||
assert(cap >= 0, "channel capacity must be nonnegative");
|
||||
let (send, recv) = (__new(__ct.lb_sender), __new(__ct.lb_receiver));
|
||||
self.__bounded(cap, send, recv);
|
||||
(send, recv)
|
||||
}
|
||||
|
||||
// extern "Lua-C" fn __unbounded(&self, s: *mut lb_sender, r: *mut lb_receiver) {
|
||||
// let (send, recv) = flume::unbounded();
|
||||
// unsafe {
|
||||
// ptr::write(s, lb_sender { send });
|
||||
// ptr::write(r, lb_receiver { recv });
|
||||
// }
|
||||
// }
|
||||
|
||||
// extern "Lua-C" fn __bounded(&self, cap: usize, s: *mut lb_sender, r: *mut lb_receiver) {
|
||||
// let (send, recv) = flume::bounded(cap);
|
||||
// unsafe {
|
||||
// ptr::write(s, lb_sender { send });
|
||||
// ptr::write(r, lb_receiver { recv });
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
// #[cdef]
|
||||
// pub struct lb_sender {
|
||||
// #[opaque]
|
||||
// send: Sender<c_int>,
|
||||
// }
|
||||
|
||||
// #[metatype]
|
||||
// impl lb_sender {
|
||||
// extern "Lua" fn send(self, value: _) {
|
||||
// let key = __ref(value);
|
||||
// }
|
||||
// }
|
||||
|
||||
// #[cdef]
|
||||
// pub struct lb_receiver {
|
||||
// #[opaque]
|
||||
// recv: Receiver<c_int>,
|
||||
// }
|
||||
|
||||
// #[metatype]
|
||||
// impl lb_receiver {}
|
||||
@@ -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** in Lua. 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<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// 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) in Lua.
|
||||
///
|
||||
/// ```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 cannot be read.
|
||||
pub async extern "Lua-C" fn read(path: &str) -> Result<Vec<u8>> {
|
||||
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 cannot be read.
|
||||
pub extern "Lua-C" fn read_sync(path: &str) -> Result<Vec<u8>> {
|
||||
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 cannot 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 cannot 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 cannot be read.
|
||||
pub async extern "Lua-C" fn read_dir(path: &str) -> Result<lb_read_dir> {
|
||||
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 cannot be read.
|
||||
pub extern "Lua-C" fn read_dir_sync(path: &str) -> Result<lb_read_dir_sync> {
|
||||
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<lb_glob_dir> {
|
||||
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<lb_glob_dir> {
|
||||
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 cannot be created.
|
||||
pub extern "Lua-C" fn temp_dir() -> Result<lb_temp_dir> {
|
||||
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 cannot be created.
|
||||
pub extern "Lua-C" fn temp_dir_in(path: &str) -> Result<lb_temp_dir> {
|
||||
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<tokio::fs::ReadDir>);
|
||||
|
||||
#[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 cannot be read.
|
||||
#[call]
|
||||
pub async extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry>> {
|
||||
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<std::fs::ReadDir>);
|
||||
|
||||
#[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 cannot be read.
|
||||
#[call]
|
||||
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_dir_entry_sync>> {
|
||||
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 cannot be determined.
|
||||
pub async extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||
Ok(lb_file_type::new(self.0.file_type().await?))
|
||||
}
|
||||
|
||||
/// Returns the metadata for this entry.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may throw if the metadata cannot be retrieved.
|
||||
pub async extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||
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 cannot be determined.
|
||||
pub extern "Lua-C" fn r#type(&self) -> Result<lb_file_type> {
|
||||
Ok(lb_file_type::new(self.0.file_type()?))
|
||||
}
|
||||
|
||||
/// Returns the metadata for this entry.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may throw if the metadata cannot be retrieved.
|
||||
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||
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 cannot be retrieved.
|
||||
pub extern "Lua-C" fn created(&self) -> Result<f64> {
|
||||
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 cannot be retrieved.
|
||||
pub extern "Lua-C" fn modified(&self) -> Result<f64> {
|
||||
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 cannot be retrieved.
|
||||
pub extern "Lua-C" fn accessed(&self) -> Result<f64> {
|
||||
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<std::fs::Permissions>);
|
||||
|
||||
#[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<walkdir::IntoIter>);
|
||||
|
||||
#[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 cannot be read.
|
||||
#[call]
|
||||
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
|
||||
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 cannot be retrieved.
|
||||
pub extern "Lua-C" fn metadata(&self) -> Result<lb_file_meta> {
|
||||
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<walkdir::IntoIter>,
|
||||
#[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 cannot be read.
|
||||
#[call]
|
||||
pub extern "Lua-C" fn next(&self) -> Result<Option<lb_walk_dir_entry>> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
83
crates/lb/src/fs/async.rs
Normal file
83
crates/lb/src/fs/async.rs
Normal file
@@ -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<ReadDir>);
|
||||
|
||||
#[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<Option<lb_dir_entry>> {
|
||||
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<lb_file_type> {
|
||||
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<lb_file_meta> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
46
crates/lb/src/fs/glob.rs
Normal file
46
crates/lb/src/fs/glob.rs
Normal file
@@ -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<IntoIter>,
|
||||
#[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<Option<lb_walk_dir_entry>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
190
crates/lb/src/fs/mod.rs
Normal file
190
crates/lb/src/fs/mod.rs
Normal file
@@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// 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<Vec<u8>> {
|
||||
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<Vec<u8>> {
|
||||
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<lb_read_dir> {
|
||||
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<lb_read_dir_sync> {
|
||||
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<lb_walk_dir> {
|
||||
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<lb_glob_dir> {
|
||||
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<lb_glob_dir> {
|
||||
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<lb_temp_dir> {
|
||||
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<lb_temp_dir> {
|
||||
Ok(lb_temp_dir::new(tempfile::tempdir_in(path)?))
|
||||
}
|
||||
}
|
||||
251
crates/lb/src/fs/sync.rs
Normal file
251
crates/lb/src/fs/sync.rs
Normal file
@@ -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<f64> {
|
||||
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<f64> {
|
||||
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<f64> {
|
||||
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<Permissions>);
|
||||
|
||||
#[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<ReadDir>);
|
||||
|
||||
#[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<Option<lb_dir_entry_sync>> {
|
||||
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<lb_file_type> {
|
||||
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<lb_file_meta> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
25
crates/lb/src/fs/temp.rs
Normal file
25
crates/lb/src/fs/temp.rs
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
90
crates/lb/src/fs/walk.rs
Normal file
90
crates/lb/src/fs/walk.rs
Normal file
@@ -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<IntoIter>);
|
||||
|
||||
#[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<Option<lb_walk_dir_entry>> {
|
||||
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<lb_file_meta> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
//! luby standard library
|
||||
#![warn(missing_docs)]
|
||||
#[cfg(feature = "task")]
|
||||
pub mod chan;
|
||||
//! luby core libraries.
|
||||
#[cfg(feature = "fs")]
|
||||
pub mod fs;
|
||||
#[cfg(feature = "net")]
|
||||
@@ -10,3 +7,5 @@ pub mod net;
|
||||
pub mod runtime;
|
||||
#[cfg(feature = "task")]
|
||||
pub mod task;
|
||||
#[cfg(feature = "time")]
|
||||
pub mod time;
|
||||
|
||||
1078
crates/lb/src/net.rs
1078
crates/lb/src/net.rs
File diff suppressed because it is too large
Load Diff
588
crates/lb/src/net/mod.rs
Normal file
588
crates/lb/src/net/mod.rs
Normal file
@@ -0,0 +1,588 @@
|
||||
//! Network library.
|
||||
//!
|
||||
//! The `lb:net` library provides an asynchronous network API for woring with TCP, UDP and IPC
|
||||
//! sockets.
|
||||
//!
|
||||
//! ## Exports
|
||||
//!
|
||||
//! See [`lb_netlib`] for items exported by this library.
|
||||
use derive_more::{From, FromStr};
|
||||
use luaffi::{cdef, marker::OneOf, metatype};
|
||||
use std::{
|
||||
cell::{BorrowError, BorrowMutError},
|
||||
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tokio::net::TcpSocket;
|
||||
|
||||
mod tcp;
|
||||
|
||||
pub use tcp::*;
|
||||
|
||||
/// 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),
|
||||
/// IP or socket address syntax error.
|
||||
#[error("{0}")]
|
||||
InvalidAddr(#[from] AddrParseError),
|
||||
/// Socket was already converted and can no longer be used.
|
||||
#[error("socket was already converted")]
|
||||
SocketConsumed,
|
||||
/// Socket was closed and can no longer be used.
|
||||
#[error("socket was closed")]
|
||||
SocketClosed,
|
||||
/// Specified socket half was not `"read"` or `"write"`.
|
||||
#[error("invalid socket half")]
|
||||
InvalidSocketHalf,
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// Items exported by the `lb:net` library.
|
||||
///
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net")
|
||||
/// ```
|
||||
#[cdef(module = "lb:net")]
|
||||
pub struct lb_netlib;
|
||||
|
||||
#[metatype]
|
||||
impl lb_netlib {
|
||||
#[new]
|
||||
extern "Lua-C" fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// An IPv4 address representing localhost: `127.0.0.1`
|
||||
pub extern "Lua-C" fn localhost() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv4Addr::LOCALHOST.into())
|
||||
}
|
||||
|
||||
/// An IPv6 address representing localhost: `::1`
|
||||
pub extern "Lua-C" fn localhost_v6() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv6Addr::LOCALHOST.into())
|
||||
}
|
||||
|
||||
/// An IPv4 address representing an unspecified address: `0.0.0.0`
|
||||
pub extern "Lua-C" fn unspecified() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv4Addr::UNSPECIFIED.into())
|
||||
}
|
||||
|
||||
/// An IPv6 address representing an unspecified address: `::`
|
||||
pub extern "Lua-C" fn unspecified_v6() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv6Addr::UNSPECIFIED.into())
|
||||
}
|
||||
|
||||
/// An IPv4 address representing the broadcast address: `255.255.255.255`
|
||||
pub extern "Lua-C" fn broadcast() -> lb_ipaddr {
|
||||
lb_ipaddr(Ipv4Addr::BROADCAST.into())
|
||||
}
|
||||
|
||||
/// Creates an IP address from the given input.
|
||||
///
|
||||
/// If `addr` is an [`lb_ipaddr`], a copy of that value is returned. If `addr` is an
|
||||
/// [`lb_socketaddr`], the IP part of the socket address is returned. Otherwise, parses `addr`
|
||||
/// as an IP address string. Both IPv4 and IPv6 addresses are accepted.
|
||||
///
|
||||
/// An address string may be something like `127.0.0.1`.
|
||||
///
|
||||
/// The type of the parsed address can be checked using [`is_v4`](lb_ipaddr::is_v4) or
|
||||
/// [`is_v6`](lb_ipaddr::is_v6) functions on the returned [`lb_ipaddr`](lb_ipaddr) value.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will throw an error if the input syntax is invalid.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net")
|
||||
/// local addr = net.ipaddr("192.168.1.1")
|
||||
///
|
||||
/// assert(addr:is_v4())
|
||||
/// ```
|
||||
pub extern "Lua" fn ipaddr(
|
||||
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||
) -> Result<lb_ipaddr> {
|
||||
if __istype(__ct.lb_ipaddr, addr) {
|
||||
__new(__ct.lb_ipaddr, addr) // copy constructor
|
||||
} else if __istype(__ct.lb_socketaddr, addr) {
|
||||
s.ip()
|
||||
} else {
|
||||
Self::__parse_ipaddr(addr)
|
||||
}
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __parse_ipaddr(addr: &str) -> Result<lb_ipaddr> {
|
||||
Ok(addr.parse()?)
|
||||
}
|
||||
|
||||
/// Creates a socket address from the given input.
|
||||
///
|
||||
/// A socket address is an IP address with a prescribed port number.
|
||||
///
|
||||
/// If `addr` is an [`lb_socketaddr`], a copy of that value is returned. If `addr` is an
|
||||
/// [`lb_ipaddr`], a socket address with that IP part is returned. Otherwise, parses `addr` as a
|
||||
/// socket address string. Both IPv4 and IPv6 addresses are accepted.
|
||||
///
|
||||
/// If `port` is specified, it is always used as the port part of the returned socket address.
|
||||
/// Otherwise, `0` is used as the default.
|
||||
///
|
||||
/// An address string may be something like `127.0.0.1:3000`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will throw an error if the input syntax is invalid.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net")
|
||||
/// local addr = net.socketaddr("::1", 8080)
|
||||
///
|
||||
/// assert(addr:ip():is_v6() and addr:ip():is_loopback())
|
||||
/// assert(addr:port() == 8080)
|
||||
/// ```
|
||||
pub extern "Lua" fn socketaddr(
|
||||
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||
port: Option<u16>,
|
||||
) -> Result<lb_socketaddr> {
|
||||
if port != () {
|
||||
Self::__new_skaddr(Self::ipaddr(addr), port)
|
||||
} else {
|
||||
if __istype(__ct.lb_socketaddr, addr) {
|
||||
__new(__ct.lb_socketaddr, addr) // copy constructor
|
||||
} else if __istype(__ct.lb_ipaddr, addr) {
|
||||
Self::__new_skaddr(addr, 0) // default port 0
|
||||
} else {
|
||||
Self::__parse_skaddr(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __new_skaddr(ip: &lb_ipaddr, port: u16) -> lb_socketaddr {
|
||||
SocketAddr::new(ip.0, port).into()
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __parse_skaddr(addr: &str) -> Result<lb_socketaddr> {
|
||||
Ok(if let Ok(addr) = addr.parse() {
|
||||
SocketAddr::new(addr, 0).into() // default port 0
|
||||
} else {
|
||||
addr.parse::<SocketAddr>()?.into()
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new TCP socket configured for IPv4.
|
||||
///
|
||||
/// This calls `socket(2)` with `AF_INET` and `SOCK_STREAM`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may throw an error if the socket could not be created.
|
||||
pub extern "Lua-C" fn tcp() -> Result<lb_tcpsocket> {
|
||||
Ok(lb_tcpsocket::new(TcpSocket::new_v4()?))
|
||||
}
|
||||
|
||||
/// Creates a new TCP socket configured for IPv6.
|
||||
///
|
||||
/// This calls `socket(2)` with `AF_INET6` and `SOCK_STREAM`.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may throw an error if the socket could not be created.
|
||||
pub extern "Lua-C" fn tcp_v6() -> Result<lb_tcpsocket> {
|
||||
Ok(lb_tcpsocket::new(TcpSocket::new_v6()?))
|
||||
}
|
||||
|
||||
/// Creates a new TCP socket bound to the given address and port.
|
||||
///
|
||||
/// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a
|
||||
/// new TCP socket configured to use either IPv4 or IPv6 depending on the type of the given
|
||||
/// address, and binds it to that address.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may throw an error if the socket could not be created or bound to the
|
||||
/// specified address.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net")
|
||||
/// local socket = net.bind_tcp("127.0.0.1")
|
||||
///
|
||||
/// assert(socket:local_addr():ip() == net.ipaddr("127.0.0.1"))
|
||||
/// socket:set_nodelay(true)
|
||||
/// ```
|
||||
pub extern "Lua" fn bind_tcp(
|
||||
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||
port: Option<u16>,
|
||||
) -> Result<lb_tcpsocket> {
|
||||
let addr = Self::socketaddr(addr, port);
|
||||
let socket;
|
||||
if addr.ip().is_v6() {
|
||||
socket = Self::tcp_v6();
|
||||
} else {
|
||||
socket = Self::tcp();
|
||||
}
|
||||
socket.bind(addr);
|
||||
socket
|
||||
}
|
||||
|
||||
/// Creates a new TCP socket listening on the given address and port.
|
||||
///
|
||||
/// This is a convenience function that combines [`bind_tcp`](Self::bind_tcp) and
|
||||
/// [`listen`](lb_tcpsocket::listen). It accepts the same arguments as
|
||||
/// [`socketaddr`](Self::socketaddr).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may throw an error if the socket could not be created, bound to the specified
|
||||
/// address, or could not transition to the listening state.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```lua,no_run
|
||||
/// local net = require("lb:net")
|
||||
/// local listener = net.listen_tcp("127.0.0.1")
|
||||
///
|
||||
/// assert(listener:local_addr():ip() == net.ipaddr("127.0.0.1"))
|
||||
///
|
||||
/// for stream in listener do
|
||||
/// print("client connected: ", stream:peer_addr())
|
||||
/// end
|
||||
/// ```
|
||||
pub extern "Lua" fn listen_tcp(
|
||||
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||
port: Option<u16>,
|
||||
) -> Result<lb_tcplistener> {
|
||||
Self::bind_tcp(addr, port).listen(1024)
|
||||
}
|
||||
|
||||
/// Establishes a new TCP connection to the server at the given address and port.
|
||||
///
|
||||
/// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a
|
||||
/// new TCP socket and connects it to the specified address.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may throw an error if the socket could not be created or connected to the
|
||||
/// specified address.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net")
|
||||
/// local listener = net.listen_tcp("127.0.0.1")
|
||||
/// local stream = net.connect_tcp("127.0.0.1", listener:local_addr():port())
|
||||
///
|
||||
/// assert(stream:peer_addr():ip() == net.ipaddr("127.0.0.1"))
|
||||
/// stream:write("Hello, server!\n")
|
||||
/// ```
|
||||
pub async extern "Lua" fn connect_tcp(
|
||||
addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>,
|
||||
port: Option<u16>,
|
||||
) -> Result<lb_tcpstream> {
|
||||
let addr = Self::socketaddr(addr, port);
|
||||
let socket;
|
||||
if addr.ip().is_v6() {
|
||||
socket = Self::tcp_v6();
|
||||
} else {
|
||||
socket = Self::tcp();
|
||||
}
|
||||
socket.connect(addr)
|
||||
}
|
||||
}
|
||||
|
||||
/// IP address, either IPv4 or IPv6.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// This example creates an [`lb_ipaddr`] by parsing an IP address string.
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net")
|
||||
/// local addr = net.ipaddr("127.0.0.1") -- ipv4 loopback address
|
||||
///
|
||||
/// assert(addr:is_v4() and addr:is_loopback())
|
||||
/// assert(tostring(addr) == "127.0.0.1")
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
||||
#[cdef]
|
||||
pub struct lb_ipaddr(#[opaque] IpAddr);
|
||||
|
||||
#[metatype]
|
||||
impl lb_ipaddr {
|
||||
/// Returns `true` if this is an IPv4 address.
|
||||
pub extern "Lua-C" fn is_v4(&self) -> bool {
|
||||
self.0.is_ipv4()
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an IPv6 address.
|
||||
pub extern "Lua-C" fn is_v6(&self) -> bool {
|
||||
self.0.is_ipv6()
|
||||
}
|
||||
|
||||
/// Returns the string `"v4"` if this is an IPv4 address, or `"v6"` if this is an IPv6 address.
|
||||
pub extern "Lua" fn family(&self) -> String {
|
||||
if self.is_v6() { "v6" } else { "v4" }
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a loopback address.
|
||||
///
|
||||
/// For IPv4, this is any address in the `127.0.0.0/8` range.
|
||||
///
|
||||
/// For IPv6, this is the address `::1`.
|
||||
pub extern "Lua-C" fn is_loopback(&self) -> bool {
|
||||
self.0.is_loopback()
|
||||
}
|
||||
|
||||
/// Returns `true` if this address is unspecified.
|
||||
///
|
||||
/// For IPv4, this is the address `0.0.0.0`.
|
||||
///
|
||||
/// For IPv6, this is the address `::`.
|
||||
pub extern "Lua-C" fn is_unspecified(&self) -> bool {
|
||||
self.0.is_unspecified()
|
||||
}
|
||||
|
||||
/// Returns `true` if this address is a multicast address.
|
||||
///
|
||||
/// For IPv4, this is any address in the `224.0.0.0/4` range.
|
||||
///
|
||||
/// For IPv6, this is any address in the `ff00::/8` range.
|
||||
pub extern "Lua-C" fn is_multicast(&self) -> bool {
|
||||
self.0.is_multicast()
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an IPv4 private address.
|
||||
///
|
||||
/// For IPv4, this is any address in one of these ranges:
|
||||
///
|
||||
/// - `10.0.0.0/8`
|
||||
/// - `172.16.0.0/12`
|
||||
/// - `192.168.0.0/16`
|
||||
///
|
||||
/// For IPv6, this always returns `false`.
|
||||
pub extern "Lua-C" fn is_v4_private(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => v4.is_private(),
|
||||
IpAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an IPv4 link-local address.
|
||||
///
|
||||
/// For IPv4, this is any address in the `169.254.0.0/16` range.
|
||||
///
|
||||
/// For IPv6, this always returns `false`.
|
||||
pub extern "Lua-C" fn is_v4_link_local(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => v4.is_link_local(),
|
||||
IpAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an IPv4 broadcast address.
|
||||
///
|
||||
/// For IPv4, this is the address `255.255.255.255`.
|
||||
///
|
||||
/// For IPv6, this always returns `false`.
|
||||
pub extern "Lua-C" fn is_v4_broadcast(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => v4.is_broadcast(),
|
||||
IpAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an IPv4 documentation address.
|
||||
///
|
||||
/// For IPv4, this is any address in one of these ranges:
|
||||
///
|
||||
/// - `192.0.2.0/24` (TEST-NET-1)
|
||||
/// - `198.51.100.0/24` (TEST-NET-2)
|
||||
/// - `203.0.113.0/24` (TEST-NET-3)
|
||||
///
|
||||
/// For IPv6, this always returns `false`.
|
||||
pub extern "Lua-C" fn is_v4_documentation(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => v4.is_documentation(),
|
||||
IpAddr::V6(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an IPv6 unique local address.
|
||||
///
|
||||
/// For IPv4, this always returns `false`.
|
||||
///
|
||||
/// For IPv6, this is any address in the `fc00::/7` range.
|
||||
pub extern "Lua-C" fn is_v6_unique_local(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(_) => false,
|
||||
IpAddr::V6(v6) => v6.is_unique_local(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this is an IPv6 unicast address with link-local scope.
|
||||
///
|
||||
/// For IPv4, this always returns `false`.
|
||||
///
|
||||
/// For IPv6, this is any address in the `fe80::/10` range.
|
||||
pub extern "Lua-C" fn is_v6_unicast_link_local(&self) -> bool {
|
||||
match self.0 {
|
||||
IpAddr::V4(_) => false,
|
||||
IpAddr::V6(v6) => v6.is_unicast_link_local(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this address to IPv4.
|
||||
///
|
||||
/// For IPv4, this returns the address unchanged.
|
||||
///
|
||||
/// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible
|
||||
/// IPv6 address. Otherwise, this returns `nil`.
|
||||
pub extern "Lua-C" fn to_v4(&self) -> Option<Self> {
|
||||
match self.0 {
|
||||
IpAddr::V4(_) => Some(*self),
|
||||
IpAddr::V6(v6) => v6.to_ipv4().map(|v| Self(v.into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this address to IPv6.
|
||||
///
|
||||
/// For IPv4, this returns the IPv4-mapped IPv6 address as defined by
|
||||
/// [`Ipv4Addr::to_ipv6_mapped`].
|
||||
///
|
||||
/// For IPv6, this returns the address unchanged.
|
||||
pub extern "Lua-C" fn to_v6(&self) -> Self {
|
||||
match self.0 {
|
||||
IpAddr::V4(v4) => Self(v4.to_ipv6_mapped().into()),
|
||||
IpAddr::V6(_) => *self,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the canonical form of this address.
|
||||
///
|
||||
/// For IPv4, this returns the address unchanged.
|
||||
///
|
||||
/// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible
|
||||
/// IPv6 address. Otherwise, this returns the address unchanged.
|
||||
pub extern "Lua-C" fn to_canonical(&self) -> Self {
|
||||
match self.0 {
|
||||
IpAddr::V4(_) => *self,
|
||||
IpAddr::V6(v6) => v6.to_ipv4().map_or(*self, |v| Self(v.into())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the given addresses are equal.
|
||||
///
|
||||
/// Two addresses are considered equal if they are of the same family (IPv4 or IPv6) and
|
||||
/// represent the same address in octets.
|
||||
#[eq]
|
||||
pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool {
|
||||
left.0 == right.0
|
||||
}
|
||||
|
||||
/// Returns `true` if the left address is less than the right address.
|
||||
///
|
||||
/// IPv4 addresses are always less than IPv6 addresses.
|
||||
#[lt]
|
||||
pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool {
|
||||
left.0 < right.0
|
||||
}
|
||||
|
||||
/// Returns `true` if the left address is less than or equal to the right address.
|
||||
///
|
||||
/// IPv4 addresses are always less than IPv6 addresses.
|
||||
#[le]
|
||||
pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool {
|
||||
left.0 <= right.0
|
||||
}
|
||||
|
||||
/// Returns the string representation of this address.
|
||||
#[tostring]
|
||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Socket address, which is an IP address with a port number.
|
||||
///
|
||||
/// This represents an IP address with a prescribed port, such as `127.0.0.1:8080` or `[::1]:443`.
|
||||
/// It is used to specify endpoints for network connections and listeners, and can be constructed by
|
||||
/// [`socketaddr`](lb_netlib::socketaddr).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net")
|
||||
/// local addr = net.socketaddr("127.0.0.1:8080")
|
||||
///
|
||||
/// assert(addr:ip() == net.ipaddr("127.0.0.1") and addr:port() == 8080)
|
||||
/// assert(tostring(addr) == "127.0.0.1:8080")
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)]
|
||||
#[cdef]
|
||||
pub struct lb_socketaddr(#[opaque] SocketAddr);
|
||||
|
||||
#[metatype]
|
||||
impl lb_socketaddr {
|
||||
/// The IP part of this address.
|
||||
pub extern "Lua-C" fn ip(&self) -> lb_ipaddr {
|
||||
self.0.ip().into()
|
||||
}
|
||||
|
||||
/// The port part of this address.
|
||||
pub extern "Lua-C" fn port(&self) -> u16 {
|
||||
self.0.port()
|
||||
}
|
||||
|
||||
/// Returns a new socket address with the given IP address and the same port.
|
||||
pub extern "Lua-C" fn with_ip(&self, ip: &lb_ipaddr) -> Self {
|
||||
SocketAddr::new(ip.0, self.port()).into()
|
||||
}
|
||||
|
||||
/// Returns a new socket address with the given port and the same IP address.
|
||||
pub extern "Lua-C" fn with_port(&self, port: u16) -> Self {
|
||||
SocketAddr::new(self.ip().0, port).into()
|
||||
}
|
||||
|
||||
/// Returns `true` if the given addresses are equal.
|
||||
#[eq]
|
||||
pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool {
|
||||
left.0 == right.0
|
||||
}
|
||||
|
||||
/// Returns `true` if the left address is less than the right address.
|
||||
#[lt]
|
||||
pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool {
|
||||
left.0 < right.0
|
||||
}
|
||||
|
||||
/// Returns `true` if the left address is less than or equal to the right address.
|
||||
#[le]
|
||||
pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool {
|
||||
left.0 <= right.0
|
||||
}
|
||||
|
||||
/// Returns the string representation of this address.
|
||||
#[tostring]
|
||||
pub extern "Lua-C" fn tostring(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
622
crates/lb/src/net/tcp.rs
Normal file
622
crates/lb/src/net/tcp.rs
Normal file
@@ -0,0 +1,622 @@
|
||||
use super::*;
|
||||
use luaffi::{cdef, marker::fun, metatype};
|
||||
use luajit::LUA_NOREF;
|
||||
use std::io::ErrorKind;
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
ffi::c_int,
|
||||
time::Duration,
|
||||
};
|
||||
use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt, Interest},
|
||||
net::{TcpListener, TcpSocket, TcpStream},
|
||||
};
|
||||
|
||||
/// TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`].
|
||||
///
|
||||
/// This type represents a TCP socket in its initial state, before it is connected or set to listen.
|
||||
/// It can be configured (e.g., socket options, bind address) before being converted to an
|
||||
/// [`lb_tcpstream`] (via [`connect`](lb_tcpsocket::connect)) or [`lb_tcplistener`] (via
|
||||
/// [`listen`](lb_tcpsocket::listen)), after which it can no longer be used.
|
||||
///
|
||||
/// Methods on this type may fail if the operating system does not support the requested operation.
|
||||
#[derive(Debug)]
|
||||
#[cdef]
|
||||
pub struct lb_tcpsocket(#[opaque] RefCell<Option<TcpSocket>>);
|
||||
|
||||
#[metatype]
|
||||
impl lb_tcpsocket {
|
||||
pub(super) fn new(socket: TcpSocket) -> Self {
|
||||
Self(RefCell::new(Some(socket)))
|
||||
}
|
||||
|
||||
fn socket<'s>(&'s self) -> Result<Ref<'s, TcpSocket>> {
|
||||
let socket = self.0.borrow();
|
||||
match *socket {
|
||||
Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())),
|
||||
None => Err(Error::SocketConsumed),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the value of the `SO_KEEPALIVE` option on this socket.
|
||||
pub extern "Lua-C" fn keepalive(&self) -> Result<bool> {
|
||||
Ok(self.socket()?.keepalive()?)
|
||||
}
|
||||
|
||||
/// Sets value for the `SO_KEEPALIVE` option on this socket.
|
||||
///
|
||||
/// This enables or disables periodic keepalive messages on the connection.
|
||||
pub extern "Lua-C" fn set_keepalive(&self, enabled: bool) -> Result<()> {
|
||||
Ok(self.socket()?.set_keepalive(enabled)?)
|
||||
}
|
||||
|
||||
/// Gets the value of the `SO_REUSEADDR` option on this socket.
|
||||
pub extern "Lua-C" fn reuseaddr(&self) -> Result<bool> {
|
||||
Ok(self.socket()?.reuseaddr()?)
|
||||
}
|
||||
|
||||
/// Sets value for the `SO_REUSEADDR` option on this socket.
|
||||
///
|
||||
/// This allows the socket to bind to an address that is already in use.
|
||||
pub extern "Lua-C" fn set_reuseaddr(&self, enabled: bool) -> Result<()> {
|
||||
Ok(self.socket()?.set_reuseaddr(enabled)?)
|
||||
}
|
||||
|
||||
/// Gets the value of the `SO_REUSEPORT` option on this socket.
|
||||
pub extern "Lua-C" fn reuseport(&self) -> Result<bool> {
|
||||
Ok(self.socket()?.reuseport()?)
|
||||
}
|
||||
|
||||
/// Sets value for the `SO_REUSEPORT` option on this socket.
|
||||
///
|
||||
/// This allows multiple sockets to bind to the same port.
|
||||
pub extern "Lua-C" fn set_reuseport(&self, enabled: bool) -> Result<()> {
|
||||
Ok(self.socket()?.set_reuseport(enabled)?)
|
||||
}
|
||||
|
||||
/// Gets the value of the `SO_SNDBUF` option on this socket.
|
||||
pub extern "Lua-C" fn sendbuf(&self) -> Result<u32> {
|
||||
Ok(self.socket()?.send_buffer_size()?)
|
||||
}
|
||||
|
||||
/// Sets value for the `SO_SNDBUF` option on this socket.
|
||||
///
|
||||
/// This sets the size of the send buffer in bytes.
|
||||
pub extern "Lua-C" fn set_sendbuf(&self, size: u32) -> Result<()> {
|
||||
Ok(self.socket()?.set_send_buffer_size(size)?)
|
||||
}
|
||||
|
||||
/// Gets the value of the `SO_RCVBUF` option on this socket.
|
||||
pub extern "Lua-C" fn recvbuf(&self) -> Result<u32> {
|
||||
Ok(self.socket()?.recv_buffer_size()?)
|
||||
}
|
||||
|
||||
/// Sets value for the `SO_RCVBUF` option on this socket.
|
||||
///
|
||||
/// This sets the size of the receive buffer in bytes.
|
||||
pub extern "Lua-C" fn set_recvbuf(&self, size: u32) -> Result<()> {
|
||||
Ok(self.socket()?.set_recv_buffer_size(size)?)
|
||||
}
|
||||
|
||||
/// Gets the value of the `SO_LINGER` option on this socket, in seconds.
|
||||
pub extern "Lua-C" fn linger(&self) -> Result<f64> {
|
||||
Ok(self
|
||||
.socket()?
|
||||
.linger()?
|
||||
.map(|n| n.as_secs_f64())
|
||||
.unwrap_or(0.))
|
||||
}
|
||||
|
||||
/// Sets the value of the `SO_LINGER` option on this socket.
|
||||
///
|
||||
/// This controls how long the socket will remain open after close if unsent data is present.
|
||||
pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> {
|
||||
let secs = secs.max(0.);
|
||||
Ok(self
|
||||
.socket()?
|
||||
.set_linger((secs != 0.).then_some(Duration::from_secs_f64(secs)))?)
|
||||
}
|
||||
|
||||
/// Gets the value of the `TCP_NODELAY` option on this socket.
|
||||
pub extern "Lua-C" fn nodelay(&self) -> Result<bool> {
|
||||
Ok(self.socket()?.nodelay()?)
|
||||
}
|
||||
|
||||
/// Sets the value of the `TCP_NODELAY` option on this socket.
|
||||
///
|
||||
/// This enables or disables Nagle's algorithm, which delays sending small packets.
|
||||
pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> {
|
||||
Ok(self.socket()?.set_nodelay(enabled)?)
|
||||
}
|
||||
|
||||
/// Gets the local address that this socket is bound to.
|
||||
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
|
||||
Ok(self.socket()?.local_addr()?.into())
|
||||
}
|
||||
|
||||
/// Binds this socket to the given local address.
|
||||
pub extern "Lua-C" fn bind(&self, addr: &lb_socketaddr) -> Result<()> {
|
||||
Ok(self.socket()?.bind(addr.0)?)
|
||||
}
|
||||
|
||||
/// Transitions this socket to the listening state.
|
||||
///
|
||||
/// This consumes the socket and returns a new [`lb_tcplistener`] that can accept incoming
|
||||
/// connections. This socket object can no longer be used after this call.
|
||||
pub extern "Lua-C" fn listen(&self, backlog: u32) -> Result<lb_tcplistener> {
|
||||
let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?;
|
||||
Ok(lb_tcplistener::new(socket.listen(backlog)?))
|
||||
}
|
||||
|
||||
/// Connects this socket to the given remote socket address, transitioning it to an established
|
||||
/// state.
|
||||
///
|
||||
/// This consumes the socket and returns a new [`lb_tcpstream`] that can be used to send and
|
||||
/// receive data. This socket object can no longer be used after this call.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function may throw an error if connection could not be established to the given remote
|
||||
/// address.
|
||||
pub async extern "Lua-C" fn connect(&self, addr: &lb_socketaddr) -> Result<lb_tcpstream> {
|
||||
let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?;
|
||||
Ok(lb_tcpstream::new(socket.connect(addr.0).await?))
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP connection between a local and a remote socket.
|
||||
///
|
||||
/// This represents an established TCP connection. It is created by connecting an [`lb_tcpsocket`]
|
||||
/// to a remote socket (via [`connect`](lb_tcpsocket::connect)) or accepting a connection from an
|
||||
/// [`lb_tcplistener`] (via [`accept`](lb_tcplistener::accept)). It provides methods for reading
|
||||
/// from and writing to the stream asynchronously.
|
||||
///
|
||||
/// The stream supports reading and writing data in both directions concurrently. Typically you
|
||||
/// would spawn one reader task and one writer task to handle incoming and outgoing data
|
||||
/// respectively. Connection is closed when this object goes out of scope and gets garbage
|
||||
/// collected, or when [`close`](Self::close) is explicitly called.
|
||||
///
|
||||
/// Methods on this type may fail if the operating system does not support the requested operation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// This examples spawns a reader task and a writer task to operate on the stream concurrently.
|
||||
///
|
||||
/// ```lua,no_run
|
||||
/// local task = require("lb:task")
|
||||
/// local net = require("lb:net")
|
||||
/// local socket = net.connect_tcp("127.0.0.1:1234")
|
||||
///
|
||||
/// print("local address: ", socket:local_addr())
|
||||
/// print("remote address: ", socket:peer_addr())
|
||||
///
|
||||
/// local reader = spawn(function()
|
||||
/// for chunk in socket, 1024 do
|
||||
/// print("received: ", chunk)
|
||||
/// end
|
||||
///
|
||||
/// print("done reading")
|
||||
/// end)
|
||||
///
|
||||
/// local writer = spawn(function()
|
||||
/// for i = 1, 10 do
|
||||
/// local msg = ("message %d"):format(i)
|
||||
/// socket:write(msg)
|
||||
/// print("sent: ", msg)
|
||||
/// end
|
||||
///
|
||||
/// print("done writing")
|
||||
/// end)
|
||||
///
|
||||
/// task.join(reader, writer)
|
||||
/// ```
|
||||
///
|
||||
/// The above example uses the socket as an iterator in a generic `for` loop to read data in chunks
|
||||
/// of up to 1024 bytes. It is equivalent to the following:
|
||||
///
|
||||
/// ```lua,no_run
|
||||
/// local net = require("lb:net")
|
||||
/// local socket = net.connect_tcp("127.0.0.1:1234")
|
||||
///
|
||||
/// while true do
|
||||
/// local chunk = socket:read_partial(1024)
|
||||
/// if chunk == nil then break end
|
||||
/// print("received: ", chunk)
|
||||
/// end
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[cdef]
|
||||
pub struct lb_tcpstream {
|
||||
#[opaque]
|
||||
read: RefCell<Option<OwnedReadHalf>>,
|
||||
#[opaque]
|
||||
write: RefCell<Option<OwnedWriteHalf>>,
|
||||
}
|
||||
|
||||
#[metatype]
|
||||
impl lb_tcpstream {
|
||||
pub(super) fn new(stream: TcpStream) -> Self {
|
||||
let (read, write) = stream.into_split();
|
||||
Self {
|
||||
read: RefCell::new(Some(read)),
|
||||
write: RefCell::new(Some(write)),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_half<'s>(&'s self) -> Result<RefMut<'s, OwnedReadHalf>> {
|
||||
let read = self.read.try_borrow_mut()?;
|
||||
match *read {
|
||||
Some(_) => Ok(RefMut::map(read, |s| s.as_mut().unwrap())),
|
||||
None => Err(Error::SocketClosed),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_half<'s>(&'s self) -> Result<RefMut<'s, OwnedWriteHalf>> {
|
||||
let write = self.write.try_borrow_mut()?;
|
||||
match *write {
|
||||
Some(_) => Ok(RefMut::map(write, |s| s.as_mut().unwrap())),
|
||||
None => Err(Error::SocketClosed),
|
||||
}
|
||||
}
|
||||
|
||||
/// The local socket address that this stream is bound to.
|
||||
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
|
||||
Ok(self.read_half()?.local_addr()?.into())
|
||||
}
|
||||
|
||||
/// The remote socket address that this stream is connected to.
|
||||
pub extern "Lua-C" fn peer_addr(&self) -> Result<lb_socketaddr> {
|
||||
Ok(self.read_half()?.peer_addr()?.into())
|
||||
}
|
||||
|
||||
/// Waits for this stream to be ready in the given half.
|
||||
///
|
||||
/// The argument `half` can be `"read"` for the readable half, `"write"` for the writable half,
|
||||
/// or `nil` for both.
|
||||
pub async extern "Lua-C" fn ready(&self, half: Option<&str>) -> Result<()> {
|
||||
self.read_half()?
|
||||
.ready(match half {
|
||||
Some("read") => Interest::READABLE,
|
||||
Some("write") => Interest::WRITABLE,
|
||||
None => Interest::READABLE | Interest::WRITABLE,
|
||||
_ => Err(Error::InvalidSocketHalf)?,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Closes this stream in the given half.
|
||||
///
|
||||
/// The argument `half` can be `"read"` for the readable half, `"write"` for the writable half,
|
||||
/// or `nil` for both.
|
||||
///
|
||||
/// Once the half is closed, it can no longer be used for reading or writing for that half. Once
|
||||
/// both halves are closed, the stream is fully shut down.
|
||||
pub extern "Lua-C" fn close(&self, half: Option<&str>) -> Result<()> {
|
||||
Ok(match half {
|
||||
Some("read") => drop(self.read.try_borrow_mut()?.take()),
|
||||
Some("write") => drop(self.write.try_borrow_mut()?.take()),
|
||||
None => drop((
|
||||
self.read.try_borrow_mut()?.take(),
|
||||
self.write.try_borrow_mut()?.take(),
|
||||
)),
|
||||
_ => Err(Error::InvalidSocketHalf)?,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_disc(err: ErrorKind) -> bool {
|
||||
matches!(
|
||||
err,
|
||||
ErrorKind::ConnectionReset // graceful shutdown
|
||||
| ErrorKind::BrokenPipe // abrupt shutdown
|
||||
| ErrorKind::UnexpectedEof // could not read requested amount of data
|
||||
| ErrorKind::WriteZero // could not write requested amount of data
|
||||
)
|
||||
}
|
||||
|
||||
/// Reads exactly `len` bytes from this stream.
|
||||
///
|
||||
/// If the connection was closed, this returns `nil`.
|
||||
pub async extern "Lua-C" fn read(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||
let mut buf = vec![0; len as usize];
|
||||
Ok(match self.read_half()?.read_exact(&mut buf).await {
|
||||
Ok(_) => Some(buf),
|
||||
Err(err) if Self::is_disc(err.kind()) => None,
|
||||
Err(err) => return Err(err.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Reads up to `len` bytes from this stream.
|
||||
///
|
||||
/// The returned bytes may be less than `len` in length if the stream had less data available in
|
||||
/// queue. If there was no data available or the connection was closed, this returns `nil`.
|
||||
pub async extern "Lua-C" fn read_partial(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||
let mut buf = vec![0; len as usize];
|
||||
Ok(match self.read_half()?.read(&mut buf).await {
|
||||
Ok(0) => None,
|
||||
Ok(n) => Some({
|
||||
buf.truncate(n);
|
||||
buf
|
||||
}),
|
||||
Err(err) if Self::is_disc(err.kind()) => None,
|
||||
Err(err) => return Err(err.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts to read up to `len` bytes from this stream without waiting.
|
||||
///
|
||||
/// The returned bytes may be less than `len` in length if the stream had less data available in
|
||||
/// queue. If there was no data available or the connection was closed, this returns `nil`.
|
||||
pub extern "Lua-C" fn try_read(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||
let mut buf = vec![0; len as usize];
|
||||
Ok(match self.read_half()?.try_read(&mut buf) {
|
||||
Ok(0) => None,
|
||||
Ok(n) => Some({
|
||||
buf.truncate(n);
|
||||
buf
|
||||
}),
|
||||
Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None,
|
||||
Err(err) => return Err(err.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Writes exactly the given bytes to this stream.
|
||||
///
|
||||
/// If the connection was closed, this returns `false`.
|
||||
pub async extern "Lua-C" fn write(&self, buf: &[u8]) -> Result<bool> {
|
||||
Ok(match self.write_half()?.write_all(buf).await {
|
||||
Ok(()) => true,
|
||||
Err(err) if Self::is_disc(err.kind()) => false,
|
||||
Err(err) => return Err(err.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Writes the given bytes to this stream, and returns the number of bytes successfully written.
|
||||
///
|
||||
/// The returned number may be less than the length of `buf` if there was not enough space in
|
||||
/// queue. If the connection was closed, this returns `nil`.
|
||||
pub async extern "Lua-C" fn write_partial(&self, buf: &[u8]) -> Result<Option<u32>> {
|
||||
Ok(match self.write_half()?.write(buf).await {
|
||||
Ok(n) => Some(n as u32),
|
||||
Err(err) if Self::is_disc(err.kind()) => None,
|
||||
Err(err) => return Err(err.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempts to write the given bytes to this stream without waiting, and returns the number of
|
||||
/// bytes successfully written.
|
||||
///
|
||||
/// The returned number may be less than the length of `buf` if there was not enough space in
|
||||
/// queue. If the connection was closed, this returns `nil`.
|
||||
pub extern "Lua-C" fn try_write(&self, buf: &[u8]) -> Result<Option<u32>> {
|
||||
Ok(match self.write_half()?.try_write(buf) {
|
||||
Ok(n) => Some(n as u32),
|
||||
Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None,
|
||||
Err(err) => return Err(err.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Peeks up to `len` bytes at incoming data without consuming it.
|
||||
///
|
||||
/// Successive calls will return the same data until it is consumed by the [`read*`](Self::read)
|
||||
/// family of functions.
|
||||
pub async extern "Lua-C" fn peek(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||
let mut buf = vec![0; len as usize];
|
||||
Ok(match self.read_half()?.peek(&mut buf).await {
|
||||
Ok(0) => None,
|
||||
Ok(n) => Some({
|
||||
buf.truncate(n);
|
||||
buf
|
||||
}),
|
||||
Err(err) if Self::is_disc(err.kind()) => None,
|
||||
Err(err) => return Err(err.into()),
|
||||
})
|
||||
}
|
||||
|
||||
/// Alias for [`read_partial`](Self::read_partial).
|
||||
#[call]
|
||||
pub async extern "Lua" fn call(&self, len: u32) -> Result<Option<Vec<u8>>> {
|
||||
self.read_partial(len)
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP socket server, listening for connections.
|
||||
///
|
||||
/// This type represents a TCP server socket that can accept incoming connections. It is created by
|
||||
/// transitioning an [`lb_tcpsocket`] to the listening state via [`listen`](lb_tcpsocket::listen).
|
||||
///
|
||||
/// Methods on this type may fail if the operating system does not support the requested operation.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// The listener can be used as an iterator in a generic `for` loop to accept incoming connections:
|
||||
///
|
||||
/// ```lua,no_run
|
||||
/// local net = require("lb:net")
|
||||
/// local listener = net.listen_tcp("127.0.0.1")
|
||||
///
|
||||
/// print("listening on: ", listener:local_addr())
|
||||
///
|
||||
/// for stream in listener do
|
||||
/// print("accepted connection from: ", stream:peer_addr())
|
||||
/// print("local address: ", stream:local_addr())
|
||||
///
|
||||
/// spawn(function()
|
||||
/// stream:write("hello from server\n")
|
||||
/// stream:close()
|
||||
/// end)
|
||||
/// end
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
#[cdef]
|
||||
pub struct lb_tcplistener {
|
||||
#[opaque]
|
||||
listener: TcpListener,
|
||||
__on_accept_ref: c_int,
|
||||
}
|
||||
|
||||
#[metatype]
|
||||
impl lb_tcplistener {
|
||||
pub(super) fn new(listener: TcpListener) -> Self {
|
||||
Self {
|
||||
listener,
|
||||
__on_accept_ref: LUA_NOREF,
|
||||
}
|
||||
}
|
||||
|
||||
/// The local socket address that this listener is bound to.
|
||||
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
|
||||
Ok(self.listener.local_addr()?.into())
|
||||
}
|
||||
|
||||
/// Registers a callback to be invoked with each new incoming connection before it is converted
|
||||
/// to an [`lb_tcpstream`].
|
||||
///
|
||||
/// The callback receives a temporary [`lb_tcplistener_stream`] object, which can be used to log
|
||||
/// incoming connections or configure socket options (such as
|
||||
/// [`set_nodelay`](lb_tcplistener_stream), [`set_linger`](lb_tcplistener_stream), etc.) before
|
||||
/// it is converted to an [`lb_tcpstream`]. The callback is called synchronously during
|
||||
/// [`accept`](Self::accept) and should complete as quickly as possible. The provided
|
||||
/// configurable object is only valid within the callback and is converted to an
|
||||
/// [`lb_tcpstream`] as soon as it returns.
|
||||
///
|
||||
/// If a callback already exists, it is replaced with the new one.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```lua
|
||||
/// local net = require("lb:net")
|
||||
/// local listener = net.listen_tcp("127.0.0.1")
|
||||
///
|
||||
/// listener:on_accept(function(stream)
|
||||
/// print("accepted connection from: ", stream:peer_addr())
|
||||
/// print("local address: ", stream:local_addr())
|
||||
///
|
||||
/// stream:set_nodelay(true)
|
||||
/// end)
|
||||
/// ```
|
||||
pub extern "Lua" fn on_accept(&self, cb: fun<(&lb_tcplistener_stream,), ()>) {
|
||||
assert(
|
||||
rawequal(cb, ()) || r#type(cb) == "function",
|
||||
concat!("function expected in argument 'cb', got ", r#type(cb)),
|
||||
);
|
||||
__unref(self.__on_accept_ref);
|
||||
self.__on_accept_ref = __ref(cb);
|
||||
}
|
||||
|
||||
/// Accepts a new incoming TCP connection.
|
||||
///
|
||||
/// If an [`on_accept`](Self::on_accept) callback is registered, it is invoked with a temporary
|
||||
/// [`lb_tcplistener_stream`] object representing the new connection. This allows configuration
|
||||
/// of socket options for this specific connection, before the stream is converted to an
|
||||
/// [`lb_tcpstream`] and returned for the connection to be read from or written to.
|
||||
pub async extern "Lua" fn accept(&self) -> Result<lb_tcpstream> {
|
||||
let stream = self.__accept();
|
||||
let on_accept = __registry[self.__on_accept_ref];
|
||||
if !rawequal(on_accept, ()) {
|
||||
on_accept(stream);
|
||||
}
|
||||
stream.__convert()
|
||||
}
|
||||
|
||||
async extern "Lua-C" fn __accept(&self) -> Result<lb_tcplistener_stream> {
|
||||
let (stream, _) = self.listener.accept().await?;
|
||||
Ok(lb_tcplistener_stream::new(stream))
|
||||
}
|
||||
|
||||
/// Alias for [`accept`](Self::accept).
|
||||
#[call]
|
||||
pub async extern "Lua" fn call(&self) -> Result<lb_tcpstream> {
|
||||
self.accept()
|
||||
}
|
||||
|
||||
#[gc]
|
||||
extern "Lua" fn gc(&self) {
|
||||
__unref(self.__on_accept_ref);
|
||||
}
|
||||
}
|
||||
|
||||
/// TCP connection that has just been accepted by [`lb_tcplistener`].
|
||||
///
|
||||
/// This type is passed to the [`on_accept`](lb_tcplistener::on_accept) callback on
|
||||
/// [`lb_tcplistener`], allowing socket options to be set before the stream is converted to an
|
||||
/// [`lb_tcpstream`]. After conversion, this object can no longer be used.
|
||||
///
|
||||
/// Methods on this type may fail if the operating system does not support the requested operation.
|
||||
#[derive(Debug)]
|
||||
#[cdef]
|
||||
pub struct lb_tcplistener_stream(#[opaque] RefCell<Option<TcpStream>>);
|
||||
|
||||
#[metatype]
|
||||
impl lb_tcplistener_stream {
|
||||
fn new(stream: TcpStream) -> Self {
|
||||
Self(RefCell::new(Some(stream)))
|
||||
}
|
||||
|
||||
fn stream<'s>(&'s self) -> Result<Ref<'s, TcpStream>> {
|
||||
let socket = self.0.borrow();
|
||||
match *socket {
|
||||
Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())),
|
||||
None => Err(Error::SocketConsumed),
|
||||
}
|
||||
}
|
||||
|
||||
/// The local socket address that the listener is bound to.
|
||||
pub extern "Lua-C" fn local_addr(&self) -> Result<lb_socketaddr> {
|
||||
Ok(self.stream()?.local_addr()?.into())
|
||||
}
|
||||
|
||||
/// The remote socket address that this stream is connected to.
|
||||
pub extern "Lua-C" fn peer_addr(&self) -> Result<lb_socketaddr> {
|
||||
Ok(self.stream()?.peer_addr()?.into())
|
||||
}
|
||||
|
||||
/// Gets the value of the `TCP_NODELAY` option on this stream.
|
||||
pub extern "Lua-C" fn nodelay(&self) -> Result<bool> {
|
||||
Ok(self.stream()?.nodelay()?)
|
||||
}
|
||||
|
||||
/// Sets the value of the `TCP_NODELAY` option on this stream.
|
||||
///
|
||||
/// This enables or disables Nagle's algorithm, which delays sending small packets.
|
||||
pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> {
|
||||
Ok(self.stream()?.set_nodelay(enabled)?)
|
||||
}
|
||||
|
||||
/// Gets the value of the `SO_LINGER` option on this stream, in seconds.
|
||||
pub extern "Lua-C" fn linger(&self) -> Result<f64> {
|
||||
Ok(self
|
||||
.stream()?
|
||||
.linger()?
|
||||
.map(|n| n.as_secs_f64())
|
||||
.unwrap_or(0.))
|
||||
}
|
||||
|
||||
/// Sets the value of the `SO_LINGER` option on this stream.
|
||||
///
|
||||
/// This controls how long the stream will remain open after close if unsent data is present.
|
||||
pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> {
|
||||
let secs = secs.max(0.);
|
||||
Ok(self
|
||||
.stream()?
|
||||
.set_linger((secs != 0.).then_some(std::time::Duration::from_secs_f64(secs)))?)
|
||||
}
|
||||
|
||||
/// Gets the value of the `IP_TTL` option for this stream.
|
||||
pub extern "Lua-C" fn ttl(&self) -> Result<u32> {
|
||||
Ok(self.stream()?.ttl()?)
|
||||
}
|
||||
|
||||
/// Sets the value for the `IP_TTL` option on this stream.
|
||||
pub extern "Lua-C" fn set_ttl(&self, ttl: u32) -> Result<()> {
|
||||
Ok(self.stream()?.set_ttl(ttl)?)
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __convert(&self) -> Result<lb_tcpstream> {
|
||||
Ok(lb_tcpstream::new(
|
||||
self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
#![doc(hidden)]
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use luaffi::{Module, Registry};
|
||||
use luajit::{Chunk, State};
|
||||
use luaify::luaify_chunk;
|
||||
use luajit::{Chunk, Index, NewTable, State};
|
||||
use std::rc::Rc;
|
||||
use tokio::{
|
||||
task::{JoinHandle, LocalSet, futures::TaskLocalFuture, spawn_local},
|
||||
@@ -13,6 +14,8 @@ pub type ErrorFn = dyn Fn(&luajit::Error);
|
||||
pub struct Builder {
|
||||
registry: Registry,
|
||||
report_err: Rc<ErrorFn>,
|
||||
jit_opts: Vec<String>,
|
||||
prohibit_globals: bool,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
@@ -23,6 +26,15 @@ impl Builder {
|
||||
Some(trace) => eprintln!("unhandled lua error: {err}\n{trace}"),
|
||||
None => eprintln!("unhandled lua error: {err}"),
|
||||
}),
|
||||
jit_opts: vec![
|
||||
// more aggressive jit options based on OpenResty's defaults
|
||||
// https://github.com/openresty/luajit2#updated-jit-default-parameters
|
||||
"maxtrace=8000".into(),
|
||||
"maxrecord=16000".into(),
|
||||
"minstitch=3".into(),
|
||||
"maxmcode=40960".into(),
|
||||
],
|
||||
prohibit_globals: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,19 +47,44 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn prohibit_globals(&mut self, enabled: bool) -> &mut Self {
|
||||
self.prohibit_globals = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn jit_opt(&mut self, opt: impl AsRef<str>) -> &mut Self {
|
||||
self.jit_opts.push(opt.as_ref().into());
|
||||
self
|
||||
}
|
||||
|
||||
pub fn jit_opts(&mut self, opts: impl IntoIterator<Item: AsRef<str>>) -> &mut Self {
|
||||
for opt in opts {
|
||||
self.jit_opt(opt);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn module<T: Module>(&mut self) -> &mut Self {
|
||||
self.registry.preload::<T>();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(&self) -> luajit::Result<Runtime> {
|
||||
let mut state = State::new()?;
|
||||
let chunk = Chunk::new(self.registry.build()).with_path("[luby]");
|
||||
state.eval(&chunk, 0, Some(0))?;
|
||||
|
||||
if self.prohibit_globals {
|
||||
state_prohibit_globals(&mut state)?;
|
||||
}
|
||||
|
||||
for opt in self.jit_opts.iter() {
|
||||
state_set_jitopt(&mut state, opt)?;
|
||||
}
|
||||
|
||||
Ok(Runtime {
|
||||
cx: Context {
|
||||
state: {
|
||||
let mut s = State::new()?;
|
||||
s.eval(Chunk::new(self.registry.build()).path("[luby]"), 0, 0)?;
|
||||
s
|
||||
},
|
||||
state,
|
||||
report_err: self.report_err.clone(),
|
||||
},
|
||||
tasks: LocalSet::new(),
|
||||
@@ -55,6 +92,60 @@ impl Builder {
|
||||
}
|
||||
}
|
||||
|
||||
fn state_prohibit_globals(state: &mut State) -> luajit::Result<()> {
|
||||
let mut s = state.guard();
|
||||
let chunk = Chunk::new(luaify_chunk!({
|
||||
return |self, key, value| {
|
||||
error(("undeclared local variable '%s'").format(key), 2);
|
||||
};
|
||||
}))
|
||||
.with_path("[luby]");
|
||||
s.eval(&chunk, 0, Some(1)).unwrap();
|
||||
s.push(NewTable::new());
|
||||
(s.push("__index"), s.push_idx(-3), s.set(-3));
|
||||
(s.push("__newindex"), s.push_idx(-3), s.set(-3));
|
||||
s.set_metatable(Index::globals());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn state_set_jitopt(state: &mut State, opt: &str) -> luajit::Result<()> {
|
||||
let mut s = state.guard();
|
||||
if let Some((cmd, opt)) = parse_jitlib_cmd(opt)
|
||||
&& let Ok(_) = s.require(format!("jit.{cmd}"), Some(1))
|
||||
{
|
||||
// require("jit.{cmd}").start(opt)
|
||||
(s.push("start"), s.get(-2));
|
||||
s.push(opt);
|
||||
s.call(1, Some(0))?;
|
||||
} else {
|
||||
s.require("jit", Some(1)).unwrap();
|
||||
match opt {
|
||||
cmd @ ("on" | "off" | "flush") => {
|
||||
// require("jit").<on|off|flush>()
|
||||
(s.push(cmd), s.get(-2));
|
||||
s.call(0, Some(0))?;
|
||||
}
|
||||
_ => {
|
||||
// require("jit").opt.start(opt)
|
||||
(s.push("opt"), s.get(-2));
|
||||
(s.push("start"), s.get(-2));
|
||||
s.push(opt);
|
||||
s.call(1, Some(0))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_jitlib_cmd(cmd: &str) -> Option<(&str, &str)> {
|
||||
match cmd {
|
||||
"p" => Some(("p", "Flspv10")), // default -jp flags
|
||||
"v" => Some(("v", "-")), // default -jv flags
|
||||
"dump" => Some(("dump", "tirs")), // default -jdump flags
|
||||
_ => cmd.split_once('='),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct Runtime {
|
||||
#[deref]
|
||||
@@ -97,7 +188,7 @@ pub struct Context {
|
||||
impl Context {
|
||||
pub fn new_thread(&self) -> Self {
|
||||
Self {
|
||||
state: self.state.new_thread(),
|
||||
state: State::new_thread(&self.state),
|
||||
report_err: self.report_err.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! # Task library
|
||||
//! Task library.
|
||||
//!
|
||||
//! The `lb:task` library primitives for asynchronous communication between tasks via message
|
||||
//! passing channels.
|
||||
//! The `lb:task` library provides utilities for the scheduling of and communication between
|
||||
//! asynchronous tasks.
|
||||
//!
|
||||
//! ## Exports
|
||||
//!
|
||||
@@ -12,17 +12,16 @@ use luaffi::{
|
||||
marker::{function, many},
|
||||
metatype,
|
||||
};
|
||||
use luajit::LUA_MULTRET;
|
||||
use std::{cell::RefCell, ffi::c_int, time::Duration};
|
||||
use tokio::{task::JoinHandle, time::sleep};
|
||||
|
||||
/// Items exported by the `lb:task` library.
|
||||
///
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require) in Lua.
|
||||
/// [`require("lb:task")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
||||
///
|
||||
/// ```lua
|
||||
/// local task = require("lb:task");
|
||||
/// local task = require("lb:task")
|
||||
/// ```
|
||||
#[cdef(module = "lb:task")]
|
||||
pub struct lb_tasklib;
|
||||
@@ -59,12 +58,12 @@ impl lb_tasklib {
|
||||
extern "Lua-C" fn __spawn(spawn_ref: c_int, handle_ref: c_int) -> lb_task {
|
||||
let handle = spawn(async move |cx| {
|
||||
// SAFETY: handle_ref is always unique, created in Self::spawn above.
|
||||
let state = unsafe { cx.new_ref_unchecked(spawn_ref) };
|
||||
let state = unsafe { luajit::Ref::from_raw(cx, spawn_ref) };
|
||||
let mut s = cx.guard();
|
||||
s.resize(0);
|
||||
s.push(state); // this drops the state table ref, but the table is still on the stack
|
||||
let narg = s.unpack(1, 1, None) - 1; // unpack the function and its args from the state table
|
||||
match s.call_async(narg, LUA_MULTRET).await {
|
||||
match s.call_async(narg, None).await {
|
||||
Ok(nret) => {
|
||||
s.pack(1, nret); // pack the return values back into the state table
|
||||
}
|
||||
|
||||
49
crates/lb/src/time.rs
Normal file
49
crates/lb/src/time.rs
Normal file
@@ -0,0 +1,49 @@
|
||||
//! Time library.
|
||||
//!
|
||||
//! The `lb:time` library provides utilities for working with the date and time and its related
|
||||
//! constructs.
|
||||
//!
|
||||
//! ## Exports
|
||||
//!
|
||||
//! See [`lb_timelib`] for items exported by this library.
|
||||
use luaffi::{cdef, metatype};
|
||||
|
||||
/// Items exported by the `lb:time` library.
|
||||
///
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:time")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
||||
///
|
||||
/// ```lua
|
||||
/// local time = require("lb:time")
|
||||
/// ```
|
||||
#[cdef(module = "lb:time")]
|
||||
pub struct lb_timelib;
|
||||
|
||||
#[metatype]
|
||||
impl lb_timelib {
|
||||
#[new]
|
||||
extern "Lua-C" fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
/// Returns an instant object that represents the current time at the time of calling.
|
||||
pub extern "Lua-C" fn instant() -> lb_instant {
|
||||
lb_instant::new(std::time::Instant::now())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the measurement of a monotonically nondecreasing clock.
|
||||
#[cdef]
|
||||
pub struct lb_instant(#[opaque] std::time::Instant);
|
||||
|
||||
#[metatype]
|
||||
impl lb_instant {
|
||||
fn new(instant: std::time::Instant) -> Self {
|
||||
Self(instant)
|
||||
}
|
||||
|
||||
/// Returns the number of seconds elapsed since this instant was measured.
|
||||
pub extern "Lua-C" fn elapsed_secs(&self) -> f64 {
|
||||
self.0.elapsed().as_secs_f64()
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ describe("tcp", function()
|
||||
spawn(function()
|
||||
assert(not pcall(client.read, client, 1)) -- this should fail, since the first task is still reading
|
||||
end):await()
|
||||
server:shutdown()
|
||||
server:close()
|
||||
reader:await()
|
||||
end)
|
||||
|
||||
@@ -104,7 +104,7 @@ describe("tcp", function()
|
||||
spawn(function()
|
||||
client:write("hello") -- should be able to write while the first task is reading
|
||||
end):await()
|
||||
server:shutdown()
|
||||
server:close()
|
||||
reader:await()
|
||||
end)
|
||||
|
||||
@@ -123,9 +123,7 @@ describe("tcp", function()
|
||||
assert(server:write("ping") == true)
|
||||
end
|
||||
sleep(100)
|
||||
server:shutdown()
|
||||
server = nil
|
||||
collectgarbage()
|
||||
server:close()
|
||||
reader:await()
|
||||
end)
|
||||
|
||||
@@ -144,9 +142,7 @@ describe("tcp", function()
|
||||
assert(server:read(4) == "pong")
|
||||
end
|
||||
sleep(100)
|
||||
server:shutdown()
|
||||
server = nil
|
||||
collectgarbage()
|
||||
server:close()
|
||||
writer:await()
|
||||
end)
|
||||
end)
|
||||
@@ -180,9 +176,9 @@ describe("tcp", function()
|
||||
local buf = client_stream:read(5)
|
||||
assert(buf ~= nil and #buf == 5)
|
||||
assert(buf == "hello")
|
||||
-- shutdown
|
||||
server_stream:shutdown()
|
||||
client_stream:shutdown()
|
||||
-- close
|
||||
server_stream:close()
|
||||
client_stream:close()
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
13
crates/lb_sqlite/Cargo.toml
Normal file
13
crates/lb_sqlite/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "lb_sqlite"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
lb = { path = "../lb", features = ["time"] }
|
||||
luaffi = { version = "0.0.1", path = "../luaffi" }
|
||||
rusqlite = { version = "0.36.0", features = ["bundled", "load_extension", "backup", "blob", "limits", "window", "series", "session", "collation", "serialize"] }
|
||||
3
crates/lb_sqlite/src/lib.rs
Normal file
3
crates/lb_sqlite/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
//! luby SQLite library.
|
||||
#[path = "mod.rs"]
|
||||
pub mod sqlite;
|
||||
27
crates/lb_sqlite/src/mod.rs
Normal file
27
crates/lb_sqlite/src/mod.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
//! SQLite library.
|
||||
//!
|
||||
//! The `lb:sqlite` library provides an interface to SQLite databases.
|
||||
//!
|
||||
//! ## Exports
|
||||
//!
|
||||
//! See [`lb_sqlitelib`] for items exported by this library.
|
||||
use luaffi::{cdef, metatype};
|
||||
|
||||
/// Items exported by the `lb:sqlite` library.
|
||||
///
|
||||
/// This library can be acquired by calling
|
||||
/// [`require("lb:sqlite")`](https://www.lua.org/manual/5.1/manual.html#pdf-require).
|
||||
///
|
||||
/// ```lua
|
||||
/// local sqlite = require("lb:sqlite")
|
||||
/// ```
|
||||
#[cdef(module = "lb:sqlite")]
|
||||
pub struct lb_sqlitelib;
|
||||
|
||||
#[metatype]
|
||||
impl lb_sqlitelib {
|
||||
#[new]
|
||||
extern "Lua-C" fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
@@ -645,7 +645,7 @@ impl<'r, 'm> MetatypeFunctionBuilder<'r, 'm> {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Annotation {
|
||||
pub trait Annotate {
|
||||
fn annotation() -> impl Display;
|
||||
}
|
||||
|
||||
@@ -699,7 +699,7 @@ macro_rules! impl_primitive {
|
||||
fn build(_b: &mut TypeBuilder) {}
|
||||
}
|
||||
|
||||
impl Annotation for $rty {
|
||||
impl Annotate for $rty {
|
||||
fn annotation() -> impl Display {
|
||||
$lty
|
||||
}
|
||||
@@ -867,9 +867,9 @@ impl_ptr!(Option<&mut T>, true);
|
||||
|
||||
macro_rules! impl_ref_annotation {
|
||||
($ty:ty) => {
|
||||
impl<T> Annotation for $ty
|
||||
impl<T> Annotate for $ty
|
||||
where
|
||||
T: Annotation,
|
||||
T: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}", T::annotation())
|
||||
@@ -1005,9 +1005,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Annotation for [T]
|
||||
impl<T> Annotate for [T]
|
||||
where
|
||||
T: Annotation,
|
||||
T: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}[]", T::annotation())
|
||||
@@ -1035,9 +1035,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Annotation for [T; N]
|
||||
impl<T, const N: usize> Annotate for [T; N]
|
||||
where
|
||||
T: Annotation,
|
||||
T: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}[]", T::annotation())
|
||||
@@ -1106,25 +1106,25 @@ impl_externcfn!(fn(A, B, C, D, E, F, G) -> H);
|
||||
impl_externcfn!(fn(A, B, C, D, E, F, G, H) -> I);
|
||||
impl_externcfn!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
||||
|
||||
impl<'s> Annotation for &'s [u8] {
|
||||
impl<'s> Annotate for &'s [u8] {
|
||||
fn annotation() -> impl Display {
|
||||
"string"
|
||||
}
|
||||
}
|
||||
|
||||
impl<'s> Annotation for &'s str {
|
||||
impl<'s> Annotate for &'s str {
|
||||
fn annotation() -> impl Display {
|
||||
"string"
|
||||
}
|
||||
}
|
||||
|
||||
impl Annotation for Vec<u8> {
|
||||
impl Annotate for Vec<u8> {
|
||||
fn annotation() -> impl Display {
|
||||
"string"
|
||||
}
|
||||
}
|
||||
|
||||
impl Annotation for String {
|
||||
impl Annotate for String {
|
||||
fn annotation() -> impl Display {
|
||||
"string"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#![allow(non_camel_case_types)]
|
||||
use crate::{
|
||||
__internal::{disp, display},
|
||||
Annotation,
|
||||
Annotate,
|
||||
};
|
||||
use std::{fmt::Display, marker::PhantomData};
|
||||
|
||||
@@ -9,7 +9,7 @@ enum Marker {}
|
||||
|
||||
pub struct any(Marker);
|
||||
|
||||
impl Annotation for any {
|
||||
impl Annotate for any {
|
||||
fn annotation() -> impl Display {
|
||||
"any"
|
||||
}
|
||||
@@ -17,7 +17,7 @@ impl Annotation for any {
|
||||
|
||||
pub struct many(Marker);
|
||||
|
||||
impl Annotation for many {
|
||||
impl Annotate for many {
|
||||
fn annotation() -> impl Display {
|
||||
"..."
|
||||
}
|
||||
@@ -25,7 +25,7 @@ impl Annotation for many {
|
||||
|
||||
pub struct nil(Marker);
|
||||
|
||||
impl Annotation for nil {
|
||||
impl Annotate for nil {
|
||||
fn annotation() -> impl Display {
|
||||
"nil"
|
||||
}
|
||||
@@ -33,7 +33,7 @@ impl Annotation for nil {
|
||||
|
||||
pub struct lightuserdata(Marker);
|
||||
|
||||
impl Annotation for lightuserdata {
|
||||
impl Annotate for lightuserdata {
|
||||
fn annotation() -> impl Display {
|
||||
"lightuserdata"
|
||||
}
|
||||
@@ -41,10 +41,10 @@ impl Annotation for lightuserdata {
|
||||
|
||||
pub struct table<K, V>(Marker, PhantomData<*mut [(K, V)]>);
|
||||
|
||||
impl<K, V> Annotation for table<K, V>
|
||||
impl<K, V> Annotate for table<K, V>
|
||||
where
|
||||
K: Annotation,
|
||||
V: Annotation,
|
||||
K: Annotate,
|
||||
V: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("table<{}, {}>", K::annotation(), V::annotation())
|
||||
@@ -53,7 +53,7 @@ where
|
||||
|
||||
pub struct function(Marker);
|
||||
|
||||
impl Annotation for function {
|
||||
impl Annotate for function {
|
||||
fn annotation() -> impl Display {
|
||||
"function"
|
||||
}
|
||||
@@ -63,9 +63,9 @@ pub struct fun<I, O>(Marker, PhantomData<fn(I) -> O>);
|
||||
|
||||
macro_rules! impl_fun {
|
||||
(fn($($arg:ident),*)) => {
|
||||
impl<$($arg,)*> Annotation for fun<($($arg,)*), ()>
|
||||
impl<$($arg,)*> Annotate for fun<($($arg,)*), ()>
|
||||
where
|
||||
$($arg: Annotation,)*
|
||||
$($arg: Annotate,)*
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
disp(|f| {
|
||||
@@ -79,10 +79,10 @@ macro_rules! impl_fun {
|
||||
};
|
||||
|
||||
(fn($($arg:ident),*) -> $ret:ident) => {
|
||||
impl<$($arg,)* $ret> Annotation for fun<($($arg,)*), $ret>
|
||||
impl<$($arg,)* $ret> Annotate for fun<($($arg,)*), $ret>
|
||||
where
|
||||
$($arg: Annotation,)*
|
||||
$ret: Annotation,
|
||||
$($arg: Annotate,)*
|
||||
$ret: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
disp(|f| {
|
||||
@@ -111,7 +111,7 @@ impl_fun!(fn(A, B, C, D, E, F, G, H, I) -> J);
|
||||
|
||||
pub struct userdata(Marker);
|
||||
|
||||
impl Annotation for userdata {
|
||||
impl Annotate for userdata {
|
||||
fn annotation() -> impl Display {
|
||||
"userdata"
|
||||
}
|
||||
@@ -119,7 +119,7 @@ impl Annotation for userdata {
|
||||
|
||||
pub struct thread(Marker);
|
||||
|
||||
impl Annotation for thread {
|
||||
impl Annotate for thread {
|
||||
fn annotation() -> impl Display {
|
||||
"thread"
|
||||
}
|
||||
@@ -127,7 +127,7 @@ impl Annotation for thread {
|
||||
|
||||
pub struct cdata(Marker);
|
||||
|
||||
impl Annotation for cdata {
|
||||
impl Annotate for cdata {
|
||||
fn annotation() -> impl Display {
|
||||
"cdata"
|
||||
}
|
||||
@@ -135,10 +135,10 @@ impl Annotation for cdata {
|
||||
|
||||
pub struct Either<T, U>(Marker, PhantomData<(T, U)>);
|
||||
|
||||
impl<T, U> Annotation for Either<T, U>
|
||||
impl<T, U> Annotate for Either<T, U>
|
||||
where
|
||||
T: Annotation,
|
||||
U: Annotation,
|
||||
T: Annotate,
|
||||
U: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("({} | {})", T::annotation(), U::annotation())
|
||||
@@ -149,9 +149,9 @@ pub struct OneOf<X>(Marker, PhantomData<X>);
|
||||
|
||||
macro_rules! impl_oneof {
|
||||
($($ty:ident),+) => {
|
||||
impl<$($ty),+> Annotation for OneOf<($($ty,)+)>
|
||||
impl<$($ty),+> Annotate for OneOf<($($ty,)+)>
|
||||
where
|
||||
$($ty: Annotation),+
|
||||
$($ty: Annotate),+
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
disp(|f| {
|
||||
@@ -174,18 +174,18 @@ impl_oneof!(A, B, C, D, E, F, G);
|
||||
impl_oneof!(A, B, C, D, E, F, G, H);
|
||||
impl_oneof!(A, B, C, D, E, F, G, H, I);
|
||||
|
||||
impl<T> Annotation for Option<T>
|
||||
impl<T> Annotate for Option<T>
|
||||
where
|
||||
T: Annotation,
|
||||
T: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}?", T::annotation())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> Annotation for Result<T, E>
|
||||
impl<T, E> Annotate for Result<T, E>
|
||||
where
|
||||
T: Annotation,
|
||||
T: Annotate,
|
||||
{
|
||||
fn annotation() -> impl Display {
|
||||
display!("{}", T::annotation())
|
||||
|
||||
@@ -70,7 +70,7 @@ fn generate_type(ty: &Ident) -> Result<TokenStream> {
|
||||
}
|
||||
}
|
||||
|
||||
impl #ffi::Annotation for #ty {
|
||||
impl #ffi::Annotate for #ty {
|
||||
fn annotation() -> impl ::std::fmt::Display { #name }
|
||||
}
|
||||
|
||||
|
||||
@@ -682,7 +682,7 @@ fn stub_lua_function(func: &mut ImplItemFn) -> Result<()> {
|
||||
func.attrs.push(parse_quote!(#[allow(unused)]));
|
||||
func.block.stmts.clear();
|
||||
func.block.stmts.push(parse_quote!(
|
||||
const fn has_annotation<T: #ffi::Annotation>() {}
|
||||
const fn has_annotation<T: #ffi::Annotate>() {}
|
||||
));
|
||||
|
||||
// convert `...` variadic to a `rest: luaffi::marker::Many` parameter
|
||||
|
||||
@@ -1,3 +1,53 @@
|
||||
//! # luaify
|
||||
//!
|
||||
//! A macro for generating Lua code from Rust syntax.
|
||||
//!
|
||||
//! This macro performs a direct one-to-one translation of Rust expressions and blocks into
|
||||
//! equivalent Lua code. For example,
|
||||
//!
|
||||
//! ```rust
|
||||
//! use luaify::luaify;
|
||||
//!
|
||||
//! luaify!(|a, b| {
|
||||
//! let c = a + b;
|
||||
//!
|
||||
//! fn inner_function(c: _) {
|
||||
//! print(concat!("the sum of ", a, " and ", b, " is ", c));
|
||||
//! c
|
||||
//! }
|
||||
//!
|
||||
//! inner_function(c);
|
||||
//! });
|
||||
//! ```
|
||||
//!
|
||||
//! will translate to the following equivalent Lua code (embedded as an [`&str`] or [`String`] in
|
||||
//! Rust):
|
||||
//!
|
||||
//! ```lua
|
||||
//! function(a, b)
|
||||
//! local c = a + b
|
||||
//!
|
||||
//! local function inner_function(c)
|
||||
//! print("the sum of " .. a .. " and " .. b .. " is " .. c)
|
||||
//! return c
|
||||
//! end
|
||||
//!
|
||||
//! inner_function(c)
|
||||
//! end
|
||||
//! ```
|
||||
//!
|
||||
//! This macro accepts a smaller subset of all valid Rust syntax due to the difference between
|
||||
//! Rust's expression-based syntax and Lua's statement-based syntax. Most Rust syntax are translated
|
||||
//! to their equivalents in Lua, however the following features are not supported and will result in
|
||||
//! a compile-time error:
|
||||
//!
|
||||
//! - static typing (typed local variables, parameters and return types in function signatures,
|
||||
//! etc.)
|
||||
//! - pattern matching (e.g. `match`, `if let`, `while let`)
|
||||
//! - items other than statements or functions (e.g. `struct`, `enum`, `trait`, `impl`)
|
||||
//! - block statements in expression position (e.g. `if`, `while`, `for`, `loop` that evaluate to a
|
||||
//! value)
|
||||
//! - expressions in statement position (except for function calls and assignments)
|
||||
use crate::{
|
||||
generate::{generate, generate_chunk},
|
||||
transform::{transform, transform_chunk},
|
||||
@@ -10,6 +60,7 @@ mod generate;
|
||||
mod transform;
|
||||
mod utils;
|
||||
|
||||
/// Generates Lua code for the given expression.
|
||||
#[proc_macro]
|
||||
pub fn luaify(input: TokenStream1) -> TokenStream1 {
|
||||
let mut expr = parse_macro_input!(input);
|
||||
@@ -20,6 +71,7 @@ pub fn luaify(input: TokenStream1) -> TokenStream1 {
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Generates Lua code for the given block.
|
||||
#[proc_macro]
|
||||
pub fn luaify_chunk(input: TokenStream1) -> TokenStream1 {
|
||||
let mut block = parse_macro_input!(input);
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
//! Raw bindings for LuaJIT generated using [bindgen](https://docs.rs/bindgen/).
|
||||
//!
|
||||
//! ## Feature flags
|
||||
//!
|
||||
//! Features flags are used to control certain features of LuaJIT. They are not enabled by default
|
||||
//! unless otherwise specified below.
|
||||
//!
|
||||
//! - `runtime`: links the target with `libluajit`. This should only be enabled by binary targets.
|
||||
//! - `jit`: enables the JIT compiler. *This is enabled by default.*
|
||||
//! - `ffi`: enables the FFI library. *This is enabled by default.*
|
||||
//! - `unwind`: configures LuaJIT to use stack unwinding instead of `longjmp` for error handling.
|
||||
//! **This MUST be enabled if `panic = "unwind"` is set.**
|
||||
//! - `bundled-alloc`: configures LuaJIT to include its own bundled allocator. If this is not
|
||||
//! enabled, LuaJIT will use the system allocator by default.
|
||||
//! - `lua52`: enables Lua 5.2 compatibility mode. *This is enabled by default.*
|
||||
#![allow(nonstandard_style)]
|
||||
use std::{ffi::*, ptr};
|
||||
|
||||
|
||||
@@ -16,4 +16,3 @@ bitflags = { version = "2.9.1", features = ["std"] }
|
||||
bstr = "1.12.0"
|
||||
luaffi = { path = "../luaffi" }
|
||||
luajit-sys = { path = "../luajit-sys" }
|
||||
thiserror = "2.0.12"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
15
src/lib.rs
15
src/lib.rs
@@ -1,20 +1,27 @@
|
||||
#[cfg(feature = "task")]
|
||||
pub use lb::chan;
|
||||
//! luby standard library.
|
||||
#[cfg(feature = "fs")]
|
||||
pub use lb::fs;
|
||||
#[cfg(feature = "net")]
|
||||
pub use lb::net;
|
||||
#[cfg(feature = "task")]
|
||||
pub use lb::task;
|
||||
#[cfg(feature = "time")]
|
||||
pub use lb::time;
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
pub use lb_sqlite::sqlite;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn open(#[allow(unused)] rt: &mut lb::runtime::Builder) {
|
||||
#[cfg(feature = "task")]
|
||||
rt.module::<task::lb_tasklib>();
|
||||
#[cfg(feature = "task")]
|
||||
rt.module::<chan::lb_chanlib>();
|
||||
#[cfg(feature = "time")]
|
||||
rt.module::<time::lb_timelib>();
|
||||
#[cfg(feature = "fs")]
|
||||
rt.module::<fs::lb_fslib>();
|
||||
#[cfg(feature = "net")]
|
||||
rt.module::<net::lb_netlib>();
|
||||
|
||||
#[cfg(feature = "sqlite")]
|
||||
rt.module::<sqlite::lb_sqlitelib>();
|
||||
}
|
||||
|
||||
75
src/main.rs
75
src/main.rs
@@ -1,4 +1,5 @@
|
||||
use clap::Parser;
|
||||
use luajit::Chunk;
|
||||
use mimalloc::MiMalloc;
|
||||
use owo_colors::OwoColorize;
|
||||
use std::{backtrace::Backtrace, fmt::Display, num::NonZero, panic, process, thread};
|
||||
@@ -78,12 +79,21 @@ struct Args {
|
||||
#[clap(long, short = 'j', help_heading = "Runtime", value_name = "CMD=FLAGS")]
|
||||
jit: Vec<String>,
|
||||
|
||||
/// Allow global variables.
|
||||
#[clap(
|
||||
long,
|
||||
help_heading = "Runtime",
|
||||
value_name = "ENABLED",
|
||||
default_value_t = true
|
||||
)]
|
||||
allow_globals: bool,
|
||||
|
||||
/// Number of worker threads.
|
||||
#[clap(
|
||||
long,
|
||||
short = 'T',
|
||||
help_heading = "Runtime",
|
||||
value_name = "THREADS",
|
||||
value_name = "COUNT",
|
||||
default_value_t = Self::threads()
|
||||
)]
|
||||
threads: NonZero<usize>,
|
||||
@@ -92,14 +102,14 @@ struct Args {
|
||||
#[clap(
|
||||
long,
|
||||
help_heading = "Runtime",
|
||||
value_name = "THREADS",
|
||||
value_name = "COUNT",
|
||||
default_value_t = Self::blocking_threads()
|
||||
)]
|
||||
blocking_threads: NonZero<usize>,
|
||||
|
||||
/// Enable tokio-console integration.
|
||||
#[cfg(feature = "tokio-console")]
|
||||
#[clap(long, help_heading = "Debugging")]
|
||||
#[clap(long, help_heading = "Debugging", value_name = "ENABLED")]
|
||||
enable_console: bool,
|
||||
|
||||
/// tokio-console publish address.
|
||||
@@ -221,55 +231,18 @@ fn init_tokio(args: &Args) -> tokio::runtime::Runtime {
|
||||
}
|
||||
|
||||
fn init_lua(args: &Args) -> lb::runtime::Runtime {
|
||||
let mut rt = {
|
||||
let mut rt = lb::runtime::Builder::new();
|
||||
luby::open(&mut rt);
|
||||
let mut rt = lb::runtime::Builder::new();
|
||||
luby::open(&mut rt);
|
||||
|
||||
if args.dump.iter().find(|s| *s == "cdef").is_some() {
|
||||
print!("{}", rt.registry()); // for cdef debugging
|
||||
}
|
||||
|
||||
rt.unhandled_error(error_cb).build().unwrap()
|
||||
};
|
||||
|
||||
for arg in args.jit.iter() {
|
||||
let mut s = rt.guard();
|
||||
let res = if let Some((cmd, flags)) = parse_jitlib_cmd(arg)
|
||||
&& let Ok(_) = s.require(format!("jit.{cmd}"), 1)
|
||||
{
|
||||
(s.push("start"), s.get(-2), s.push(flags));
|
||||
s.call(1, 0) // require("jit.{cmd}").start(flags)
|
||||
} else {
|
||||
s.require("jit", 1).unwrap();
|
||||
match arg.as_str() {
|
||||
cmd @ ("on" | "off" | "flush") => {
|
||||
(s.push(cmd), s.get(-2));
|
||||
s.call(0, 0) // require("jit").[on/off/flush]()
|
||||
}
|
||||
flags => {
|
||||
(s.push("opt"), s.get(-2));
|
||||
(s.push("start"), s.get(-2), s.push(flags));
|
||||
s.call(1, 0) // require("jit").opt.start(flags)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(err) = res {
|
||||
drop(s);
|
||||
rt.report_error(&err);
|
||||
}
|
||||
if args.dump.iter().find(|s| *s == "cdef").is_some() {
|
||||
print!("{}", rt.registry()); // for cdef debugging
|
||||
}
|
||||
|
||||
rt
|
||||
}
|
||||
|
||||
fn parse_jitlib_cmd(s: &str) -> Option<(&str, &str)> {
|
||||
match s {
|
||||
"p" => Some(("p", "Flspv10")), // default -jp flags
|
||||
"v" => Some(("v", "-")), // default -jv flags
|
||||
"dump" => Some(("dump", "tirs")), // default -jdump flags
|
||||
_ => s.split_once('='),
|
||||
}
|
||||
rt.unhandled_error(error_cb)
|
||||
.prohibit_globals(!args.allow_globals)
|
||||
.jit_opts(args.jit.iter())
|
||||
.build()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
|
||||
@@ -282,9 +255,9 @@ async fn main_async(args: Args, cx: &mut lb::runtime::Context) -> ExitCode {
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(ref err) = cx.load(&luajit::Chunk::new(chunk).path(path)) {
|
||||
if let Err(ref err) = cx.load(&Chunk::new(chunk).with_path(path)) {
|
||||
cx.report_error(err);
|
||||
} else if let Err(ref err) = cx.call_async(0, 0).await {
|
||||
} else if let Err(ref err) = cx.call_async(0, Some(0)).await {
|
||||
cx.report_error(err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
if (...) ~= nil and (...).type == "group" then return end -- prevent recursive harness call
|
||||
|
||||
local ok = pcall(require, "lb:task")
|
||||
if not ok then error("lua test harness requires lb:task module") end
|
||||
if not ok then error("lua test harness requires 'lb:task'") end
|
||||
local ok, time = pcall(require, "lb:time")
|
||||
if not ok then error("lua test harness requires 'lb:time'") end
|
||||
local ok, fs = pcall(require, "lb:fs")
|
||||
if not ok then error("lua test harness requires lb:fs module") end
|
||||
if not ok then error("lua test harness requires 'lb:fs'") end
|
||||
|
||||
local global = _G
|
||||
local colors = {
|
||||
local color = {
|
||||
reset = "\x1b[0m",
|
||||
pass = "\x1b[32;1m",
|
||||
fail = "\x1b[31;1m",
|
||||
pass = "\x1b[32;1m", -- green
|
||||
fail = "\x1b[31;1m", -- red
|
||||
faint = "\x1b[2;39;49m", -- faint
|
||||
}
|
||||
|
||||
local icons = {
|
||||
local icon = {
|
||||
check = "\u{2713}",
|
||||
cross = "\u{00d7}",
|
||||
chevron = "\u{203a}",
|
||||
}
|
||||
|
||||
local function color(name, s)
|
||||
return ("%s %s %s"):format(colors[name], s, colors.reset)
|
||||
local function style(name, s)
|
||||
return ("%s%s%s"):format(color[name], s, color.reset)
|
||||
end
|
||||
|
||||
local function rjust(s, w)
|
||||
if w == nil then w = 12 end
|
||||
if #s >= w then return s end
|
||||
return (" "):rep(w - #s) .. s
|
||||
end
|
||||
|
||||
local function create_test(name, f, group)
|
||||
local test = { type = "test", name = name or "", group = group, state = "pending", f = f }
|
||||
local fenv = setmetatable({}, { __index = global })
|
||||
local fenv = setmetatable({}, { __index = global, __newindex = global })
|
||||
setfenv(f, fenv)
|
||||
return test
|
||||
end
|
||||
@@ -43,7 +52,7 @@ local function create_group(name, f, parent)
|
||||
table.insert(group.items, item)
|
||||
return item
|
||||
end,
|
||||
}, { __index = global })
|
||||
}, { __index = global, __newindex = global })
|
||||
|
||||
setfenv(f, fenv)
|
||||
f(group)
|
||||
@@ -54,24 +63,24 @@ local function name_test(test)
|
||||
local name = test.name
|
||||
local group = test.group
|
||||
while group ~= nil do
|
||||
if group.name ~= "" then name = ("%s %s %s"):format(group.name, icons.chevron, name) end
|
||||
if group.name ~= "" then name = ("%s %s %s"):format(group.name, icon.chevron, name) end
|
||||
group = group.parent
|
||||
end
|
||||
return name
|
||||
end
|
||||
|
||||
local function trace(msg)
|
||||
return color("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1)
|
||||
return style("fail", msg) .. debug.traceback("", 2):sub(("\nstack traceback:"):len() + 1)
|
||||
end
|
||||
|
||||
local function run_test(test)
|
||||
local ok, res = xpcall(test.f, trace, test)
|
||||
local ok, trace = xpcall(test.f, trace, test)
|
||||
if ok then
|
||||
test.state = "pass"
|
||||
print("", ("%s %s"):format(color("pass", "PASS"), name_test(test)))
|
||||
print(("%s %s"):format(style("pass", rjust("PASS")), name_test(test)))
|
||||
else
|
||||
test.state = "fail"
|
||||
print("", ("%s %s\n\n%s\n"):format(color("fail", "FAIL"), name_test(test), res))
|
||||
print(("%s %s\n\n%s\n"):format(style("fail", rjust("!!! FAIL")), name_test(test), trace))
|
||||
end
|
||||
collectgarbage() -- gc after each test to test destructors
|
||||
return test
|
||||
@@ -87,7 +96,7 @@ local function start(cx, item)
|
||||
end
|
||||
end
|
||||
|
||||
local function check_unrefs()
|
||||
local function check_refs()
|
||||
-- ensure all refs were properly unref'ed
|
||||
local registry = debug.getregistry()
|
||||
local count = #registry
|
||||
@@ -106,7 +115,7 @@ end
|
||||
|
||||
local function main(item)
|
||||
local cx = { tasks = {} }
|
||||
local pass, fail = 0, 0
|
||||
local time, pass, fail = time.instant(), 0, 0
|
||||
start(cx, item)
|
||||
for _, task in ipairs(cx.tasks) do
|
||||
if task:await().state == "pass" then
|
||||
@@ -115,23 +124,25 @@ local function main(item)
|
||||
fail = fail + 1
|
||||
end
|
||||
end
|
||||
local code = 1
|
||||
local elapsed = time:elapsed_secs()
|
||||
local retcode
|
||||
if fail == 0 then
|
||||
print("", color("pass", ("%s %d tests passed"):format(icons.check, pass)))
|
||||
code = 0
|
||||
print(style("pass", ("\t%s %d tests passed"):format(icon.check, pass)))
|
||||
retcode = 0
|
||||
else
|
||||
print(
|
||||
"",
|
||||
("%s, %s"):format(
|
||||
color("pass", ("%s %d tests passed"):format(icons.check, pass)),
|
||||
color("fail", ("%s %d tests failed"):format(icons.cross, fail))
|
||||
("\t%s, %s"):format(
|
||||
style("pass", ("%s %d tests passed"):format(icon.check, pass)),
|
||||
style("fail", ("%s %d tests failed"):format(icon.cross, fail))
|
||||
)
|
||||
)
|
||||
retcode = 1
|
||||
end
|
||||
print(style("faint", ("\t%s completed in %.2fs"):format(icon.chevron, elapsed)))
|
||||
cx = nil
|
||||
collectgarbage()
|
||||
check_unrefs()
|
||||
return code -- report error to cargo
|
||||
check_refs() -- check that all refs were properly unref'ed in destructors
|
||||
return retcode -- report error to cargo
|
||||
end
|
||||
|
||||
return main(create_group("", function()
|
||||
@@ -144,6 +155,34 @@ return main(create_group("", function()
|
||||
end
|
||||
end
|
||||
|
||||
local function include_doctest(path, pat)
|
||||
for entry in fs.glob_dir(path, pat) do
|
||||
local line, doctest = 0, nil
|
||||
for s in fs.read(entry:path()):gmatch("([^\n]*)\n?") do
|
||||
line = line + 1
|
||||
local prefix = s:match("^%s*///")
|
||||
s = prefix and s:sub(#prefix + 1)
|
||||
if s and not s:match("^%s*```%s*$") then
|
||||
if s:match("^%s*```lua$") then
|
||||
doctest = { line = line, col = #prefix + 2 }
|
||||
elseif doctest then
|
||||
table.insert(doctest, (" "):rep(#prefix) .. s)
|
||||
end
|
||||
else
|
||||
if doctest then
|
||||
local name = ("%s:%d:%d"):format(entry:path(), doctest.line, doctest.col)
|
||||
local f, err = loadstring(table.concat(doctest, "\n"), "@" .. name)
|
||||
if not f then error(err) end
|
||||
test(("%s %s"):format(name, style("faint", "(doctest)")), f)
|
||||
end
|
||||
doctest = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include("tests", "**/*.lua")
|
||||
include("crates", "*/tests/**/*.lua")
|
||||
include_doctest("src", "**/*.rs")
|
||||
include_doctest("crates", "*/src/**/*.rs")
|
||||
end))
|
||||
|
||||
@@ -10,14 +10,17 @@ fn main() -> ExitCode {
|
||||
let lua = {
|
||||
let mut rt = lb::runtime::Builder::new();
|
||||
luby::open(&mut rt);
|
||||
rt.unhandled_error(error_cb).build().unwrap()
|
||||
rt.unhandled_error(error_cb)
|
||||
.prohibit_globals(true)
|
||||
.build()
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let path = "tests/main.lua";
|
||||
let main = lua.spawn(async move |s| {
|
||||
if let Err(ref err) = s.load(Chunk::new(fs::read(path).unwrap()).path(path)) {
|
||||
if let Err(ref err) = s.load(&Chunk::new(fs::read(path).unwrap()).with_path(path)) {
|
||||
s.report_error(err);
|
||||
} else if let Err(ref err) = s.call_async(0, 1).await {
|
||||
} else if let Err(ref err) = s.call_async(0, Some(1)).await {
|
||||
s.report_error(err);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user