summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCasey Rodarmor <casey@rodarmor.com>2023-12-28 18:08:02 -0800
committerGitHub <noreply@github.com>2023-12-29 02:08:02 +0000
commitf039701f71d09ad70d6005ef2b2856818af0cba4 (patch)
tree72528c0f802f4b47fbce25940a5908cbe215cf09
parentb21819dcf289397fd57fd1b0ed3df509e55ecb08 (diff)
Expand tilde in import and module paths (#1792)
-rw-r--r--Cargo.lock70
-rw-r--r--Cargo.toml1
-rw-r--r--README.md17
-rw-r--r--src/compiler.rs18
-rw-r--r--src/error.rs4
-rw-r--r--tests/imports.rs17
-rw-r--r--tests/modules.rs19
7 files changed, 142 insertions, 4 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 65bb40e7..d521757b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -157,6 +157,27 @@ dependencies = [
]
[[package]]
+name = "dirs"
+version = "5.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
+dependencies = [
+ "dirs-sys",
+]
+
+[[package]]
+name = "dirs-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
+dependencies = [
+ "libc",
+ "option-ext",
+ "redox_users",
+ "windows-sys 0.48.0",
+]
+
+[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -326,6 +347,7 @@ dependencies = [
"cradle",
"ctrlc",
"derivative",
+ "dirs",
"dotenvy",
"edit-distance",
"env_logger",
@@ -373,6 +395,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
[[package]]
+name = "libredox"
+version = "0.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
+dependencies = [
+ "bitflags 2.4.1",
+ "libc",
+ "redox_syscall",
+]
+
+[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -424,6 +457,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
+name = "option-ext"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
+
+[[package]]
name = "pretty_assertions"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -506,6 +545,17 @@ dependencies = [
]
[[package]]
+name = "redox_users"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
+dependencies = [
+ "getrandom",
+ "libredox",
+ "thiserror",
+]
+
+[[package]]
name = "ref-type"
version = "0.0.0"
dependencies = [
@@ -786,6 +836,26 @@ dependencies = [
]
[[package]]
+name = "thiserror"
+version = "1.0.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.43",
+]
+
+[[package]]
name = "typed-arena"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index f0414c54..cdf211cd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -24,6 +24,7 @@ camino = "1.0.4"
clap = { version = "2.33.0", features = ["wrap_help"] }
ctrlc = { version = "3.1.1", features = ["termination"] }
derivative = "2.0.0"
+dirs = "5.0.1"
dotenvy = "0.15"
edit-distance = "2.0.0"
env_logger = "0.10.0"
diff --git a/README.md b/README.md
index f0f55154..5ba69f07 100644
--- a/README.md
+++ b/README.md
@@ -2358,7 +2358,8 @@ A
```
The `import` path can be absolute or relative to the location of the justfile
-containing it.
+containing it. A leading `~/` in the import path is replaced with the current
+users home directory.
Justfiles are insensitive to order, so included files can reference variables
and recipes defined after the `import` statement.
@@ -2406,7 +2407,19 @@ If a module is named `foo`, just will search for the module file in `foo.just`,
`foo/mod.just`, `foo/justfile`, and `foo/.justfile`. In the latter two cases,
the module file may have any capitalization.
-Environment files are loaded for the root justfile.
+Module statements may be of the form:
+
+```mf
+mod foo 'PATH'
+```
+
+Which loads the module's source file from `PATH`, instead of from the usual
+locations. A leading `~/` in `PATH` is replaced with the current user's home
+directory.
+
+Environment files are only loaded for the root justfile, and loaded environment
+variables are available in submodules. Settings in submodules that affect
+enviroment file loading are ignored.
Recipes in submodules without the `[no-cd]` attribute run with the working
directory set to the directory containing the submodule source file.
diff --git a/src/compiler.rs b/src/compiler.rs
index 6680a845..bc836ed6 100644
--- a/src/compiler.rs
+++ b/src/compiler.rs
@@ -41,7 +41,7 @@ impl Compiler {
let parent = current.parent().unwrap();
let import = if let Some(path) = path {
- parent.join(&path.cooked)
+ parent.join(Self::expand_tilde(&path.cooked)?)
} else {
Self::find_module_file(parent, *name)?
};
@@ -53,7 +53,11 @@ impl Compiler {
stack.push((import, depth + 1));
}
Item::Import { relative, absolute } => {
- let import = current.parent().unwrap().join(&relative.cooked).lexiclean();
+ let import = current
+ .parent()
+ .unwrap()
+ .join(Self::expand_tilde(&relative.cooked)?)
+ .lexiclean();
if srcs.contains_key(&import) {
return Err(Error::CircularImport { current, import });
}
@@ -117,6 +121,16 @@ impl Compiler {
}
}
+ fn expand_tilde(path: &str) -> RunResult<'static, PathBuf> {
+ Ok(if let Some(path) = path.strip_prefix("~/") {
+ dirs::home_dir()
+ .ok_or(Error::Homedir)?
+ .join(path.trim_start_matches('/'))
+ } else {
+ PathBuf::from(path)
+ })
+ }
+
#[cfg(test)]
pub(crate) fn test_compile(src: &str) -> CompileResult<Justfile> {
let tokens = Lexer::test_lex(src)?;
diff --git a/src/error.rs b/src/error.rs
index a445e2e0..3a0da1b4 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -95,6 +95,7 @@ pub(crate) enum Error<'src> {
GetConfirmation {
io_error: io::Error,
},
+ Homedir,
InitExists {
justfile: PathBuf,
},
@@ -347,6 +348,9 @@ impl<'src> ColorDisplay for Error<'src> {
GetConfirmation { io_error } => {
write!(f, "Failed to read confirmation from stdin: {io_error}")?;
}
+ Homedir => {
+ write!(f, "Failed to get homedir")?;
+ }
InitExists { justfile } => {
write!(f, "Justfile `{}` already exists", justfile.display())?;
}
diff --git a/tests/imports.rs b/tests/imports.rs
index b1cb395c..22a04b71 100644
--- a/tests/imports.rs
+++ b/tests/imports.rs
@@ -152,3 +152,20 @@ fn recipes_in_import_are_overridden_by_recipes_in_parent() {
.stdout("ROOT\n")
.run();
}
+
+#[cfg(not(windows))]
+#[test]
+fn import_paths_beginning_with_tilde_are_expanded_to_homdir() {
+ Test::new()
+ .write("foobar/mod.just", "foo:\n @echo FOOBAR")
+ .justfile(
+ "
+ import '~/mod.just'
+ ",
+ )
+ .test_round_trip(false)
+ .arg("foo")
+ .stdout("FOOBAR\n")
+ .env("HOME", "foobar")
+ .run();
+}
diff --git a/tests/modules.rs b/tests/modules.rs
index ea4796a4..8c2e7723 100644
--- a/tests/modules.rs
+++ b/tests/modules.rs
@@ -529,3 +529,22 @@ fn submodule_shebang_recipes_run_in_submodule_directory() {
.stdout("BAR")
.run();
}
+
+#[cfg(not(windows))]
+#[test]
+fn module_paths_beginning_with_tilde_are_expanded_to_homdir() {
+ Test::new()
+ .write("foobar/mod.just", "foo:\n @echo FOOBAR")
+ .justfile(
+ "
+ mod foo '~/mod.just'
+ ",
+ )
+ .test_round_trip(false)
+ .arg("--unstable")
+ .arg("foo")
+ .arg("foo")
+ .stdout("FOOBAR\n")
+ .env("HOME", "foobar")
+ .run();
+}