diff --git a/README.md b/README.md index 994ec488..6427ed9a 100644 --- a/README.md +++ b/README.md @@ -1875,6 +1875,7 @@ Recipes, `mod` statements, and aliases may be annotated with attributes that cha | `[script(COMMAND)]`1.32.0 | recipe | Execute recipe as a script interpreted by `COMMAND`. See [script recipes](#script-recipes) for more details. | | `[unix]`1.8.0 | recipe | Enable recipe on Unixes. (Includes MacOS). | | `[windows]`1.8.0 | recipe | Enable recipe on Windows. | +| `[working-directory('bar')]`1.37.0 | recipe | Set the working directory for the recipe, relative to the default working directory. | A recipe can have multiple attributes, either on multiple lines: @@ -1938,6 +1939,23 @@ Can be used with paths that are relative to the current directory, because `[no-cd]` prevents `just` from changing the current directory when executing `commit`. +#### Changing Working Directory1.37.0 + +`just` normally executes recipes with the current directory set to the directory +that contains the `justfile`. The execution directory can be changed with the +`[working-directory('dir')]` attribute. This can be used to create recipes which +are executed in a directory relative to the default directory. + +For example, this `example` recipe: + +```just +[working-directory('dir')] +example: + echo "$(pwd)" +``` + +Which will run in the `dir` directory. + #### Requiring Confirmation for Recipes1.17.0 `just` normally executes all recipes unless there is an error. The `[confirm]` diff --git a/src/attribute.rs b/src/attribute.rs index ebdc2127..e90aa0b9 100644 --- a/src/attribute.rs +++ b/src/attribute.rs @@ -23,13 +23,14 @@ pub(crate) enum Attribute<'src> { Script(Option>), Unix, Windows, + WorkingDirectory(StringLiteral<'src>), } impl AttributeDiscriminant { fn argument_range(self) -> RangeInclusive { match self { Self::Confirm | Self::Doc => 0..=1, - Self::Group | Self::Extension => 1..=1, + Self::Group | Self::Extension | Self::WorkingDirectory => 1..=1, Self::Linux | Self::Macos | Self::NoCd @@ -93,6 +94,9 @@ impl<'src> Attribute<'src> { }), AttributeDiscriminant::Unix => Self::Unix, AttributeDiscriminant::Windows => Self::Windows, + AttributeDiscriminant::WorkingDirectory => { + Self::WorkingDirectory(arguments.into_iter().next().unwrap()) + } }) } @@ -109,7 +113,8 @@ impl<'src> Display for Attribute<'src> { Self::Confirm(Some(argument)) | Self::Doc(Some(argument)) | Self::Extension(argument) - | Self::Group(argument) => write!(f, "({argument})")?, + | Self::Group(argument) + | Self::WorkingDirectory(argument) => write!(f, "({argument})")?, Self::Script(Some(shell)) => write!(f, "({shell})")?, Self::Confirm(None) | Self::Doc(None) diff --git a/src/recipe.rs b/src/recipe.rs index 05516225..fdf6b27a 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -131,11 +131,24 @@ impl<'src, D> Recipe<'src, D> { } fn working_directory<'a>(&'a self, context: &'a ExecutionContext) -> Option { - if self.change_directory() { - Some(context.working_directory()) - } else { - None + if !self.change_directory() { + return None; } + + let working_dir = self + .attributes + .iter() + .filter_map(|attribute| match attribute { + Attribute::WorkingDirectory(dir) => Some(dir), + _ => None, + }) + .last(); + + Some( + working_dir + .map(|dir| context.working_directory().join(dir.raw)) + .unwrap_or(context.working_directory()), + ) } fn no_quiet(&self) -> bool { diff --git a/tests/working_directory.rs b/tests/working_directory.rs index 3396b73e..461a5e24 100644 --- a/tests/working_directory.rs +++ b/tests/working_directory.rs @@ -331,3 +331,70 @@ file := shell('cat file.txt') .stdout("FILE\n") .run(); } + +#[test] +fn attribute() { + Test::new() + .justfile( + r#" + [working-directory('bar')] + print1: + echo "$(basename "$PWD")" + + [working-directory('bar')] + [working-directory('baz')] + print2: + echo "$(basename "$PWD")" + + [working-directory('bar')] + [no-cd] + print3: + echo "$(basename "$PWD")" + "#, + ) + .current_dir("foo") + .tree(tree! { + foo: {}, + bar: {}, + baz: {}, + }) + .args(["print1", "print2", "print3"]) + .stderr( + r#"echo "$(basename "$PWD")" +echo "$(basename "$PWD")" +echo "$(basename "$PWD")" +"#, + ) + .stdout("bar\nbaz\nfoo\n") + .run(); +} + +#[test] +fn setting_and_attribute() { + Test::new() + .justfile( + r#" + set working-directory := 'bar' + + [working-directory('baz')] + print1: + echo "$(basename "$PWD")" + echo "$(basename "$(dirname "$PWD")")" + "#, + ) + .current_dir("foo") + .tree(tree! { + foo: {}, + bar: { + baz: {}, + }, + }) + .args(["print1"]) + .stderr( + r#"echo "$(basename "$PWD")" +echo "$(basename "$(dirname "$PWD")")" +"#, + ) + .stdout("baz\nbar\n") + .run(); +}