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
|
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`,
|
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,
|
`foo/mod.just`, `foo/justfile`, and `foo/.justfile`. In the latter two cases,
|
||||||
the module file may have any capitalization.
|
the module file may have any capitalization.
|
||||||
|
@ -2,7 +2,7 @@ use {super::*, serde::Serialize};
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Invocation<'src: 'run, 'run> {
|
struct Invocation<'src: 'run, 'run> {
|
||||||
arguments: &'run [&'run str],
|
arguments: Vec<&'run str>,
|
||||||
recipe: &'run Recipe<'src>,
|
recipe: &'run Recipe<'src>,
|
||||||
settings: &'run Settings<'src>,
|
settings: &'run Settings<'src>,
|
||||||
scope: &'run Scope<'src, 'run>,
|
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()
|
arguments.iter().map(String::as_str).collect()
|
||||||
} else if let Some(recipe) = &self.default {
|
} else if let Some(recipe) = &self.default {
|
||||||
recipe.check_can_be_default_recipe()?;
|
recipe.check_can_be_default_recipe()?;
|
||||||
@ -220,15 +220,29 @@ impl<'src> Justfile<'src> {
|
|||||||
return Err(Error::NoDefaultRecipe);
|
return Err(Error::NoDefaultRecipe);
|
||||||
};
|
};
|
||||||
|
|
||||||
let arguments = argvec.as_slice();
|
|
||||||
|
|
||||||
let mut missing = Vec::new();
|
let mut missing = Vec::new();
|
||||||
let mut invocations = Vec::new();
|
let mut invocations = Vec::new();
|
||||||
let mut remaining = arguments;
|
|
||||||
let mut scopes = BTreeMap::new();
|
let mut scopes = BTreeMap::new();
|
||||||
let arena: Arena<Scope> = Arena::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(
|
if let Some((invocation, consumed)) = self.invocation(
|
||||||
0,
|
0,
|
||||||
&mut Vec::new(),
|
&mut Vec::new(),
|
||||||
@ -241,12 +255,12 @@ impl<'src> Justfile<'src> {
|
|||||||
first,
|
first,
|
||||||
rest,
|
rest,
|
||||||
)? {
|
)? {
|
||||||
rest = &rest[consumed..];
|
remaining = rest[consumed..].to_vec();
|
||||||
invocations.push(invocation);
|
invocations.push(invocation);
|
||||||
} else {
|
} else {
|
||||||
missing.push((*first).to_owned());
|
missing.push(first.to_string());
|
||||||
|
remaining = rest.to_vec();
|
||||||
}
|
}
|
||||||
remaining = rest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !missing.is_empty() {
|
if !missing.is_empty() {
|
||||||
@ -273,7 +287,7 @@ impl<'src> Justfile<'src> {
|
|||||||
Self::run_recipe(
|
Self::run_recipe(
|
||||||
&context,
|
&context,
|
||||||
invocation.recipe,
|
invocation.recipe,
|
||||||
invocation.arguments,
|
&invocation.arguments,
|
||||||
&dotenv,
|
&dotenv,
|
||||||
search,
|
search,
|
||||||
&mut ran,
|
&mut ran,
|
||||||
@ -306,7 +320,7 @@ impl<'src> Justfile<'src> {
|
|||||||
search: &'run Search,
|
search: &'run Search,
|
||||||
parent: &'run Scope<'src, 'run>,
|
parent: &'run Scope<'src, 'run>,
|
||||||
first: &'run str,
|
first: &'run str,
|
||||||
rest: &'run [&'run str],
|
rest: &[&'run str],
|
||||||
) -> RunResult<'src, Option<(Invocation<'src, 'run>, usize)>> {
|
) -> RunResult<'src, Option<(Invocation<'src, 'run>, usize)>> {
|
||||||
if let Some(module) = self.modules.get(first) {
|
if let Some(module) = self.modules.get(first) {
|
||||||
path.push(first);
|
path.push(first);
|
||||||
@ -327,7 +341,7 @@ impl<'src> Justfile<'src> {
|
|||||||
Invocation {
|
Invocation {
|
||||||
settings: &module.settings,
|
settings: &module.settings,
|
||||||
recipe,
|
recipe,
|
||||||
arguments: &[],
|
arguments: Vec::new(),
|
||||||
scope,
|
scope,
|
||||||
},
|
},
|
||||||
depth,
|
depth,
|
||||||
@ -352,7 +366,7 @@ impl<'src> Justfile<'src> {
|
|||||||
if recipe.parameters.is_empty() {
|
if recipe.parameters.is_empty() {
|
||||||
Ok(Some((
|
Ok(Some((
|
||||||
Invocation {
|
Invocation {
|
||||||
arguments: &[],
|
arguments: Vec::new(),
|
||||||
recipe,
|
recipe,
|
||||||
scope: parent,
|
scope: parent,
|
||||||
settings: &self.settings,
|
settings: &self.settings,
|
||||||
@ -373,7 +387,7 @@ impl<'src> Justfile<'src> {
|
|||||||
}
|
}
|
||||||
Ok(Some((
|
Ok(Some((
|
||||||
Invocation {
|
Invocation {
|
||||||
arguments: &rest[..argument_count],
|
arguments: rest[..argument_count].to_vec(),
|
||||||
recipe,
|
recipe,
|
||||||
scope: parent,
|
scope: parent,
|
||||||
settings: &self.settings,
|
settings: &self.settings,
|
||||||
|
@ -52,6 +52,74 @@ fn module_recipes_can_be_run_as_subcommands() {
|
|||||||
.run();
|
.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]
|
#[test]
|
||||||
fn assignments_are_evaluated_in_modules() {
|
fn assignments_are_evaluated_in_modules() {
|
||||||
Test::new()
|
Test::new()
|
||||||
|
Loading…
Reference in New Issue
Block a user