Add skip-comments setting (#1333)

Add a new setting "skip-comments", which defaults to true. If unset,
this causes lines internal to a non-shebang recipe beginning with the
character '#' (including '#!' internal to a non-shebang recipe; that is,
any such instances occurring after the first line of a recipe) to be
treated as comments of the justfile itself. They will not be echoed to
stderr when the recipe executes.
This commit is contained in:
Greg Shuflin 2022-10-04 17:32:30 -07:00 committed by GitHub
parent 7680b19979
commit e445cfb47d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 204 additions and 81 deletions

View File

@ -59,6 +59,7 @@ assignment : NAME ':=' expression eol
export : 'export' assignment
setting : 'set' 'dotenv-load' boolean?
| 'set' 'ignore-comments' boolean?
| 'set' 'export' boolean?
| 'set' 'positional-arguments' boolean?
| 'set' 'allow-duplicate-recipes' boolean?

View File

@ -641,15 +641,16 @@ foo:
#### Table of Settings
| Name | Value | Description |
| ------------------------- | ------------------ | --------------------------------------------------------------------------------------------- |
| `allow-duplicate-recipes` | boolean | Allow recipes appearing later in a `justfile` to override earlier recipes with the same name. |
| `dotenv-load` | boolean | Load a `.env` file, if present. |
| `export` | boolean | Export all variables as environment variables. |
| `positional-arguments` | boolean | Pass positional arguments. |
| `shell` | `[COMMAND, ARGS…]` | Set the command used to invoke recipes and evaluate backticks. |
| `windows-shell` | `[COMMAND, ARGS…]` | Set the command used to invoke recipes and evaluate backticks. |
| `windows-powershell` | boolean | Use PowerShell on Windows as default shell. (Deprecated. Use `windows-shell` instead. |
| Name | Value | Default | Description |
| ------------------------- | ------------------ | --------|---------------------------------------------------------------------------------------------- |
| `allow-duplicate-recipes` | boolean | False | Allow recipes appearing later in a `justfile` to override earlier recipes with the same name. |
| `dotenv-load` | boolean | False | Load a `.env` file, if present. |
| `export` | boolean | False | Export all variables as environment variables. |
| `ignore-comments` | boolean | False | Ignore recipe lines beginning with `#`. |
| `positional-arguments` | boolean | False | Pass positional arguments. |
| `shell` | `[COMMAND, ARGS…]` | - | Set the command used to invoke recipes and evaluate backticks. |
| `windows-powershell` | boolean | False | Use PowerShell on Windows as default shell. (Deprecated. Use `windows-shell` instead. |
| `windows-shell` | `[COMMAND, ARGS…]` | - | Set the command used to invoke recipes and evaluate backticks. |
Boolean settings can be written as:

View File

@ -40,7 +40,7 @@ impl<'src> Analyzer<'src> {
}
}
let mut settings = Settings::new();
let mut settings = Settings::default();
for (_, set) in self.sets {
match set.value {
@ -53,6 +53,9 @@ impl<'src> Analyzer<'src> {
Setting::Export(export) => {
settings.export = export;
}
Setting::IgnoreComments(ignore_comments) => {
settings.ignore_comments = ignore_comments;
}
Setting::PositionalArguments(positional_arguments) => {
settings.positional_arguments = positional_arguments;
}

View File

@ -257,7 +257,15 @@ impl<'src> Justfile<'src> {
let mut ran = BTreeSet::new();
for (recipe, arguments) in grouped {
Self::run_recipe(&context, recipe, arguments, &dotenv, search, &mut ran)?;
Self::run_recipe(
&context,
recipe,
arguments,
&dotenv,
search,
&self.settings,
&mut ran,
)?;
}
Ok(())
@ -281,6 +289,7 @@ impl<'src> Justfile<'src> {
arguments: &[&str],
dotenv: &BTreeMap<String, String>,
search: &Search,
settings: &Settings,
ran: &mut BTreeSet<Vec<String>>,
) -> RunResult<'src, ()> {
let mut invocation = vec![recipe.name().to_owned()];
@ -319,6 +328,7 @@ impl<'src> Justfile<'src> {
&arguments.iter().map(String::as_ref).collect::<Vec<&str>>(),
dotenv,
search,
settings,
ran,
)?;
}
@ -341,6 +351,7 @@ impl<'src> Justfile<'src> {
&evaluated.iter().map(String::as_ref).collect::<Vec<&str>>(),
dotenv,
search,
settings,
&mut ran,
)?;
}

View File

@ -10,6 +10,7 @@ pub(crate) enum Keyword {
Export,
False,
If,
IgnoreComments,
PositionalArguments,
Set,
Shell,

View File

@ -1,5 +1,6 @@
#![deny(clippy::all, clippy::pedantic)]
#![allow(
clippy::default_trait_access,
clippy::doc_markdown,
clippy::enum_glob_use,
clippy::missing_errors_doc,

View File

@ -12,6 +12,13 @@ impl<'src> Line<'src> {
self.fragments.is_empty()
}
pub(crate) fn is_comment(&self) -> bool {
match self.fragments.first() {
Some(Fragment::Text { token }) => token.lexeme().starts_with('#'),
_ => false,
}
}
pub(crate) fn is_continuation(&self) -> bool {
match self.fragments.last() {
Some(Fragment::Text { token }) => token.lexeme().ends_with('\\'),

View File

@ -228,7 +228,8 @@ impl<'src> Node<'src> for Set<'src> {
| Setting::DotenvLoad(value)
| Setting::Export(value)
| Setting::PositionalArguments(value)
| Setting::WindowsPowerShell(value) => {
| Setting::WindowsPowerShell(value)
| Setting::IgnoreComments(value) => {
set.push_mut(value.to_string());
}
Setting::Shell(Shell { command, arguments })

View File

@ -755,36 +755,23 @@ impl<'tokens, 'src> Parser<'tokens, 'src> {
let name = Name::from_identifier(self.presume(Identifier)?);
let lexeme = name.lexeme();
if Keyword::AllowDuplicateRecipes == lexeme {
let value = self.parse_set_bool()?;
return Ok(Set {
value: Setting::AllowDuplicateRecipes(value),
name,
});
} else if Keyword::DotenvLoad == lexeme {
let value = self.parse_set_bool()?;
return Ok(Set {
value: Setting::DotenvLoad(value),
name,
});
} else if Keyword::Export == lexeme {
let value = self.parse_set_bool()?;
return Ok(Set {
value: Setting::Export(value),
name,
});
} else if Keyword::PositionalArguments == lexeme {
let value = self.parse_set_bool()?;
return Ok(Set {
value: Setting::PositionalArguments(value),
name,
});
} else if Keyword::WindowsPowershell == lexeme {
let value = self.parse_set_bool()?;
return Ok(Set {
value: Setting::WindowsPowerShell(value),
name,
});
let set_bool: Option<Setting> = match Keyword::from_lexeme(lexeme) {
Some(kw) => match kw {
Keyword::AllowDuplicateRecipes => {
Some(Setting::AllowDuplicateRecipes(self.parse_set_bool()?))
}
Keyword::DotenvLoad => Some(Setting::DotenvLoad(self.parse_set_bool()?)),
Keyword::IgnoreComments => Some(Setting::IgnoreComments(self.parse_set_bool()?)),
Keyword::Export => Some(Setting::Export(self.parse_set_bool()?)),
Keyword::PositionalArguments => Some(Setting::PositionalArguments(self.parse_set_bool()?)),
Keyword::WindowsPowershell => Some(Setting::WindowsPowerShell(self.parse_set_bool()?)),
_ => None,
},
None => None,
};
if let Some(value) = set_bool {
return Ok(Set { name, value });
}
self.expect(ColonEquals)?;

View File

@ -95,7 +95,7 @@ impl<'src, D> Recipe<'src, D> {
}
}
pub(crate) fn run_linewise<'run>(
fn run_linewise<'run>(
&self,
context: &RecipeContext<'src, 'run>,
dotenv: &BTreeMap<String, String>,
@ -114,6 +114,10 @@ impl<'src, D> Recipe<'src, D> {
let mut continued = false;
let quiet_command = lines.peek().map_or(false, |line| line.is_quiet());
let infallible_command = lines.peek().map_or(false, |line| line.is_infallible());
let comment_line =
context.settings.ignore_comments && lines.peek().map_or(false, |line| line.is_comment());
loop {
if lines.peek().is_none() {
break;
@ -121,7 +125,7 @@ impl<'src, D> Recipe<'src, D> {
let line = lines.next().unwrap();
line_number += 1;
evaluated += &evaluator.evaluate_line(line, continued)?;
if line.is_continuation() {
if line.is_continuation() && !comment_line {
continued = true;
evaluated.pop();
} else {
@ -138,6 +142,10 @@ impl<'src, D> Recipe<'src, D> {
command = &command[1..];
}
if comment_line {
continue;
}
if command.is_empty() {
continue;
}

View File

@ -4,6 +4,7 @@ use super::*;
pub(crate) enum Setting<'src> {
AllowDuplicateRecipes(bool),
DotenvLoad(bool),
IgnoreComments(bool),
Export(bool),
PositionalArguments(bool),
Shell(Shell<'src>),
@ -16,6 +17,7 @@ impl<'src> Display for Setting<'src> {
match self {
Setting::AllowDuplicateRecipes(value)
| Setting::DotenvLoad(value)
| Setting::IgnoreComments(value)
| Setting::Export(value)
| Setting::PositionalArguments(value)
| Setting::WindowsPowerShell(value) => write!(f, "{}", value),

View File

@ -5,11 +5,12 @@ pub(crate) const DEFAULT_SHELL_ARGS: &[&str] = &["-cu"];
pub(crate) const WINDOWS_POWERSHELL_SHELL: &str = "powershell.exe";
pub(crate) const WINDOWS_POWERSHELL_ARGS: &[&str] = &["-NoLogo", "-Command"];
#[derive(Debug, PartialEq, Serialize)]
#[derive(Debug, PartialEq, Serialize, Default)]
pub(crate) struct Settings<'src> {
pub(crate) allow_duplicate_recipes: bool,
pub(crate) dotenv_load: Option<bool>,
pub(crate) export: bool,
pub(crate) ignore_comments: bool,
pub(crate) positional_arguments: bool,
pub(crate) shell: Option<Shell<'src>>,
pub(crate) windows_powershell: bool,
@ -17,18 +18,6 @@ pub(crate) struct Settings<'src> {
}
impl<'src> Settings<'src> {
pub(crate) fn new() -> Settings<'src> {
Settings {
allow_duplicate_recipes: false,
dotenv_load: None,
export: false,
positional_arguments: false,
shell: None,
windows_powershell: false,
windows_shell: None,
}
}
pub(crate) fn shell_command(&self, config: &Config) -> Command {
let (command, args) = self.shell(config);
@ -82,7 +71,7 @@ mod tests {
#[test]
fn default_shell() {
let settings = Settings::new();
let settings = Settings::default();
let config = Config {
shell_command: false,
@ -94,8 +83,10 @@ mod tests {
#[test]
fn default_shell_powershell() {
let mut settings = Settings::new();
settings.windows_powershell = true;
let settings = Settings {
windows_powershell: true,
..Default::default()
};
let config = Config {
shell_command: false,
@ -114,7 +105,7 @@ mod tests {
#[test]
fn overwrite_shell() {
let settings = Settings::new();
let settings = Settings::default();
let config = Config {
shell_command: true,
@ -128,8 +119,10 @@ mod tests {
#[test]
fn overwrite_shell_powershell() {
let mut settings = Settings::new();
settings.windows_powershell = true;
let settings = Settings {
windows_powershell: true,
..Default::default()
};
let config = Config {
shell_command: true,
@ -143,20 +136,21 @@ mod tests {
#[test]
fn shell_cooked() {
let mut settings = Settings::new();
settings.shell = Some(Shell {
command: StringLiteral {
kind: StringKind::from_token_start("\"").unwrap(),
raw: "asdf.exe",
cooked: "asdf.exe".to_string(),
},
arguments: vec![StringLiteral {
kind: StringKind::from_token_start("\"").unwrap(),
raw: "-nope",
cooked: "-nope".to_string(),
}],
});
let settings = Settings {
shell: Some(Shell {
command: StringLiteral {
kind: StringKind::from_token_start("\"").unwrap(),
raw: "asdf.exe",
cooked: "asdf.exe".to_string(),
},
arguments: vec![StringLiteral {
kind: StringKind::from_token_start("\"").unwrap(),
raw: "-nope",
cooked: "-nope".to_string(),
}],
}),
..Default::default()
};
let config = Config {
shell_command: false,
@ -168,8 +162,10 @@ mod tests {
#[test]
fn shell_present_but_not_shell_args() {
let mut settings = Settings::new();
settings.windows_powershell = true;
let settings = Settings {
windows_powershell: true,
..Default::default()
};
let config = Config {
shell: Some("lol".to_string()),
@ -181,8 +177,10 @@ mod tests {
#[test]
fn shell_args_present_but_not_shell() {
let mut settings = Settings::new();
settings.windows_powershell = true;
let settings = Settings {
windows_powershell: true,
..Default::default()
};
let config = Config {
shell_command: false,

85
tests/ignore_comments.rs Normal file
View File

@ -0,0 +1,85 @@
use super::*;
#[test]
fn ignore_comments_in_recipe() {
Test::new()
.justfile(
"
set ignore-comments
some_recipe:
# A recipe-internal comment
echo something-useful
",
)
.stdout("something-useful\n")
.stderr("echo something-useful\n")
.run();
}
#[test]
fn dont_ignore_comments_in_recipe_by_default() {
Test::new()
.justfile(
"
some_recipe:
# A recipe-internal comment
echo something-useful
",
)
.stdout("something-useful\n")
.stderr("# A recipe-internal comment\necho something-useful\n")
.run();
}
#[test]
fn ignore_recipe_comments_with_shell_setting() {
Test::new()
.justfile(
"
set shell := ['echo', '-n']
set ignore-comments
some_recipe:
# Alternate shells still ignore comments
echo something-useful
",
)
.stdout("something-useful\n")
.stderr("echo something-useful\n")
.run();
}
#[test]
fn continuations_iwth_echo_comments_false() {
Test::new()
.justfile(
"
set ignore-comments
some_recipe:
# Comment lines ignore line continuations \\
echo something-useful
",
)
.stdout("something-useful\n")
.stderr("echo something-useful\n")
.run();
}
#[test]
fn continuations_with_echo_comments_true() {
Test::new()
.justfile(
"
set ignore-comments := false
some_recipe:
# comment lines can be continued \\
echo something-useful
",
)
.stdout("")
.stderr("# comment lines can be continued echo something-useful\n")
.run();
}

View File

@ -44,6 +44,7 @@ fn alias() {
"export": false,
"positional_arguments": false,
"shell": null,
"ignore_comments": false,
"windows_powershell": false,
"windows_shell": null,
},
@ -71,6 +72,7 @@ fn assignment() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -113,6 +115,7 @@ fn body() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -165,6 +168,7 @@ fn dependencies() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -254,6 +258,7 @@ fn dependency_argument() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -307,6 +312,7 @@ fn duplicate_recipes() {
"allow_duplicate_recipes": true,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -342,6 +348,7 @@ fn doc_comment() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -365,6 +372,7 @@ fn empty_justfile() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -497,6 +505,7 @@ fn parameters() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -568,6 +577,7 @@ fn priors() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -603,6 +613,7 @@ fn private() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -638,6 +649,7 @@ fn quiet() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -665,6 +677,7 @@ fn settings() {
set dotenv-load
set export
set positional-arguments
set ignore-comments
set shell := ['a', 'b', 'c']
foo:
@ -691,6 +704,7 @@ fn settings() {
"allow_duplicate_recipes": false,
"dotenv_load": true,
"export": true,
"ignore_comments": true,
"positional_arguments": true,
"shell": {
"arguments": ["b", "c"],
@ -732,6 +746,7 @@ fn shebang() {
"allow_duplicate_recipes": false,
"dotenv_load": null,
"export": false,
"ignore_comments": false,
"positional_arguments": false,
"shell": null,
"windows_powershell": false,
@ -769,6 +784,7 @@ fn simple() {
"export": false,
"positional_arguments": false,
"shell": null,
"ignore_comments": false,
"windows_powershell": false,
"windows_shell": null,
},

View File

@ -50,6 +50,7 @@ mod export;
mod fall_back_to_parent;
mod fmt;
mod functions;
mod ignore_comments;
mod init;
#[cfg(unix)]
mod interrupts;