mirror of
https://github.com/lune-org/lune.git
synced 2025-04-10 21:40:54 +01:00
Add luaurc to lune-utils crate, resolve issues with require in lune-std
This commit is contained in:
parent
cf7e1f3cd9
commit
a714efdac4
9 changed files with 196 additions and 26 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -1651,6 +1651,9 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"path-clean",
|
"path-clean",
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
use lune_utils::{
|
use lune_utils::{
|
||||||
luaurc::LuauRc,
|
|
||||||
path::{clean_path_and_make_absolute, diff_path, get_current_dir},
|
path::{clean_path_and_make_absolute, diff_path, get_current_dir},
|
||||||
|
LuauRc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::context::*;
|
use super::context::*;
|
||||||
|
|
|
@ -27,9 +27,9 @@ use crate::library::LuneStandardLibrary;
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(super) struct RequireContext {
|
pub(super) struct RequireContext {
|
||||||
cache_libraries: Arc<AsyncMutex<HashMap<LuneStandardLibrary, LuaResult<LuaRegistryKey>>>>,
|
libraries: Arc<AsyncMutex<HashMap<LuneStandardLibrary, LuaResult<LuaRegistryKey>>>>,
|
||||||
cache_results: Arc<AsyncMutex<HashMap<PathBuf, LuaResult<LuaRegistryKey>>>>,
|
results: Arc<AsyncMutex<HashMap<PathBuf, LuaResult<LuaRegistryKey>>>>,
|
||||||
cache_pending: Arc<AsyncMutex<HashMap<PathBuf, Sender<()>>>>,
|
pending: Arc<AsyncMutex<HashMap<PathBuf, Sender<()>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequireContext {
|
impl RequireContext {
|
||||||
|
@ -42,9 +42,9 @@ impl RequireContext {
|
||||||
*/
|
*/
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
cache_libraries: Arc::new(AsyncMutex::new(HashMap::new())),
|
libraries: Arc::new(AsyncMutex::new(HashMap::new())),
|
||||||
cache_results: Arc::new(AsyncMutex::new(HashMap::new())),
|
results: Arc::new(AsyncMutex::new(HashMap::new())),
|
||||||
cache_pending: Arc::new(AsyncMutex::new(HashMap::new())),
|
pending: Arc::new(AsyncMutex::new(HashMap::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +57,6 @@ impl RequireContext {
|
||||||
absolute path by prepending the current working directory.
|
absolute path by prepending the current working directory.
|
||||||
*/
|
*/
|
||||||
pub fn resolve_paths(
|
pub fn resolve_paths(
|
||||||
&self,
|
|
||||||
source: impl AsRef<str>,
|
source: impl AsRef<str>,
|
||||||
path: impl AsRef<str>,
|
path: impl AsRef<str>,
|
||||||
) -> LuaResult<(PathBuf, PathBuf)> {
|
) -> LuaResult<(PathBuf, PathBuf)> {
|
||||||
|
@ -66,7 +65,7 @@ impl RequireContext {
|
||||||
.ok_or_else(|| LuaError::runtime("Failed to get parent path of source"))?
|
.ok_or_else(|| LuaError::runtime("Failed to get parent path of source"))?
|
||||||
.join(path.as_ref());
|
.join(path.as_ref());
|
||||||
|
|
||||||
let abs_path = clean_path_and_make_absolute(path);
|
let abs_path = clean_path_and_make_absolute(&path);
|
||||||
let rel_path = clean_path(path);
|
let rel_path = clean_path(path);
|
||||||
|
|
||||||
Ok((abs_path, rel_path))
|
Ok((abs_path, rel_path))
|
||||||
|
@ -77,7 +76,7 @@ impl RequireContext {
|
||||||
*/
|
*/
|
||||||
pub fn is_cached(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
|
pub fn is_cached(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
|
||||||
let is_cached = self
|
let is_cached = self
|
||||||
.cache_results
|
.results
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.expect("RequireContext may not be used from multiple threads")
|
.expect("RequireContext may not be used from multiple threads")
|
||||||
.contains_key(abs_path.as_ref());
|
.contains_key(abs_path.as_ref());
|
||||||
|
@ -89,7 +88,7 @@ impl RequireContext {
|
||||||
*/
|
*/
|
||||||
pub fn is_pending(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
|
pub fn is_pending(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
|
||||||
let is_pending = self
|
let is_pending = self
|
||||||
.cache_pending
|
.pending
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.expect("RequireContext may not be used from multiple threads")
|
.expect("RequireContext may not be used from multiple threads")
|
||||||
.contains_key(abs_path.as_ref());
|
.contains_key(abs_path.as_ref());
|
||||||
|
@ -107,7 +106,7 @@ impl RequireContext {
|
||||||
abs_path: impl AsRef<Path>,
|
abs_path: impl AsRef<Path>,
|
||||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||||
let results = self
|
let results = self
|
||||||
.cache_results
|
.results
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.expect("RequireContext may not be used from multiple threads");
|
.expect("RequireContext may not be used from multiple threads");
|
||||||
|
|
||||||
|
@ -137,7 +136,7 @@ impl RequireContext {
|
||||||
) -> LuaResult<LuaMultiValue<'lua>> {
|
) -> LuaResult<LuaMultiValue<'lua>> {
|
||||||
let mut thread_recv = {
|
let mut thread_recv = {
|
||||||
let pending = self
|
let pending = self
|
||||||
.cache_pending
|
.pending
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.expect("RequireContext may not be used from multiple threads");
|
.expect("RequireContext may not be used from multiple threads");
|
||||||
let thread_id = pending
|
let thread_id = pending
|
||||||
|
@ -200,7 +199,7 @@ impl RequireContext {
|
||||||
|
|
||||||
// Set this abs path as currently pending
|
// Set this abs path as currently pending
|
||||||
let (broadcast_tx, _) = broadcast::channel(1);
|
let (broadcast_tx, _) = broadcast::channel(1);
|
||||||
self.cache_pending
|
self.pending
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.expect("RequireContext may not be used from multiple threads")
|
.expect("RequireContext may not be used from multiple threads")
|
||||||
.insert(abs_path.to_path_buf(), broadcast_tx);
|
.insert(abs_path.to_path_buf(), broadcast_tx);
|
||||||
|
@ -220,7 +219,7 @@ impl RequireContext {
|
||||||
// NOTE: We use the async lock and not try_lock here because
|
// NOTE: We use the async lock and not try_lock here because
|
||||||
// some other thread may be wanting to insert into the require
|
// some other thread may be wanting to insert into the require
|
||||||
// cache at the same time, and that's not an actual error case
|
// cache at the same time, and that's not an actual error case
|
||||||
self.cache_results
|
self.results
|
||||||
.lock()
|
.lock()
|
||||||
.await
|
.await
|
||||||
.insert(abs_path.to_path_buf(), load_res);
|
.insert(abs_path.to_path_buf(), load_res);
|
||||||
|
@ -229,7 +228,7 @@ impl RequireContext {
|
||||||
// broadcast a message to let any listeners know that this
|
// broadcast a message to let any listeners know that this
|
||||||
// path has now finished the require process and is cached
|
// path has now finished the require process and is cached
|
||||||
let broadcast_tx = self
|
let broadcast_tx = self
|
||||||
.cache_pending
|
.pending
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.expect("RequireContext may not be used from multiple threads")
|
.expect("RequireContext may not be used from multiple threads")
|
||||||
.remove(abs_path)
|
.remove(abs_path)
|
||||||
|
@ -253,7 +252,7 @@ impl RequireContext {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut cache = self
|
let mut cache = self
|
||||||
.cache_libraries
|
.libraries
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.expect("RequireContext may not be used from multiple threads");
|
.expect("RequireContext may not be used from multiple threads");
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ use mlua::prelude::*;
|
||||||
|
|
||||||
use super::context::*;
|
use super::context::*;
|
||||||
|
|
||||||
pub(super) async fn require<'lua, 'ctx>(
|
pub(super) fn require<'lua, 'ctx>(
|
||||||
lua: &'lua Lua,
|
lua: &'lua Lua,
|
||||||
ctx: &'ctx RequireContext,
|
ctx: &'ctx RequireContext,
|
||||||
name: &str,
|
name: &str,
|
|
@ -6,7 +6,7 @@ mod context;
|
||||||
use context::RequireContext;
|
use context::RequireContext;
|
||||||
|
|
||||||
mod alias;
|
mod alias;
|
||||||
mod builtin;
|
mod library;
|
||||||
mod path;
|
mod path;
|
||||||
|
|
||||||
const REQUIRE_IMPL: &str = r"
|
const REQUIRE_IMPL: &str = r"
|
||||||
|
@ -36,7 +36,7 @@ pub fn create(lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let require_fn = lua.create_async_function(require)?;
|
let require_fn = lua.create_async_function(require)?;
|
||||||
let get_source_fn = lua.create_function(move |lua, _: ()| match lua.inspect_stack(2) {
|
let get_source_fn = lua.create_function(move |lua, (): ()| match lua.inspect_stack(2) {
|
||||||
None => Err(LuaError::runtime(
|
None => Err(LuaError::runtime(
|
||||||
"Failed to get stack info for require source",
|
"Failed to get stack info for require source",
|
||||||
)),
|
)),
|
||||||
|
@ -80,11 +80,8 @@ async fn require<'lua>(
|
||||||
.app_data_ref()
|
.app_data_ref()
|
||||||
.expect("Failed to get RequireContext from app data");
|
.expect("Failed to get RequireContext from app data");
|
||||||
|
|
||||||
if let Some(builtin_name) = path
|
if let Some(builtin_name) = path.strip_prefix("@lune/").map(str::to_ascii_lowercase) {
|
||||||
.strip_prefix("@lune/")
|
library::require(lua, &context, &builtin_name)
|
||||||
.map(|name| name.to_ascii_lowercase())
|
|
||||||
{
|
|
||||||
builtin::require(lua, &context, &builtin_name).await
|
|
||||||
} else if let Some(aliased_path) = path.strip_prefix('@') {
|
} else if let Some(aliased_path) = path.strip_prefix('@') {
|
||||||
let (alias, path) = aliased_path.split_once('/').ok_or(LuaError::runtime(
|
let (alias, path) = aliased_path.split_once('/').ok_or(LuaError::runtime(
|
||||||
"Require with custom alias must contain '/' delimiter",
|
"Require with custom alias must contain '/' delimiter",
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub(super) async fn require<'lua, 'ctx>(
|
||||||
where
|
where
|
||||||
'lua: 'ctx,
|
'lua: 'ctx,
|
||||||
{
|
{
|
||||||
let (abs_path, rel_path) = ctx.resolve_paths(source, path)?;
|
let (abs_path, rel_path) = RequireContext::resolve_paths(source, path)?;
|
||||||
require_abs_rel(lua, ctx, abs_path, rel_path).await
|
require_abs_rel(lua, ctx, abs_path, rel_path).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,12 @@ workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["async"] }
|
mlua = { version = "0.9.7", features = ["async"] }
|
||||||
|
|
||||||
|
tokio = { version = "1", default-features = false, features = ["fs"] }
|
||||||
|
|
||||||
dunce = "1.0"
|
dunce = "1.0"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
path-clean = "1.0"
|
path-clean = "1.0"
|
||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
|
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
#![allow(clippy::cargo_common_metadata)]
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
mod luaurc;
|
||||||
mod table_builder;
|
mod table_builder;
|
||||||
mod version_string;
|
mod version_string;
|
||||||
|
|
||||||
pub mod path;
|
pub mod path;
|
||||||
|
|
||||||
|
pub use self::luaurc::LuauRc;
|
||||||
pub use self::table_builder::TableBuilder;
|
pub use self::table_builder::TableBuilder;
|
||||||
pub use self::version_string::get_version_string;
|
pub use self::version_string::get_version_string;
|
||||||
|
|
164
crates/lune-utils/src/luaurc.rs
Normal file
164
crates/lune-utils/src/luaurc.rs
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
path::{Path, PathBuf, MAIN_SEPARATOR},
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::Value as JsonValue;
|
||||||
|
use tokio::fs::read;
|
||||||
|
|
||||||
|
use crate::path::{clean_path, clean_path_and_make_absolute};
|
||||||
|
|
||||||
|
const LUAURC_FILE: &str = ".luaurc";
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
enum LuauLanguageMode {
|
||||||
|
NoCheck,
|
||||||
|
NonStrict,
|
||||||
|
Strict,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
struct LuauRcConfig {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
language_mode: Option<LuauLanguageMode>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
lint: Option<HashMap<String, JsonValue>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
lint_errors: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
type_errors: Option<bool>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
globals: Option<Vec<String>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
paths: Option<Vec<String>>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
aliases: Option<HashMap<String, String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
A deserialized `.luaurc` file.
|
||||||
|
|
||||||
|
Contains utility methods for validating and searching for aliases.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LuauRc {
|
||||||
|
dir: Arc<Path>,
|
||||||
|
config: LuauRcConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuauRc {
|
||||||
|
/**
|
||||||
|
Reads a `.luaurc` file from the given directory.
|
||||||
|
|
||||||
|
If the file does not exist, or if it is invalid, this function returns `None`.
|
||||||
|
*/
|
||||||
|
pub async fn read(dir: impl AsRef<Path>) -> Option<Self> {
|
||||||
|
let dir = clean_path_and_make_absolute(dir);
|
||||||
|
let path = dir.join(LUAURC_FILE);
|
||||||
|
let bytes = read(&path).await.ok()?;
|
||||||
|
let config = serde_json::from_slice(&bytes).ok()?;
|
||||||
|
Some(Self {
|
||||||
|
dir: dir.into(),
|
||||||
|
config,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Reads a `.luaurc` file from the given directory, and then recursively searches
|
||||||
|
for a `.luaurc` file in the parent directories if a predicate is not satisfied.
|
||||||
|
|
||||||
|
If no `.luaurc` file exists, or if they are invalid, this function returns `None`.
|
||||||
|
*/
|
||||||
|
pub async fn read_recursive(
|
||||||
|
dir: impl AsRef<Path>,
|
||||||
|
mut predicate: impl FnMut(&Self) -> bool,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let mut current = clean_path_and_make_absolute(dir);
|
||||||
|
loop {
|
||||||
|
if let Some(rc) = Self::read(¤t).await {
|
||||||
|
if predicate(&rc) {
|
||||||
|
return Some(rc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(parent) = current.parent() {
|
||||||
|
current = parent.to_path_buf();
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Validates that the `.luaurc` file is correct.
|
||||||
|
|
||||||
|
This primarily validates aliases since they are not
|
||||||
|
validated during creation of the [`LuauRc`] struct.
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
|
||||||
|
If an alias key is invalid.
|
||||||
|
*/
|
||||||
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
|
if let Some(aliases) = &self.config.aliases {
|
||||||
|
for alias in aliases.keys() {
|
||||||
|
if !is_valid_alias_key(alias) {
|
||||||
|
return Err(format!("invalid alias key: {alias}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Gets a copy of all aliases in the `.luaurc` file.
|
||||||
|
|
||||||
|
Will return an empty map if there are no aliases.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn aliases(&self) -> HashMap<String, String> {
|
||||||
|
self.config.aliases.clone().unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Finds an alias in the `.luaurc` file by name.
|
||||||
|
|
||||||
|
If the alias does not exist, this function returns `None`.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn find_alias(&self, name: &str) -> Option<PathBuf> {
|
||||||
|
self.config.aliases.as_ref().and_then(|aliases| {
|
||||||
|
aliases.iter().find_map(|(alias, path)| {
|
||||||
|
if alias
|
||||||
|
.trim_end_matches(MAIN_SEPARATOR)
|
||||||
|
.eq_ignore_ascii_case(name)
|
||||||
|
&& is_valid_alias_key(alias)
|
||||||
|
{
|
||||||
|
Some(clean_path(self.dir.join(path)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_alias_key(alias: impl AsRef<str>) -> bool {
|
||||||
|
let alias = alias.as_ref();
|
||||||
|
if alias.is_empty()
|
||||||
|
|| alias.starts_with('.')
|
||||||
|
|| alias.starts_with("..")
|
||||||
|
|| alias.chars().any(|c| c == MAIN_SEPARATOR)
|
||||||
|
{
|
||||||
|
false // Paths are not valid alias keys
|
||||||
|
} else {
|
||||||
|
alias.chars().all(is_valid_alias_char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_alias_char(c: char) -> bool {
|
||||||
|
c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.'
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue