Skip to content

Commit

Permalink
Add [working-directory] recipe attribute (#2438)
Browse files Browse the repository at this point in the history
  • Loading branch information
bcheidemann authored Nov 27, 2024
1 parent fdf3474 commit fdc9245
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 9 deletions.
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -884,8 +884,8 @@ $ just bar
/subdir
```

You can override working directory with `set working-directory := '…'`, whose value
is relative to the default working directory.
You can override the working directory for all recipes with
`set working-directory := '…'`:

```just
set working-directory := 'bar'
Expand All @@ -901,6 +901,26 @@ $ just foo
/home/bob/bar
```

You can override the working directory for a specific recipe with the
`working-directory` attribute<sup>master</sup>:

```just
[working-directory: 'bar']
@foo:
pwd
```

```console
$ pwd
/home/bob
$ just foo
/home/bob/bar
```

The argument to the `working-directory` setting or `working-directory`
attribute may be absolute or relative. If it is relative it is interpreted
relative to the default working directory.

### Aliases

Aliases allow recipes to be invoked on the command line with alternative names:
Expand Down Expand Up @@ -1972,6 +1992,7 @@ change their behavior.
| `[script(COMMAND)]`<sup>1.32.0</sup> | recipe | Execute recipe as a script interpreted by `COMMAND`. See [script recipes](#script-recipes) for more details. |
| `[unix]`<sup>1.8.0</sup> | recipe | Enable recipe on Unixes. (Includes MacOS). |
| `[windows]`<sup>1.8.0</sup> | recipe | Enable recipe on Windows. |
| `[working-directory(PATH)]`<sup>master</sup> | recipe | Set recipe working directory. `PATH` may be relative or absolute. If relative, it is interpreted relative to the default working directory. |

A recipe can have multiple attributes, either on multiple lines:

Expand Down
9 changes: 7 additions & 2 deletions src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ pub(crate) enum Attribute<'src> {
Script(Option<Interpreter<'src>>),
Unix,
Windows,
WorkingDirectory(StringLiteral<'src>),
}

impl AttributeDiscriminant {
fn argument_range(self) -> RangeInclusive<usize> {
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
Expand Down Expand Up @@ -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())
}
})
}

Expand All @@ -117,7 +121,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)
Expand Down
4 changes: 4 additions & 0 deletions src/compile_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ impl Display for CompileError<'_> {
consist of tabs or spaces, but not both",
ShowWhitespace(whitespace)
),
NoCdAndWorkingDirectoryAttribute { recipe } => write!(
f,
"Recipe `{recipe}` has both `[no-cd]` and `[working-directory]` attributes"
),
ParameterFollowsVariadicParameter { parameter } => {
write!(f, "Parameter `{parameter}` follows variadic parameter")
}
Expand Down
3 changes: 3 additions & 0 deletions src/compile_error_kind.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pub(crate) enum CompileErrorKind<'src> {
MixedLeadingWhitespace {
whitespace: &'src str,
},
NoCdAndWorkingDirectoryAttribute {
recipe: &'src str,
},
ParameterFollowsVariadicParameter {
parameter: &'src str,
},
Expand Down
16 changes: 16 additions & 0 deletions src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,22 @@ impl<'run, 'src> Parser<'run, 'src> {
}));
}

let working_directory = attributes
.iter()
.any(|attribute| matches!(attribute, Attribute::WorkingDirectory(_)));

if working_directory {
for attribute in &attributes {
if let Attribute::NoCd = attribute {
return Err(
name.error(CompileErrorKind::NoCdAndWorkingDirectoryAttribute {
recipe: name.lexeme(),
}),
);
}
}
}

let private = name.lexeme().starts_with('_') || attributes.contains(&Attribute::Private);

let mut doc = doc.map(ToOwned::to_owned);
Expand Down
16 changes: 12 additions & 4 deletions src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,19 @@ impl<'src, D> Recipe<'src, D> {
}

fn working_directory<'a>(&'a self, context: &'a ExecutionContext) -> Option<PathBuf> {
if self.change_directory() {
Some(context.working_directory())
} else {
None
if !self.change_directory() {
return None;
}

let working_directory = context.working_directory();

for attribute in &self.attributes {
if let Attribute::WorkingDirectory(dir) = attribute {
return Some(working_directory.join(&dir.cooked));
}
}

Some(working_directory)
}

fn no_quiet(&self) -> bool {
Expand Down
22 changes: 21 additions & 1 deletion tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ pub(crate) struct Test {
pub(crate) args: Vec<String>,
pub(crate) current_dir: PathBuf,
pub(crate) env: BTreeMap<String, String>,
pub(crate) expected_files: BTreeMap<PathBuf, Vec<u8>>,
pub(crate) justfile: Option<String>,
pub(crate) shell: bool,
pub(crate) status: i32,
Expand All @@ -71,6 +72,7 @@ impl Test {
args: Vec::new(),
current_dir: PathBuf::new(),
env: BTreeMap::new(),
expected_files: BTreeMap::new(),
justfile: Some(String::new()),
shell: true,
status: EXIT_SUCCESS,
Expand Down Expand Up @@ -98,7 +100,7 @@ impl Test {
}

pub(crate) fn create_dir(self, path: impl AsRef<Path>) -> Self {
fs::create_dir_all(self.tempdir.path().join(path.as_ref())).unwrap();
fs::create_dir_all(self.tempdir.path().join(path)).unwrap();
self
}

Expand Down Expand Up @@ -195,6 +197,14 @@ impl Test {
fs::write(path, content).unwrap();
self
}

pub(crate) fn expect_file(mut self, path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> Self {
let path = path.as_ref();
self
.expected_files
.insert(path.into(), content.as_ref().into());
self
}
}

impl Test {
Expand Down Expand Up @@ -283,6 +293,16 @@ impl Test {
panic!("Output mismatch.");
}

for (path, expected) in &self.expected_files {
let actual = fs::read(self.tempdir.path().join(path)).unwrap();
assert_eq!(
actual,
expected.as_slice(),
"mismatch for expected file at path {}",
path.display(),
);
}

if self.test_round_trip && self.status == EXIT_SUCCESS {
self.round_trip();
}
Expand Down
77 changes: 77 additions & 0 deletions tests/working_directory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,3 +331,80 @@ file := shell('cat file.txt')
.stdout("FILE\n")
.run();
}

#[test]
fn attribute_duplicate() {
Test::new()
.justfile(
"
[working-directory('bar')]
[working-directory('baz')]
foo:
",
)
.stderr(
"error: Recipe attribute `working-directory` first used on line 1 is duplicated on line 2
β€”β€”β–Ά justfile:2:2
β”‚
2 β”‚ [working-directory('baz')]
β”‚ ^^^^^^^^^^^^^^^^^
",
)
.status(EXIT_FAILURE)
.run();
}

#[test]
fn attribute() {
Test::new()
.justfile(
"
[working-directory('foo')]
@qux:
echo baz > bar
",
)
.create_dir("foo")
.expect_file("foo/bar", "baz\n")
.run();
}

#[test]
fn attribute_with_nocd_is_forbidden() {
Test::new()
.justfile(
"
[working-directory('foo')]
[no-cd]
bar:
",
)
.stderr(
"
error: Recipe `bar` has both `[no-cd]` and `[working-directory]` attributes
β€”β€”β–Ά justfile:3:1
β”‚
3 β”‚ bar:
β”‚ ^^^
",
)
.status(EXIT_FAILURE)
.run();
}

#[test]
fn setting_and_attribute() {
Test::new()
.justfile(
"
set working-directory := 'foo'
[working-directory('bar')]
@baz:
echo bob > fred
",
)
.create_dir("foo/bar")
.expect_file("foo/bar/fred", "bob\n")
.run();
}

0 comments on commit fdc9245

Please sign in to comment.