From e445cfb47dcd797e8817512b919843303f61d793 Mon Sep 17 00:00:00 2001 From: Greg Shuflin Date: Tue, 4 Oct 2022 17:32:30 -0700 Subject: [PATCH] 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. --- GRAMMAR.md | 1 + README.md | 19 ++++----- src/analyzer.rs | 5 ++- src/justfile.rs | 13 +++++- src/keyword.rs | 1 + src/lib.rs | 1 + src/line.rs | 7 ++++ src/node.rs | 3 +- src/parser.rs | 47 ++++++++-------------- src/recipe.rs | 12 +++++- src/setting.rs | 2 + src/settings.rs | 72 +++++++++++++++++----------------- tests/ignore_comments.rs | 85 ++++++++++++++++++++++++++++++++++++++++ tests/json.rs | 16 ++++++++ tests/lib.rs | 1 + 15 files changed, 204 insertions(+), 81 deletions(-) create mode 100644 tests/ignore_comments.rs diff --git a/GRAMMAR.md b/GRAMMAR.md index 6e5f818a..6bb4afc1 100644 --- a/GRAMMAR.md +++ b/GRAMMAR.md @@ -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? diff --git a/README.md b/README.md index 61ff9211..7da202f3 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/src/analyzer.rs b/src/analyzer.rs index 39e03a5e..bbd4434f 100644 --- a/src/analyzer.rs +++ b/src/analyzer.rs @@ -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; } diff --git a/src/justfile.rs b/src/justfile.rs index 26f06ea2..ae90df8e 100644 --- a/src/justfile.rs +++ b/src/justfile.rs @@ -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, search: &Search, + settings: &Settings, ran: &mut BTreeSet>, ) -> 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::>(), dotenv, search, + settings, ran, )?; } @@ -341,6 +351,7 @@ impl<'src> Justfile<'src> { &evaluated.iter().map(String::as_ref).collect::>(), dotenv, search, + settings, &mut ran, )?; } diff --git a/src/keyword.rs b/src/keyword.rs index 2913e711..439a1540 100644 --- a/src/keyword.rs +++ b/src/keyword.rs @@ -10,6 +10,7 @@ pub(crate) enum Keyword { Export, False, If, + IgnoreComments, PositionalArguments, Set, Shell, diff --git a/src/lib.rs b/src/lib.rs index 4aa7ead7..41ab113e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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, diff --git a/src/line.rs b/src/line.rs index 6f26506d..f0bc6dc9 100644 --- a/src/line.rs +++ b/src/line.rs @@ -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('\\'), diff --git a/src/node.rs b/src/node.rs index 60eb0422..65c1c63c 100644 --- a/src/node.rs +++ b/src/node.rs @@ -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 }) diff --git a/src/parser.rs b/src/parser.rs index ac6324e5..4e76082c 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -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 = 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)?; diff --git a/src/recipe.rs b/src/recipe.rs index 63787a3f..80156f22 100644 --- a/src/recipe.rs +++ b/src/recipe.rs @@ -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, @@ -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; } diff --git a/src/setting.rs b/src/setting.rs index 6349cabd..a30abdc2 100644 --- a/src/setting.rs +++ b/src/setting.rs @@ -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), diff --git a/src/settings.rs b/src/settings.rs index c755a604..6e27305f 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -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, pub(crate) export: bool, + pub(crate) ignore_comments: bool, pub(crate) positional_arguments: bool, pub(crate) shell: Option>, 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, diff --git a/tests/ignore_comments.rs b/tests/ignore_comments.rs new file mode 100644 index 00000000..1dc04b19 --- /dev/null +++ b/tests/ignore_comments.rs @@ -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(); +} diff --git a/tests/json.rs b/tests/json.rs index e312111d..8742b0b3 100644 --- a/tests/json.rs +++ b/tests/json.rs @@ -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, }, diff --git a/tests/lib.rs b/tests/lib.rs index 9867e3da..3e6f9d0a 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -50,6 +50,7 @@ mod export; mod fall_back_to_parent; mod fmt; mod functions; +mod ignore_comments; mod init; #[cfg(unix)] mod interrupts;