Skip to content

Commit

Permalink
Implement support for path aliases in require
Browse files Browse the repository at this point in the history
  • Loading branch information
filiptibell committed Jan 14, 2024
1 parent 0392c16 commit b07202a
Show file tree
Hide file tree
Showing 15 changed files with 287 additions and 31 deletions.
7 changes: 6 additions & 1 deletion .luaurc
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,10 @@
"typeErrors": true,
"globals": [
"warn"
]
],
"aliases": {
"lune": "./types/",
"tests": "./tests",
"require-tests": "./tests/require/tests"
}
}
4 changes: 3 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
"luau-lsp.types.roblox": false,
"luau-lsp.require.mode": "relativeToFile",
"luau-lsp.require.directoryAliases": {
"@lune/": "./types/"
"@lune/": "./types/",
"@tests/": "./tests/",
"@require-tests/": "./tests/require/tests/"
},
"luau-lsp.ignoreGlobs": [
"tests/roblox/rbx-test-files/**/*.lua",
Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

To compile scripts that use `require` and reference multiple files, a bundler such as [darklua](https://github.com/seaofvoices/darklua) will need to be used first. This limitation will be lifted in the future and Lune will automatically bundle any referenced scripts.

- Added support for path aliases using `.luaurc` config files!

For full documentation and reference, check out the [official Luau RFC](https://rfcs.luau-lang.org/require-by-string-aliases.html), but here's a quick example:

```jsonc
// .luaurc
{
"aliases": {
"modules": "./some/long/path/to/modules"
}
}
```

```lua
-- ./some/long/path/to/modules/foo.luau
return { World = "World!" }

-- ./anywhere/you/want/my_script.luau
local mod = require("@modules/foo")
print("Hello, " .. mod.World)
```

- Added support for multiple values for a single query, and multiple values for a single header, in `net.request`. This is a part of the HTTP specification that is not widely used but that may be useful in certain cases. To clarify:

- Single values remain unchanged and will work exactly the same as before. <br/>
Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ dialoguer = "0.11"
dunce = "1.0"
lz4_flex = "0.11"
path-clean = "1.0"
pathdiff = "0.2"
pin-project = "1.0"
urlencoding = "2.1"

Expand Down
9 changes: 5 additions & 4 deletions src/lune/builtins/process/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ use std::{
process::Stdio,
};

use dunce::canonicalize;
use mlua::prelude::*;
use os_str_bytes::RawOsString;
use tokio::io::AsyncWriteExt;

use crate::lune::{scheduler::Scheduler, util::TableBuilder};
use crate::lune::{
scheduler::Scheduler,
util::{paths::CWD, TableBuilder},
};

mod tee_writer;

Expand All @@ -26,8 +28,7 @@ yield()

pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
let cwd_str = {
let cwd = canonicalize(env::current_dir()?)?;
let cwd_str = cwd.to_string_lossy().to_string();
let cwd_str = CWD.to_string_lossy().to_string();
if !cwd_str.ends_with(path::MAIN_SEPARATOR) {
format!("{cwd_str}{}", path::MAIN_SEPARATOR)
} else {
Expand Down
69 changes: 64 additions & 5 deletions src/lune/globals/require/alias.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,75 @@
use console::style;
use mlua::prelude::*;

use crate::lune::util::{
luaurc::LuauRc,
paths::{make_absolute_and_clean, CWD},
};

use super::context::*;

pub(super) async fn require<'lua, 'ctx>(
_ctx: &'ctx RequireContext<'lua>,
ctx: &'ctx RequireContext<'lua>,
source: &str,
alias: &str,
name: &str,
path: &str,
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
Err(LuaError::runtime(format!(
"TODO: Support require for built-in libraries (tried to require '{name}' with alias '{alias}')"
)))
let alias = alias.to_ascii_lowercase();
let path = path.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}", style(">").dim()))
.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(ctx, abs_path, rel_path).await
}
23 changes: 6 additions & 17 deletions src/lune/globals/require/context.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::{
collections::HashMap,
env,
path::{Path, PathBuf},
sync::Arc,
};
Expand All @@ -17,6 +16,7 @@ use tokio::{
use crate::lune::{
builtins::LuneBuiltin,
scheduler::{IntoLuaThread, Scheduler},
util::paths::CWD,
};

/**
Expand All @@ -28,8 +28,6 @@ use crate::lune::{
#[derive(Debug, Clone)]
pub(super) struct RequireContext<'lua> {
lua: &'lua Lua,
use_cwd_relative_paths: bool,
working_directory: PathBuf,
cache_builtins: Arc<AsyncMutex<HashMap<LuneBuiltin, LuaResult<LuaRegistryKey>>>>,
cache_results: Arc<AsyncMutex<HashMap<PathBuf, LuaResult<LuaRegistryKey>>>>,
cache_pending: Arc<AsyncMutex<HashMap<PathBuf, Sender<()>>>>,
Expand All @@ -44,13 +42,8 @@ impl<'lua> RequireContext<'lua> {
than one context may lead to undefined require-behavior.
*/
pub fn new(lua: &'lua Lua) -> Self {
// FUTURE: We could load some kind of config or env var
// to check if we should be using cwd-relative paths
let cwd = env::current_dir().expect("Failed to get current working directory");
Self {
lua,
use_cwd_relative_paths: false,
working_directory: cwd,
cache_builtins: Arc::new(AsyncMutex::new(HashMap::new())),
cache_results: Arc::new(AsyncMutex::new(HashMap::new())),
cache_pending: Arc::new(AsyncMutex::new(HashMap::new())),
Expand All @@ -70,20 +63,16 @@ impl<'lua> RequireContext<'lua> {
source: impl AsRef<str>,
path: impl AsRef<str>,
) -> LuaResult<(PathBuf, PathBuf)> {
let path = if self.use_cwd_relative_paths {
PathBuf::from(path.as_ref())
} else {
PathBuf::from(source.as_ref())
.parent()
.ok_or_else(|| LuaError::runtime("Failed to get parent path of source"))?
.join(path.as_ref())
};
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 {
self.working_directory.join(&rel_path)
CWD.join(&rel_path)
};

Ok((rel_path, abs_path))
Expand Down
4 changes: 2 additions & 2 deletions src/lune/globals/require/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ where
{
builtin::require(&context, &builtin_name).await
} else if let Some(aliased_path) = path.strip_prefix('@') {
let (alias, name) = 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",
))?;
alias::require(&context, alias, name).await
alias::require(&context, &source, alias, path).await
} else {
path::require(&context, &source, &path).await
}
Expand Down
12 changes: 11 additions & 1 deletion src/lune/globals/require/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,17 @@ where
'lua: 'ctx,
{
let (abs_path, rel_path) = ctx.resolve_paths(source, path)?;
require_abs_rel(ctx, abs_path, rel_path).await
}

pub(super) async fn require_abs_rel<'lua, 'ctx>(
ctx: &'ctx RequireContext<'lua>,
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
if let Ok(res) = require_inner(ctx, &abs_path, &rel_path).await {
return Ok(res);
Expand Down Expand Up @@ -62,7 +72,7 @@ where

// Nothing left to try, throw an error
Err(LuaError::runtime(format!(
"No file exist at the path '{}'",
"No file exists at the path '{}'",
rel_path.display()
)))
}
Expand Down
Loading

0 comments on commit b07202a

Please sign in to comment.