Skip to content

Commit f31a3e0

Browse files
Merge pull request #398 from gnpaone/sync-action
Sync source repo
2 parents b75ca7e + bbd3168 commit f31a3e0

19 files changed

Lines changed: 285 additions & 89 deletions

README.md

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1617,24 +1617,61 @@ Use `{{{{` to include a literal `{{` in a format string:
16171617
foo := f'I {{{{LOVE} curly braces!'
16181618
```
16191619

1620-
### Ignoring Errors
1620+
### Sigils
16211621

1622-
Normally, if a command returns a non-zero exit status, execution will stop. To
1623-
continue execution after a command, even if it fails, prefix the command with
1624-
`-`:
1622+
Commands in linewise recipes may be prefixed with any combination of the sigils
1623+
`-`, `@`, and `?`.
1624+
1625+
The `@` sigil toggles command echoing:
16251626

16261627
```just
16271628
foo:
1628-
-cat foo
1629-
echo 'Done!'
1629+
@echo "This line won't be echoed!"
1630+
echo "This line will be echoed!"
1631+
1632+
@bar:
1633+
@echo "This line will be echoed!"
1634+
echo "This line won't be echoed!"
1635+
```
1636+
1637+
The `-` sigil cause recipe execution to continue even if the command returns a
1638+
nonzero exit status:
1639+
1640+
```just
1641+
# execution will continue, even if bar doesn't exist
1642+
foo:
1643+
-rmdir bar
1644+
mkdir bar
1645+
echo 'so much good stuff' > bar/stuff.txt
1646+
```
1647+
1648+
The `?` sigil<sup>master</sup> causes the current recipe to stop executing if
1649+
the command exits with status code `1`, however execution of other recipes will
1650+
continue. Exit status `0` causes the current recipe to continue execution as
1651+
normal. All other exit codes are reserved and should not be used, as they may
1652+
be given meaning in a future version of `just`.
1653+
1654+
If the `guards` setting is unset or false, `?` sigils are ignored and instead
1655+
treated as part of the command.
1656+
1657+
```just
1658+
set guards
1659+
1660+
@foo: bar
1661+
echo FOO
1662+
1663+
@bar:
1664+
?[[ -f baz ]]
1665+
echo BAR
16301666
```
16311667

16321668
```console
16331669
$ just foo
1634-
cat foo
1635-
cat: foo: No such file or directory
1636-
echo 'Done!'
1637-
Done!
1670+
FOO
1671+
$ touch baz
1672+
$ just foo
1673+
BAR
1674+
FOO
16381675
```
16391676

16401677
### Functions

src/analyzer.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,23 @@ impl<'run, 'src> Analyzer<'run, 'src> {
177177
{
178178
deduplicated_recipes.insert(recipe.clone());
179179
}
180+
181+
if !recipe.is_script() {
182+
for line in &recipe.body {
183+
let sigils = line.sigils(&settings);
184+
185+
if sigils.contains(&Sigil::Guard) && sigils.contains(&Sigil::Infallible) {
186+
let Fragment::Text { token } = line.fragments.first().unwrap() else {
187+
unreachable!();
188+
};
189+
return Err(
190+
token
191+
.error(CompileErrorKind::GuardAndInfallibleSigil)
192+
.into(),
193+
);
194+
}
195+
}
196+
}
180197
}
181198

182199
let recipes = RecipeResolver::resolve_recipes(

src/compile_error.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ impl Display for CompileError<'_> {
195195
Count("argument", *found),
196196
expected.display(),
197197
),
198+
GuardAndInfallibleSigil => write!(
199+
f,
200+
"The guard `?` and infallible `-` sigils may not be used together"
201+
),
198202
Include => write!(
199203
f,
200204
"The `!include` directive has been stabilized as `import`"

src/compile_error_kind.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub(crate) enum CompileErrorKind<'src> {
8383
found: usize,
8484
expected: RangeInclusive<usize>,
8585
},
86+
GuardAndInfallibleSigil,
8687
Include,
8788
InconsistentLeadingWhitespace {
8889
expected: &'src str,

src/error.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ pub(crate) enum Error<'src> {
120120
GetConfirmation {
121121
io_error: io::Error,
122122
},
123+
GuardCode {
124+
recipe: &'src str,
125+
line_number: usize,
126+
code: i32,
127+
},
123128
Homedir,
124129
InitExists {
125130
justfile: PathBuf,
@@ -353,10 +358,6 @@ impl ColorDisplay for Error<'_> {
353358
write!(f, "{error}: {message}")?;
354359

355360
match self {
356-
Const { const_error } => write!(
357-
f,
358-
"{const_error}",
359-
)?,
360361
AmbiguousModuleFile { module, found } => write!(
361362
f,
362363
"Found multiple source files for module `{module}`: {}",
@@ -469,6 +470,10 @@ impl ColorDisplay for Error<'_> {
469470
}
470471
Compile { compile_error } => Display::fmt(compile_error, f)?,
471472
Config { config_error } => Display::fmt(config_error, f)?,
473+
Const { const_error } => write!(
474+
f,
475+
"{const_error}",
476+
)?,
472477
Cygpath {
473478
recipe,
474479
output_error,
@@ -574,6 +579,16 @@ impl ColorDisplay for Error<'_> {
574579
GetConfirmation { io_error } => {
575580
write!(f, "Failed to read confirmation from stdin: {io_error}")?;
576581
}
582+
GuardCode {
583+
recipe,
584+
line_number,
585+
code,
586+
} => {
587+
write!(
588+
f,
589+
"Guard line in recipe `{recipe}` on line {line_number} returned reserved exit code {code}",
590+
)?;
591+
}
577592
Homedir => {
578593
write!(f, "Failed to get homedir")?;
579594
}

src/evaluator.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ impl<'src, 'run> Evaluator<'src, 'run> {
103103
Setting::Fallback(value) => {
104104
settings.fallback = value;
105105
}
106+
Setting::Guards(guards) => {
107+
settings.guards = guards;
108+
}
106109
Setting::IgnoreComments(value) => {
107110
settings.ignore_comments = value;
108111
}

src/justfile.rs

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,33 @@ impl<'src> Justfile<'src> {
128128
let scope = scopes.get(&self.module_path).unwrap().1;
129129

130130
match &config.subcommand {
131+
Subcommand::Choose { .. } | Subcommand::Run { .. } => {
132+
let arguments = arguments.iter().map(String::as_str).collect::<Vec<&str>>();
133+
134+
let invocations = InvocationParser::parse_invocations(self, &arguments)?;
135+
136+
if config.one && invocations.len() > 1 {
137+
return Err(Error::ExcessInvocations {
138+
invocations: invocations.len(),
139+
});
140+
}
141+
142+
let ran = Ran::default();
143+
for invocation in invocations {
144+
Self::run_recipe(
145+
&invocation.arguments,
146+
config,
147+
&dotenv,
148+
false,
149+
&ran,
150+
invocation.recipe,
151+
&scopes,
152+
search,
153+
)?;
154+
}
155+
156+
Ok(())
157+
}
131158
Subcommand::Command {
132159
binary, arguments, ..
133160
} => {
@@ -167,7 +194,7 @@ impl<'src> Justfile<'src> {
167194
return Err(Error::Interrupted { signal });
168195
}
169196

170-
return Ok(());
197+
Ok(())
171198
}
172199
Subcommand::Evaluate { variable, .. } => {
173200
if let Some(variable) = variable {
@@ -194,36 +221,10 @@ impl<'src> Justfile<'src> {
194221
}
195222
}
196223

197-
return Ok(());
224+
Ok(())
198225
}
199-
_ => {}
226+
_ => unreachable!(),
200227
}
201-
202-
let arguments = arguments.iter().map(String::as_str).collect::<Vec<&str>>();
203-
204-
let invocations = InvocationParser::parse_invocations(self, &arguments)?;
205-
206-
if config.one && invocations.len() > 1 {
207-
return Err(Error::ExcessInvocations {
208-
invocations: invocations.len(),
209-
});
210-
}
211-
212-
let ran = Ran::default();
213-
for invocation in invocations {
214-
Self::run_recipe(
215-
&invocation.arguments,
216-
config,
217-
&dotenv,
218-
false,
219-
&ran,
220-
invocation.recipe,
221-
&scopes,
222-
search,
223-
)?;
224-
}
225-
226-
Ok(())
227228
}
228229

229230
pub(crate) fn check_unstable(&self, config: &Config) -> RunResult<'src> {

src/keyword.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ pub(crate) enum Keyword {
1717
F,
1818
Fallback,
1919
False,
20+
Guards,
2021
If,
2122
IgnoreComments,
2223
Import,

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ pub(crate) use {
8282
settings::Settings,
8383
shebang::Shebang,
8484
show_whitespace::ShowWhitespace,
85+
sigil::Sigil,
8586
signal::Signal,
8687
signal_handler::SignalHandler,
8788
source::Source,
@@ -268,6 +269,7 @@ mod setting;
268269
mod settings;
269270
mod shebang;
270271
mod show_whitespace;
272+
mod sigil;
271273
mod signal;
272274
mod signal_handler;
273275
#[cfg(unix)]

src/line.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,27 @@ impl Line<'_> {
1818
}
1919
}
2020

21+
pub(crate) fn sigils(&self, settings: &Settings) -> BTreeSet<Sigil> {
22+
let mut sigils = BTreeSet::new();
23+
24+
if let Some(first) = self.first() {
25+
for c in first.chars() {
26+
let sigil = match c {
27+
'-' => Sigil::Infallible,
28+
'?' if settings.guards => Sigil::Guard,
29+
'@' => Sigil::Quiet,
30+
_ => break,
31+
};
32+
33+
if !sigils.insert(sigil) {
34+
break;
35+
}
36+
}
37+
}
38+
39+
sigils
40+
}
41+
2142
pub(crate) fn is_comment(&self) -> bool {
2243
self.first().is_some_and(|text| text.starts_with('#'))
2344
}
@@ -33,18 +54,6 @@ impl Line<'_> {
3354
self.fragments.is_empty()
3455
}
3556

36-
pub(crate) fn is_infallible(&self) -> bool {
37-
self
38-
.first()
39-
.is_some_and(|text| text.starts_with('-') || text.starts_with("@-"))
40-
}
41-
42-
pub(crate) fn is_quiet(&self) -> bool {
43-
self
44-
.first()
45-
.is_some_and(|text| text.starts_with('@') || text.starts_with("-@"))
46-
}
47-
4857
pub(crate) fn is_shebang(&self) -> bool {
4958
self.first().is_some_and(|text| text.starts_with("#!"))
5059
}

0 commit comments

Comments
 (0)