Recipes can be invoked with path syntax (#1809)
This commit is contained in:
parent
743ab2fc82
commit
5c3b72a121
@ -2669,6 +2669,13 @@ $ just --unstable bar b
|
||||
B
|
||||
```
|
||||
|
||||
Or with path syntax:
|
||||
|
||||
```sh
|
||||
$ just --unstable bar::b
|
||||
B
|
||||
```
|
||||
|
||||
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.
|
||||
|
@ -2,7 +2,7 @@ use {super::*, serde::Serialize};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Invocation<'src: 'run, 'run> {
|
||||
arguments: &'run [&'run str],
|
||||
arguments: Vec<&'run str>,
|
||||
recipe: &'run Recipe<'src>,
|
||||
settings: &'run Settings<'src>,
|
||||
scope: &'run Scope<'src, 'run>,
|
||||
@ -209,7 +209,7 @@ impl<'src> Justfile<'src> {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let argvec: Vec<&str> = if !arguments.is_empty() {
|
||||
let mut remaining: Vec<&str> = if !arguments.is_empty() {
|
||||
arguments.iter().map(String::as_str).collect()
|
||||
} else if let Some(recipe) = &self.default {
|
||||
recipe.check_can_be_default_recipe()?;
|
||||
@ -220,15 +220,29 @@ impl<'src> Justfile<'src> {
|
||||
return Err(Error::NoDefaultRecipe);
|
||||
};
|
||||
|
||||
let arguments = argvec.as_slice();
|
||||
|
||||
let mut missing = Vec::new();
|
||||
let mut invocations = Vec::new();
|
||||
let mut remaining = arguments;
|
||||
let mut scopes = BTreeMap::new();
|
||||
let arena: Arena<Scope> = Arena::new();
|
||||
|
||||
while let Some((first, mut rest)) = remaining.split_first() {
|
||||
while let Some(first) = remaining.first().copied() {
|
||||
if first.contains("::") {
|
||||
if first.starts_with(':') || first.ends_with(':') || first.contains(":::") {
|
||||
missing.push(first.to_string());
|
||||
remaining = remaining[1..].to_vec();
|
||||
continue;
|
||||
}
|
||||
|
||||
remaining = first
|
||||
.split("::")
|
||||
.chain(remaining[1..].iter().copied())
|
||||
.collect();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
let rest = &remaining[1..];
|
||||
|
||||
if let Some((invocation, consumed)) = self.invocation(
|
||||
0,
|
||||
&mut Vec::new(),
|
||||
@ -241,12 +255,12 @@ impl<'src> Justfile<'src> {
|
||||
first,
|
||||
rest,
|
||||
)? {
|
||||
rest = &rest[consumed..];
|
||||
remaining = rest[consumed..].to_vec();
|
||||
invocations.push(invocation);
|
||||
} else {
|
||||
missing.push((*first).to_owned());
|
||||
missing.push(first.to_string());
|
||||
remaining = rest.to_vec();
|
||||
}
|
||||
remaining = rest;
|
||||
}
|
||||
|
||||
if !missing.is_empty() {
|
||||
@ -273,7 +287,7 @@ impl<'src> Justfile<'src> {
|
||||
Self::run_recipe(
|
||||
&context,
|
||||
invocation.recipe,
|
||||
invocation.arguments,
|
||||
&invocation.arguments,
|
||||
&dotenv,
|
||||
search,
|
||||
&mut ran,
|
||||
@ -306,7 +320,7 @@ impl<'src> Justfile<'src> {
|
||||
search: &'run Search,
|
||||
parent: &'run Scope<'src, 'run>,
|
||||
first: &'run str,
|
||||
rest: &'run [&'run str],
|
||||
rest: &[&'run str],
|
||||
) -> RunResult<'src, Option<(Invocation<'src, 'run>, usize)>> {
|
||||
if let Some(module) = self.modules.get(first) {
|
||||
path.push(first);
|
||||
@ -327,7 +341,7 @@ impl<'src> Justfile<'src> {
|
||||
Invocation {
|
||||
settings: &module.settings,
|
||||
recipe,
|
||||
arguments: &[],
|
||||
arguments: Vec::new(),
|
||||
scope,
|
||||
},
|
||||
depth,
|
||||
@ -352,7 +366,7 @@ impl<'src> Justfile<'src> {
|
||||
if recipe.parameters.is_empty() {
|
||||
Ok(Some((
|
||||
Invocation {
|
||||
arguments: &[],
|
||||
arguments: Vec::new(),
|
||||
recipe,
|
||||
scope: parent,
|
||||
settings: &self.settings,
|
||||
@ -373,7 +387,7 @@ impl<'src> Justfile<'src> {
|
||||
}
|
||||
Ok(Some((
|
||||
Invocation {
|
||||
arguments: &rest[..argument_count],
|
||||
arguments: rest[..argument_count].to_vec(),
|
||||
recipe,
|
||||
scope: parent,
|
||||
settings: &self.settings,
|
||||
|
@ -52,6 +52,74 @@ fn module_recipes_can_be_run_as_subcommands() {
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn module_recipes_can_be_run_with_path_syntax() {
|
||||
Test::new()
|
||||
.write("foo.just", "foo:\n @echo FOO")
|
||||
.justfile(
|
||||
"
|
||||
mod foo
|
||||
",
|
||||
)
|
||||
.test_round_trip(false)
|
||||
.arg("--unstable")
|
||||
.arg("foo::foo")
|
||||
.stdout("FOO\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_module_recipes_can_be_run_with_path_syntax() {
|
||||
Test::new()
|
||||
.write("foo.just", "mod bar")
|
||||
.write("bar.just", "baz:\n @echo BAZ")
|
||||
.justfile(
|
||||
"
|
||||
mod foo
|
||||
",
|
||||
)
|
||||
.test_round_trip(false)
|
||||
.arg("--unstable")
|
||||
.arg("foo::bar::baz")
|
||||
.stdout("BAZ\n")
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_path_syntax() {
|
||||
Test::new()
|
||||
.test_round_trip(false)
|
||||
.arg(":foo::foo")
|
||||
.stderr("error: Justfile does not contain recipe `:foo::foo`.\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
|
||||
Test::new()
|
||||
.test_round_trip(false)
|
||||
.arg("foo::foo:")
|
||||
.stderr("error: Justfile does not contain recipe `foo::foo:`.\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
|
||||
Test::new()
|
||||
.test_round_trip(false)
|
||||
.arg("foo:::foo")
|
||||
.stderr("error: Justfile does not contain recipe `foo:::foo`.\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_recipe_after_invalid_path() {
|
||||
Test::new()
|
||||
.test_round_trip(false)
|
||||
.arg(":foo::foo")
|
||||
.arg("bar")
|
||||
.stderr("error: Justfile does not contain recipes `:foo::foo` or `bar`.\n")
|
||||
.status(EXIT_FAILURE)
|
||||
.run();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assignments_are_evaluated_in_modules() {
|
||||
Test::new()
|
||||
|
Loading…
Reference in New Issue
Block a user