mirror of
https://github.com/lune-org/lune.git
synced 2025-04-04 10:30:54 +01:00
Mostly port globals to lune-std crate
This commit is contained in:
parent
0bb978640d
commit
136cb7d6d6
15 changed files with 760 additions and 5 deletions
5
Cargo.lock
generated
5
Cargo.lock
generated
|
@ -1551,7 +1551,12 @@ dependencies = [
|
|||
"lune-std-serde",
|
||||
"lune-std-stdio",
|
||||
"lune-std-task",
|
||||
"lune-utils",
|
||||
"mlua",
|
||||
"mlua-luau-scheduler 0.0.1",
|
||||
"path-clean",
|
||||
"pathdiff",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -37,6 +37,14 @@ task = ["dep:lune-std-task"]
|
|||
|
||||
[dependencies]
|
||||
mlua = "0.9.7"
|
||||
mlua-luau-scheduler = "0.0.1"
|
||||
|
||||
path-clean = "1.0"
|
||||
pathdiff = "0.2"
|
||||
|
||||
tokio = { version = "1", default-features = false, features = ["fs"] }
|
||||
|
||||
lune-utils = { version = "0.8.3", path = "../lune-utils" }
|
||||
|
||||
lune-std-datetime = { optional = true, version = "0.8.3", path = "../lune-std-datetime" }
|
||||
lune-std-fs = { optional = true, version = "0.8.3", path = "../lune-std-fs" }
|
||||
|
|
92
crates/lune-std/src/global.rs
Normal file
92
crates/lune-std/src/global.rs
Normal file
|
@ -0,0 +1,92 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
/**
|
||||
A standard global provided by Lune.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub enum LuneStandardGlobal {
|
||||
GTable,
|
||||
Print,
|
||||
Require,
|
||||
Version,
|
||||
Warn,
|
||||
}
|
||||
|
||||
impl LuneStandardGlobal {
|
||||
/**
|
||||
All available standard globals.
|
||||
*/
|
||||
pub const ALL: &'static [Self] = &[
|
||||
Self::GTable,
|
||||
Self::Print,
|
||||
Self::Require,
|
||||
Self::Version,
|
||||
Self::Warn,
|
||||
];
|
||||
|
||||
/**
|
||||
Gets the name of the global, such as `_G` or `require`.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
Self::GTable => "_G",
|
||||
Self::Print => "print",
|
||||
Self::Require => "require",
|
||||
Self::Version => "_VERSION",
|
||||
Self::Warn => "warn",
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates the Lua value for the global.
|
||||
|
||||
# Errors
|
||||
|
||||
If the global could not be created.
|
||||
*/
|
||||
#[rustfmt::skip]
|
||||
#[allow(unreachable_patterns)]
|
||||
pub fn create<'lua>(&self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
||||
let res = match self {
|
||||
Self::GTable => crate::globals::g_table::create(lua),
|
||||
Self::Print => crate::globals::print::create(lua),
|
||||
Self::Require => crate::globals::require::create(lua),
|
||||
Self::Version => crate::globals::version::create(lua),
|
||||
Self::Warn => crate::globals::warn::create(lua),
|
||||
};
|
||||
match res {
|
||||
Ok(v) => Ok(v),
|
||||
Err(e) => Err(e.context(format!(
|
||||
"Failed to create standard global '{}'",
|
||||
self.name()
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for LuneStandardGlobal {
|
||||
type Err = String;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let low = s.trim().to_ascii_lowercase();
|
||||
Ok(match low.as_str() {
|
||||
"_g" => Self::GTable,
|
||||
"print" => Self::Print,
|
||||
"require" => Self::Require,
|
||||
"_version" => Self::Version,
|
||||
"warn" => Self::Warn,
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Unknown standard global '{low}'\nValid globals are: {}",
|
||||
Self::ALL
|
||||
.iter()
|
||||
.map(Self::name)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
5
crates/lune-std/src/globals/g_table.rs
Normal file
5
crates/lune-std/src/globals/g_table.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||
lua.create_table()?.into_lua(lua)
|
||||
}
|
5
crates/lune-std/src/globals/mod.rs
Normal file
5
crates/lune-std/src/globals/mod.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
pub mod g_table;
|
||||
pub mod print;
|
||||
pub mod require;
|
||||
pub mod version;
|
||||
pub mod warn;
|
9
crates/lune-std/src/globals/print.rs
Normal file
9
crates/lune-std/src/globals/print.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||
let f = lua.create_function(|_, args: LuaMultiValue| {
|
||||
// TODO: Port this over from the old crate
|
||||
Ok(())
|
||||
})?;
|
||||
f.into_lua(lua)
|
||||
}
|
74
crates/lune-std/src/globals/require/alias.rs
Normal file
74
crates/lune-std/src/globals/require/alias.rs
Normal file
|
@ -0,0 +1,74 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::{
|
||||
luaurc::LuauRc,
|
||||
paths::{make_absolute_and_clean, CWD},
|
||||
};
|
||||
|
||||
use super::context::*;
|
||||
|
||||
pub(super) async fn require<'lua, 'ctx>(
|
||||
lua: &'lua Lua,
|
||||
ctx: &'ctx RequireContext,
|
||||
source: &str,
|
||||
alias: &str,
|
||||
path: &str,
|
||||
) -> LuaResult<LuaMultiValue<'lua>>
|
||||
where
|
||||
'lua: 'ctx,
|
||||
{
|
||||
let alias = alias.to_ascii_lowercase();
|
||||
|
||||
let parent = make_absolute_and_clean(source)
|
||||
.parent()
|
||||
.expect("how did a root path end up here..")
|
||||
.to_path_buf();
|
||||
|
||||
// Try to gather the first luaurc and / or error we
|
||||
// encounter to display better error messages to users
|
||||
let mut first_luaurc = None;
|
||||
let mut first_error = None;
|
||||
let predicate = |rc: &LuauRc| {
|
||||
if first_luaurc.is_none() {
|
||||
first_luaurc.replace(rc.clone());
|
||||
}
|
||||
if let Err(e) = rc.validate() {
|
||||
if first_error.is_none() {
|
||||
first_error.replace(e);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
rc.find_alias(&alias).is_some()
|
||||
}
|
||||
};
|
||||
|
||||
// Try to find a luaurc that contains the alias we're searching for
|
||||
let luaurc = LuauRc::read_recursive(parent, predicate)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
if let Some(error) = first_error {
|
||||
LuaError::runtime(format!("error while parsing .luaurc file: {error}"))
|
||||
} else if let Some(luaurc) = first_luaurc {
|
||||
LuaError::runtime(format!(
|
||||
"failed to find alias '{alias}' - known aliases:\n{}",
|
||||
luaurc
|
||||
.aliases()
|
||||
.iter()
|
||||
.map(|(name, path)| format!(" {name} > {path}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n")
|
||||
))
|
||||
} else {
|
||||
LuaError::runtime(format!("failed to find alias '{alias}' (no .luaurc)"))
|
||||
}
|
||||
})?;
|
||||
|
||||
// We now have our aliased path, our path require function just needs it
|
||||
// in a slightly different format with both absolute + relative to cwd
|
||||
let abs_path = luaurc.find_alias(&alias).unwrap().join(path);
|
||||
let rel_path = pathdiff::diff_paths(&abs_path, CWD.as_path()).ok_or_else(|| {
|
||||
LuaError::runtime(format!("failed to find relative path for alias '{alias}'"))
|
||||
})?;
|
||||
|
||||
super::path::require_abs_rel(lua, ctx, abs_path, rel_path).await
|
||||
}
|
14
crates/lune-std/src/globals/require/builtin.rs
Normal file
14
crates/lune-std/src/globals/require/builtin.rs
Normal file
|
@ -0,0 +1,14 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
use super::context::*;
|
||||
|
||||
pub(super) async fn require<'lua, 'ctx>(
|
||||
lua: &'lua Lua,
|
||||
ctx: &'ctx RequireContext,
|
||||
name: &str,
|
||||
) -> LuaResult<LuaMultiValue<'lua>>
|
||||
where
|
||||
'lua: 'ctx,
|
||||
{
|
||||
ctx.load_library(lua, name)
|
||||
}
|
292
crates/lune-std/src/globals/require/context.rs
Normal file
292
crates/lune-std/src/globals/require/context.rs
Normal file
|
@ -0,0 +1,292 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use mlua_luau_scheduler::LuaSchedulerExt;
|
||||
|
||||
use tokio::{
|
||||
fs::read,
|
||||
sync::{
|
||||
broadcast::{self, Sender},
|
||||
Mutex as AsyncMutex,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::library::LuneStandardLibrary;
|
||||
|
||||
/**
|
||||
Context containing cached results for all `require` operations.
|
||||
|
||||
The cache uses absolute paths, so any given relative
|
||||
path will first be transformed into an absolute path.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct RequireContext {
|
||||
cache_libraries: Arc<AsyncMutex<HashMap<LuneStandardLibrary, LuaResult<LuaRegistryKey>>>>,
|
||||
cache_results: Arc<AsyncMutex<HashMap<PathBuf, LuaResult<LuaRegistryKey>>>>,
|
||||
cache_pending: Arc<AsyncMutex<HashMap<PathBuf, Sender<()>>>>,
|
||||
}
|
||||
|
||||
impl RequireContext {
|
||||
/**
|
||||
Creates a new require context for the given [`Lua`] struct.
|
||||
|
||||
Note that this require context is global and only one require
|
||||
context should be created per [`Lua`] struct, creating more
|
||||
than one context may lead to undefined require-behavior.
|
||||
*/
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cache_libraries: Arc::new(AsyncMutex::new(HashMap::new())),
|
||||
cache_results: Arc::new(AsyncMutex::new(HashMap::new())),
|
||||
cache_pending: Arc::new(AsyncMutex::new(HashMap::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Resolves the given `source` and `path` into require paths
|
||||
to use, based on the current require context settings.
|
||||
|
||||
This will resolve path segments such as `./`, `../`, ..., and
|
||||
if the resolved path is not an absolute path, will create an
|
||||
absolute path by prepending the current working directory.
|
||||
*/
|
||||
pub fn resolve_paths(
|
||||
&self,
|
||||
source: impl AsRef<str>,
|
||||
path: impl AsRef<str>,
|
||||
) -> LuaResult<(PathBuf, PathBuf)> {
|
||||
let path = PathBuf::from(source.as_ref())
|
||||
.parent()
|
||||
.ok_or_else(|| LuaError::runtime("Failed to get parent path of source"))?
|
||||
.join(path.as_ref());
|
||||
|
||||
let rel_path = path_clean::clean(path);
|
||||
let abs_path = if rel_path.is_absolute() {
|
||||
rel_path.to_path_buf()
|
||||
} else {
|
||||
CWD.join(&rel_path)
|
||||
};
|
||||
|
||||
Ok((abs_path, rel_path))
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the given path has a cached require result.
|
||||
*/
|
||||
pub fn is_cached(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
|
||||
let is_cached = self
|
||||
.cache_results
|
||||
.try_lock()
|
||||
.expect("RequireContext may not be used from multiple threads")
|
||||
.contains_key(abs_path.as_ref());
|
||||
Ok(is_cached)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the given path is currently being used in `require`.
|
||||
*/
|
||||
pub fn is_pending(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
|
||||
let is_pending = self
|
||||
.cache_pending
|
||||
.try_lock()
|
||||
.expect("RequireContext may not be used from multiple threads")
|
||||
.contains_key(abs_path.as_ref());
|
||||
Ok(is_pending)
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the resulting value from the require cache.
|
||||
|
||||
Will panic if the path has not been cached, use [`is_cached`] first.
|
||||
*/
|
||||
pub fn get_from_cache<'lua>(
|
||||
&self,
|
||||
lua: &'lua Lua,
|
||||
abs_path: impl AsRef<Path>,
|
||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||
let results = self
|
||||
.cache_results
|
||||
.try_lock()
|
||||
.expect("RequireContext may not be used from multiple threads");
|
||||
|
||||
let cached = results
|
||||
.get(abs_path.as_ref())
|
||||
.expect("Path does not exist in results cache");
|
||||
match cached {
|
||||
Err(e) => Err(e.clone()),
|
||||
Ok(k) => {
|
||||
let multi_vec = lua
|
||||
.registry_value::<Vec<LuaValue>>(k)
|
||||
.expect("Missing require result in lua registry");
|
||||
Ok(LuaMultiValue::from_vec(multi_vec))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Waits for the resulting value from the require cache.
|
||||
|
||||
Will panic if the path has not been cached, use [`is_cached`] first.
|
||||
*/
|
||||
pub async fn wait_for_cache<'lua>(
|
||||
&self,
|
||||
lua: &'lua Lua,
|
||||
abs_path: impl AsRef<Path>,
|
||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||
let mut thread_recv = {
|
||||
let pending = self
|
||||
.cache_pending
|
||||
.try_lock()
|
||||
.expect("RequireContext may not be used from multiple threads");
|
||||
let thread_id = pending
|
||||
.get(abs_path.as_ref())
|
||||
.expect("Path is not currently pending require");
|
||||
thread_id.subscribe()
|
||||
};
|
||||
|
||||
thread_recv.recv().await.into_lua_err()?;
|
||||
|
||||
self.get_from_cache(lua, abs_path.as_ref())
|
||||
}
|
||||
|
||||
async fn load<'lua>(
|
||||
&self,
|
||||
lua: &'lua Lua,
|
||||
abs_path: impl AsRef<Path>,
|
||||
rel_path: impl AsRef<Path>,
|
||||
) -> LuaResult<LuaRegistryKey> {
|
||||
let abs_path = abs_path.as_ref();
|
||||
let rel_path = rel_path.as_ref();
|
||||
|
||||
// Read the file at the given path, try to parse and
|
||||
// load it into a new lua thread that we can schedule
|
||||
let file_contents = read(&abs_path).await?;
|
||||
let file_thread = lua
|
||||
.load(file_contents)
|
||||
.set_name(rel_path.to_string_lossy().to_string());
|
||||
|
||||
// Schedule the thread to run, wait for it to finish running
|
||||
let thread_id = lua.push_thread_back(file_thread, ())?;
|
||||
lua.track_thread(thread_id);
|
||||
lua.wait_for_thread(thread_id).await;
|
||||
let thread_res = lua.get_thread_result(thread_id).unwrap();
|
||||
|
||||
// Return the result of the thread, storing any lua value(s) in the registry
|
||||
match thread_res {
|
||||
Err(e) => Err(e),
|
||||
Ok(v) => {
|
||||
let multi_vec = v.into_vec();
|
||||
let multi_key = lua
|
||||
.create_registry_value(multi_vec)
|
||||
.expect("Failed to store require result in registry - out of memory");
|
||||
Ok(multi_key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Loads (requires) the file at the given path.
|
||||
*/
|
||||
pub async fn load_with_caching<'lua>(
|
||||
&self,
|
||||
lua: &'lua Lua,
|
||||
abs_path: impl AsRef<Path>,
|
||||
rel_path: impl AsRef<Path>,
|
||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||
let abs_path = abs_path.as_ref();
|
||||
let rel_path = rel_path.as_ref();
|
||||
|
||||
// Set this abs path as currently pending
|
||||
let (broadcast_tx, _) = broadcast::channel(1);
|
||||
self.cache_pending
|
||||
.try_lock()
|
||||
.expect("RequireContext may not be used from multiple threads")
|
||||
.insert(abs_path.to_path_buf(), broadcast_tx);
|
||||
|
||||
// Try to load at this abs path
|
||||
let load_res = self.load(lua, abs_path, rel_path).await;
|
||||
let load_val = match &load_res {
|
||||
Err(e) => Err(e.clone()),
|
||||
Ok(k) => {
|
||||
let multi_vec = lua
|
||||
.registry_value::<Vec<LuaValue>>(k)
|
||||
.expect("Failed to fetch require result from registry");
|
||||
Ok(LuaMultiValue::from_vec(multi_vec))
|
||||
}
|
||||
};
|
||||
|
||||
// NOTE: We use the async lock and not try_lock here because
|
||||
// some other thread may be wanting to insert into the require
|
||||
// cache at the same time, and that's not an actual error case
|
||||
self.cache_results
|
||||
.lock()
|
||||
.await
|
||||
.insert(abs_path.to_path_buf(), load_res);
|
||||
|
||||
// Remove the pending thread id from the require context,
|
||||
// broadcast a message to let any listeners know that this
|
||||
// path has now finished the require process and is cached
|
||||
let broadcast_tx = self
|
||||
.cache_pending
|
||||
.try_lock()
|
||||
.expect("RequireContext may not be used from multiple threads")
|
||||
.remove(abs_path)
|
||||
.expect("Pending require broadcaster was unexpectedly removed");
|
||||
broadcast_tx.send(()).ok();
|
||||
|
||||
load_val
|
||||
}
|
||||
|
||||
/**
|
||||
Loads (requires) the library with the given name.
|
||||
*/
|
||||
pub fn load_library<'lua>(
|
||||
&self,
|
||||
lua: &'lua Lua,
|
||||
name: impl AsRef<str>,
|
||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||
let library: LuneStandardLibrary = match name.as_ref().parse() {
|
||||
Err(e) => return Err(LuaError::runtime(e)),
|
||||
Ok(b) => b,
|
||||
};
|
||||
|
||||
let mut cache = self
|
||||
.cache_libraries
|
||||
.try_lock()
|
||||
.expect("RequireContext may not be used from multiple threads");
|
||||
|
||||
if let Some(res) = cache.get(&library) {
|
||||
return match res {
|
||||
Err(e) => return Err(e.clone()),
|
||||
Ok(key) => {
|
||||
let multi_vec = lua
|
||||
.registry_value::<Vec<LuaValue>>(key)
|
||||
.expect("Missing library result in lua registry");
|
||||
Ok(LuaMultiValue::from_vec(multi_vec))
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
let result = library.module(lua);
|
||||
|
||||
cache.insert(
|
||||
library,
|
||||
match result.clone() {
|
||||
Err(e) => Err(e),
|
||||
Ok(multi) => {
|
||||
let multi_vec = multi.into_vec();
|
||||
let multi_key = lua
|
||||
.create_registry_value(multi_vec)
|
||||
.expect("Failed to store require result in registry - out of memory");
|
||||
Ok(multi_key)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
result
|
||||
}
|
||||
}
|
96
crates/lune-std/src/globals/require/mod.rs
Normal file
96
crates/lune-std/src/globals/require/mod.rs
Normal file
|
@ -0,0 +1,96 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
mod context;
|
||||
use context::RequireContext;
|
||||
|
||||
mod alias;
|
||||
mod builtin;
|
||||
mod path;
|
||||
|
||||
const REQUIRE_IMPL: &str = r"
|
||||
return require(source(), ...)
|
||||
";
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||
lua.set_app_data(RequireContext::new());
|
||||
|
||||
/*
|
||||
Require implementation needs a few workarounds:
|
||||
|
||||
- Async functions run outside of the lua resumption cycle,
|
||||
so the current lua thread, as well as its stack/debug info
|
||||
is not available, meaning we have to use a normal function
|
||||
|
||||
- Using the async require function directly in another lua function
|
||||
would mean yielding across the metamethod/c-call boundary, meaning
|
||||
we have to first load our two functions into a normal lua chunk
|
||||
and then load that new chunk into our final require function
|
||||
|
||||
Also note that we inspect the stack at level 2:
|
||||
|
||||
1. The current c / rust function
|
||||
2. The wrapper lua chunk defined above
|
||||
3. The lua chunk we are require-ing from
|
||||
*/
|
||||
|
||||
let require_fn = lua.create_async_function(require)?;
|
||||
let get_source_fn = lua.create_function(move |lua, _: ()| match lua.inspect_stack(2) {
|
||||
None => Err(LuaError::runtime(
|
||||
"Failed to get stack info for require source",
|
||||
)),
|
||||
Some(info) => match info.source().source {
|
||||
None => Err(LuaError::runtime(
|
||||
"Stack info is missing source for require",
|
||||
)),
|
||||
Some(source) => lua.create_string(source.as_bytes()),
|
||||
},
|
||||
})?;
|
||||
|
||||
let require_env = TableBuilder::new(lua)?
|
||||
.with_value("source", get_source_fn)?
|
||||
.with_value("require", require_fn)?
|
||||
.build_readonly()?;
|
||||
|
||||
lua.load(REQUIRE_IMPL)
|
||||
.set_name("require")
|
||||
.set_environment(require_env)
|
||||
.into_function()?
|
||||
.into_lua(lua)
|
||||
}
|
||||
|
||||
async fn require<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(source, path): (LuaString<'lua>, LuaString<'lua>),
|
||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||
let source = source
|
||||
.to_str()
|
||||
.into_lua_err()
|
||||
.context("Failed to parse require source as string")?
|
||||
.to_string();
|
||||
|
||||
let path = path
|
||||
.to_str()
|
||||
.into_lua_err()
|
||||
.context("Failed to parse require path as string")?
|
||||
.to_string();
|
||||
|
||||
let context = lua
|
||||
.app_data_ref()
|
||||
.expect("Failed to get RequireContext from app data");
|
||||
|
||||
if let Some(builtin_name) = path
|
||||
.strip_prefix("@lune/")
|
||||
.map(|name| name.to_ascii_lowercase())
|
||||
{
|
||||
builtin::require(lua, &context, &builtin_name).await
|
||||
} else if let Some(aliased_path) = path.strip_prefix('@') {
|
||||
let (alias, path) = aliased_path.split_once('/').ok_or(LuaError::runtime(
|
||||
"Require with custom alias must contain '/' delimiter",
|
||||
))?;
|
||||
alias::require(lua, &context, &source, alias, path).await
|
||||
} else {
|
||||
path::require(lua, &context, &source, &path).await
|
||||
}
|
||||
}
|
129
crates/lune-std/src/globals/require/path.rs
Normal file
129
crates/lune-std/src/globals/require/path.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use mlua::Error::ExternalError;
|
||||
|
||||
use super::context::*;
|
||||
|
||||
pub(super) async fn require<'lua, 'ctx>(
|
||||
lua: &'lua Lua,
|
||||
ctx: &'ctx RequireContext,
|
||||
source: &str,
|
||||
path: &str,
|
||||
) -> LuaResult<LuaMultiValue<'lua>>
|
||||
where
|
||||
'lua: 'ctx,
|
||||
{
|
||||
let (abs_path, rel_path) = ctx.resolve_paths(source, path)?;
|
||||
require_abs_rel(lua, ctx, abs_path, rel_path).await
|
||||
}
|
||||
|
||||
pub(super) async fn require_abs_rel<'lua, 'ctx>(
|
||||
lua: &'lua Lua,
|
||||
ctx: &'ctx RequireContext,
|
||||
abs_path: PathBuf, // Absolute to filesystem
|
||||
rel_path: PathBuf, // Relative to CWD (for displaying)
|
||||
) -> LuaResult<LuaMultiValue<'lua>>
|
||||
where
|
||||
'lua: 'ctx,
|
||||
{
|
||||
// 1. Try to require the exact path
|
||||
match require_inner(lua, ctx, &abs_path, &rel_path).await {
|
||||
Ok(res) => return Ok(res),
|
||||
Err(err) => {
|
||||
if !is_file_not_found_error(&err) {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try to require the path with an added "luau" extension
|
||||
// 3. Try to require the path with an added "lua" extension
|
||||
for extension in ["luau", "lua"] {
|
||||
match require_inner(
|
||||
lua,
|
||||
ctx,
|
||||
&append_extension(&abs_path, extension),
|
||||
&append_extension(&rel_path, extension),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => return Ok(res),
|
||||
Err(err) => {
|
||||
if !is_file_not_found_error(&err) {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find any direct file paths, look
|
||||
// for directories with "init" files in them...
|
||||
let abs_init = abs_path.join("init");
|
||||
let rel_init = rel_path.join("init");
|
||||
|
||||
// 4. Try to require the init path with an added "luau" extension
|
||||
// 5. Try to require the init path with an added "lua" extension
|
||||
for extension in ["luau", "lua"] {
|
||||
match require_inner(
|
||||
lua,
|
||||
ctx,
|
||||
&append_extension(&abs_init, extension),
|
||||
&append_extension(&rel_init, extension),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(res) => return Ok(res),
|
||||
Err(err) => {
|
||||
if !is_file_not_found_error(&err) {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing left to try, throw an error
|
||||
Err(LuaError::runtime(format!(
|
||||
"No file exists at the path '{}'",
|
||||
rel_path.display()
|
||||
)))
|
||||
}
|
||||
|
||||
async fn require_inner<'lua, 'ctx>(
|
||||
lua: &'lua Lua,
|
||||
ctx: &'ctx RequireContext,
|
||||
abs_path: impl AsRef<Path>,
|
||||
rel_path: impl AsRef<Path>,
|
||||
) -> LuaResult<LuaMultiValue<'lua>>
|
||||
where
|
||||
'lua: 'ctx,
|
||||
{
|
||||
let abs_path = abs_path.as_ref();
|
||||
let rel_path = rel_path.as_ref();
|
||||
|
||||
if ctx.is_cached(abs_path)? {
|
||||
ctx.get_from_cache(lua, abs_path)
|
||||
} else if ctx.is_pending(abs_path)? {
|
||||
ctx.wait_for_cache(lua, &abs_path).await
|
||||
} else {
|
||||
ctx.load_with_caching(lua, &abs_path, &rel_path).await
|
||||
}
|
||||
}
|
||||
|
||||
fn append_extension(path: impl Into<PathBuf>, ext: &'static str) -> PathBuf {
|
||||
let mut new = path.into();
|
||||
match new.extension() {
|
||||
// FUTURE: There's probably a better way to do this than converting to a lossy string
|
||||
Some(e) => new.set_extension(format!("{}.{ext}", e.to_string_lossy())),
|
||||
None => new.set_extension(ext),
|
||||
};
|
||||
new
|
||||
}
|
||||
|
||||
fn is_file_not_found_error(err: &LuaError) -> bool {
|
||||
if let ExternalError(err) = err {
|
||||
err.as_ref().downcast_ref::<std::io::Error>().is_some()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
8
crates/lune-std/src/globals/version.rs
Normal file
8
crates/lune-std/src/globals/version.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::get_version_string;
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||
let s = get_version_string().to_string();
|
||||
lua.create_string(s)?.into_lua(lua)
|
||||
}
|
9
crates/lune-std/src/globals/warn.rs
Normal file
9
crates/lune-std/src/globals/warn.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||
let f = lua.create_function(|_, args: LuaMultiValue| {
|
||||
// TODO: Port this over from the old crate
|
||||
Ok(())
|
||||
})?;
|
||||
f.into_lua(lua)
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
mod global;
|
||||
mod globals;
|
||||
mod library;
|
||||
|
||||
pub use library::LuneStandardLibrary;
|
||||
pub use self::global::LuneStandardGlobal;
|
||||
pub use self::library::LuneStandardLibrary;
|
||||
|
|
|
@ -112,10 +112,16 @@ impl FromStr for LuneStandardLibrary {
|
|||
#[cfg(feature = "stdio")] "stdio" => Self::Stdio,
|
||||
#[cfg(feature = "roblox")] "roblox" => Self::Roblox,
|
||||
|
||||
_ => return Err(format!(
|
||||
"Unknown standard library '{low}'\nValid libraries are: {}",
|
||||
Self::ALL.iter().map(Self::name).collect::<Vec<_>>().join(", ")
|
||||
)),
|
||||
_ => {
|
||||
return Err(format!(
|
||||
"Unknown standard library '{low}'\nValid libraries are: {}",
|
||||
Self::ALL
|
||||
.iter()
|
||||
.map(Self::name)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue