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 export : 'export' assignment
setting : 'set' 'dotenv-load' boolean? setting : 'set' 'dotenv-load' boolean?
| 'set' 'ignore-comments' boolean?
| 'set' 'export' boolean? | 'set' 'export' boolean?
| 'set' 'positional-arguments' boolean? | 'set' 'positional-arguments' boolean?
| 'set' 'allow-duplicate-recipes' boolean? | 'set' 'allow-duplicate-recipes' boolean?

View File

@ -641,15 +641,16 @@ foo:
#### Table of Settings #### Table of Settings
| Name | Value | Description | | Name | Value | Default | Description |
| ------------------------- | ------------------ | --------------------------------------------------------------------------------------------- | | ------------------------- | ------------------ | --------|---------------------------------------------------------------------------------------------- |
| `allow-duplicate-recipes` | boolean | Allow recipes appearing later in a `justfile` to override earlier recipes with the same name. | | `allow-duplicate-recipes` | boolean | False | Allow recipes appearing later in a `justfile` to override earlier recipes with the same name. |
| `dotenv-load` | boolean | Load a `.env` file, if present. | | `dotenv-load` | boolean | False | Load a `.env` file, if present. |
| `export` | boolean | Export all variables as environment variables. | | `export` | boolean | False | Export all variables as environment variables. |
| `positional-arguments` | boolean | Pass positional arguments. | | `ignore-comments` | boolean | False | Ignore recipe lines beginning with `#`. |
| `shell` | `[COMMAND, ARGS…]` | Set the command used to invoke recipes and evaluate backticks. | | `positional-arguments` | boolean | False | Pass positional arguments. |
| `windows-shell` | `[COMMAND, ARGS…]` | Set the command used to invoke recipes and evaluate backticks. | | `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. | | `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: 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 { for (_, set) in self.sets {
match set.value { match set.value {
@ -53,6 +53,9 @@ impl<'src> Analyzer<'src> {
Setting::Export(export) => { Setting::Export(export) => {
settings.export = export; settings.export = export;
} }
Setting::IgnoreComments(ignore_comments) => {
settings.ignore_comments = ignore_comments;
}
Setting::PositionalArguments(positional_arguments) => { Setting::PositionalArguments(positional_arguments) => {
settings.positional_arguments = positional_arguments; settings.positional_arguments = positional_arguments;
} }

View File

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

View File

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

View File

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

View File

@ -12,6 +12,13 @@ impl<'src> Line<'src> {
self.fragments.is_empty() 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 { pub(crate) fn is_continuation(&self) -> bool {
match self.fragments.last() { match self.fragments.last() {
Some(Fragment::Text { token }) => token.lexeme().ends_with('\\'), 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::DotenvLoad(value)
| Setting::Export(value) | Setting::Export(value)
| Setting::PositionalArguments(value) | Setting::PositionalArguments(value)
| Setting::WindowsPowerShell(value) => { | Setting::WindowsPowerShell(value)
| Setting::IgnoreComments(value) => {
set.push_mut(value.to_string()); set.push_mut(value.to_string());
} }
Setting::Shell(Shell { command, arguments }) 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 name = Name::from_identifier(self.presume(Identifier)?);
let lexeme = name.lexeme(); let lexeme = name.lexeme();
if Keyword::AllowDuplicateRecipes == lexeme { let set_bool: Option<Setting> = match Keyword::from_lexeme(lexeme) {
let value = self.parse_set_bool()?; Some(kw) => match kw {
return Ok(Set { Keyword::AllowDuplicateRecipes => {
value: Setting::AllowDuplicateRecipes(value), Some(Setting::AllowDuplicateRecipes(self.parse_set_bool()?))
name, }
}); Keyword::DotenvLoad => Some(Setting::DotenvLoad(self.parse_set_bool()?)),
} else if Keyword::DotenvLoad == lexeme { Keyword::IgnoreComments => Some(Setting::IgnoreComments(self.parse_set_bool()?)),
let value = self.parse_set_bool()?; Keyword::Export => Some(Setting::Export(self.parse_set_bool()?)),
return Ok(Set { Keyword::PositionalArguments => Some(Setting::PositionalArguments(self.parse_set_bool()?)),
value: Setting::DotenvLoad(value), Keyword::WindowsPowershell => Some(Setting::WindowsPowerShell(self.parse_set_bool()?)),
name, _ => None,
}); },
} else if Keyword::Export == lexeme { None => None,
let value = self.parse_set_bool()?; };
return Ok(Set {
value: Setting::Export(value), if let Some(value) = set_bool {
name, return Ok(Set { name, value });
});
} 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,
});
} }
self.expect(ColonEquals)?; 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, &self,
context: &RecipeContext<'src, 'run>, context: &RecipeContext<'src, 'run>,
dotenv: &BTreeMap<String, String>, dotenv: &BTreeMap<String, String>,
@ -114,6 +114,10 @@ impl<'src, D> Recipe<'src, D> {
let mut continued = false; let mut continued = false;
let quiet_command = lines.peek().map_or(false, |line| line.is_quiet()); 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 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 { loop {
if lines.peek().is_none() { if lines.peek().is_none() {
break; break;
@ -121,7 +125,7 @@ impl<'src, D> Recipe<'src, D> {
let line = lines.next().unwrap(); let line = lines.next().unwrap();
line_number += 1; line_number += 1;
evaluated += &evaluator.evaluate_line(line, continued)?; evaluated += &evaluator.evaluate_line(line, continued)?;
if line.is_continuation() { if line.is_continuation() && !comment_line {
continued = true; continued = true;
evaluated.pop(); evaluated.pop();
} else { } else {
@ -138,6 +142,10 @@ impl<'src, D> Recipe<'src, D> {
command = &command[1..]; command = &command[1..];
} }
if comment_line {
continue;
}
if command.is_empty() { if command.is_empty() {
continue; continue;
} }

View File

@ -4,6 +4,7 @@ use super::*;
pub(crate) enum Setting<'src> { pub(crate) enum Setting<'src> {
AllowDuplicateRecipes(bool), AllowDuplicateRecipes(bool),
DotenvLoad(bool), DotenvLoad(bool),
IgnoreComments(bool),
Export(bool), Export(bool),
PositionalArguments(bool), PositionalArguments(bool),
Shell(Shell<'src>), Shell(Shell<'src>),
@ -16,6 +17,7 @@ impl<'src> Display for Setting<'src> {
match self { match self {
Setting::AllowDuplicateRecipes(value) Setting::AllowDuplicateRecipes(value)
| Setting::DotenvLoad(value) | Setting::DotenvLoad(value)
| Setting::IgnoreComments(value)
| Setting::Export(value) | Setting::Export(value)
| Setting::PositionalArguments(value) | Setting::PositionalArguments(value)
| Setting::WindowsPowerShell(value) => write!(f, "{}", 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_SHELL: &str = "powershell.exe";
pub(crate) const WINDOWS_POWERSHELL_ARGS: &[&str] = &["-NoLogo", "-Command"]; 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) struct Settings<'src> {
pub(crate) allow_duplicate_recipes: bool, pub(crate) allow_duplicate_recipes: bool,
pub(crate) dotenv_load: Option<bool>, pub(crate) dotenv_load: Option<bool>,
pub(crate) export: bool, pub(crate) export: bool,
pub(crate) ignore_comments: bool,
pub(crate) positional_arguments: bool, pub(crate) positional_arguments: bool,
pub(crate) shell: Option<Shell<'src>>, pub(crate) shell: Option<Shell<'src>>,
pub(crate) windows_powershell: bool, pub(crate) windows_powershell: bool,
@ -17,18 +18,6 @@ pub(crate) struct Settings<'src> {
} }
impl<'src> 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 { pub(crate) fn shell_command(&self, config: &Config) -> Command {
let (command, args) = self.shell(config); let (command, args) = self.shell(config);
@ -82,7 +71,7 @@ mod tests {
#[test] #[test]
fn default_shell() { fn default_shell() {
let settings = Settings::new(); let settings = Settings::default();
let config = Config { let config = Config {
shell_command: false, shell_command: false,
@ -94,8 +83,10 @@ mod tests {
#[test] #[test]
fn default_shell_powershell() { fn default_shell_powershell() {
let mut settings = Settings::new(); let settings = Settings {
settings.windows_powershell = true; windows_powershell: true,
..Default::default()
};
let config = Config { let config = Config {
shell_command: false, shell_command: false,
@ -114,7 +105,7 @@ mod tests {
#[test] #[test]
fn overwrite_shell() { fn overwrite_shell() {
let settings = Settings::new(); let settings = Settings::default();
let config = Config { let config = Config {
shell_command: true, shell_command: true,
@ -128,8 +119,10 @@ mod tests {
#[test] #[test]
fn overwrite_shell_powershell() { fn overwrite_shell_powershell() {
let mut settings = Settings::new(); let settings = Settings {
settings.windows_powershell = true; windows_powershell: true,
..Default::default()
};
let config = Config { let config = Config {
shell_command: true, shell_command: true,
@ -143,9 +136,8 @@ mod tests {
#[test] #[test]
fn shell_cooked() { fn shell_cooked() {
let mut settings = Settings::new(); let settings = Settings {
shell: Some(Shell {
settings.shell = Some(Shell {
command: StringLiteral { command: StringLiteral {
kind: StringKind::from_token_start("\"").unwrap(), kind: StringKind::from_token_start("\"").unwrap(),
raw: "asdf.exe", raw: "asdf.exe",
@ -156,7 +148,9 @@ mod tests {
raw: "-nope", raw: "-nope",
cooked: "-nope".to_string(), cooked: "-nope".to_string(),
}], }],
}); }),
..Default::default()
};
let config = Config { let config = Config {
shell_command: false, shell_command: false,
@ -168,8 +162,10 @@ mod tests {
#[test] #[test]
fn shell_present_but_not_shell_args() { fn shell_present_but_not_shell_args() {
let mut settings = Settings::new(); let settings = Settings {
settings.windows_powershell = true; windows_powershell: true,
..Default::default()
};
let config = Config { let config = Config {
shell: Some("lol".to_string()), shell: Some("lol".to_string()),
@ -181,8 +177,10 @@ mod tests {
#[test] #[test]
fn shell_args_present_but_not_shell() { fn shell_args_present_but_not_shell() {
let mut settings = Settings::new(); let settings = Settings {
settings.windows_powershell = true; windows_powershell: true,
..Default::default()
};
let config = Config { let config = Config {
shell_command: false, 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, "export": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"ignore_comments": false,
"windows_powershell": false, "windows_powershell": false,
"windows_shell": null, "windows_shell": null,
}, },
@ -71,6 +72,7 @@ fn assignment() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -113,6 +115,7 @@ fn body() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -165,6 +168,7 @@ fn dependencies() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -254,6 +258,7 @@ fn dependency_argument() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -307,6 +312,7 @@ fn duplicate_recipes() {
"allow_duplicate_recipes": true, "allow_duplicate_recipes": true,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -342,6 +348,7 @@ fn doc_comment() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -365,6 +372,7 @@ fn empty_justfile() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -497,6 +505,7 @@ fn parameters() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -568,6 +577,7 @@ fn priors() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -603,6 +613,7 @@ fn private() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -638,6 +649,7 @@ fn quiet() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -665,6 +677,7 @@ fn settings() {
set dotenv-load set dotenv-load
set export set export
set positional-arguments set positional-arguments
set ignore-comments
set shell := ['a', 'b', 'c'] set shell := ['a', 'b', 'c']
foo: foo:
@ -691,6 +704,7 @@ fn settings() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": true, "dotenv_load": true,
"export": true, "export": true,
"ignore_comments": true,
"positional_arguments": true, "positional_arguments": true,
"shell": { "shell": {
"arguments": ["b", "c"], "arguments": ["b", "c"],
@ -732,6 +746,7 @@ fn shebang() {
"allow_duplicate_recipes": false, "allow_duplicate_recipes": false,
"dotenv_load": null, "dotenv_load": null,
"export": false, "export": false,
"ignore_comments": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"windows_powershell": false, "windows_powershell": false,
@ -769,6 +784,7 @@ fn simple() {
"export": false, "export": false,
"positional_arguments": false, "positional_arguments": false,
"shell": null, "shell": null,
"ignore_comments": false,
"windows_powershell": false, "windows_powershell": false,
"windows_shell": null, "windows_shell": null,
}, },

View File

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