Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Suppress command exit code error messages #1354

Merged

Conversation

gokhanettin
Copy link
Contributor

@gokhanettin gokhanettin commented Sep 25, 2022

When a recipe wraps cli tool and the tool exits with a non-zero code,
just adds its own extra exit error message along with the messages
from the tool. Introduce no-exit-message attribute to suppress this
additional message.

Closes #1334

@casey
Copy link
Owner

casey commented Sep 28, 2022

I didn't take a close look, but this looks good. Thanks for writing tests! Let's figure out if this flag should also suppress command echoing.

@casey
Copy link
Owner

casey commented Oct 13, 2022

Nice, let me know when this is ready to review!

@gokhanettin gokhanettin force-pushed the suppress-command-exit-code-error-messages branch from 80f07eb to 504c354 Compare October 14, 2022 19:38
@gokhanettin
Copy link
Contributor Author

@casey, I think it is ready to review. We can update the docs when we agree on the implementation.

I have a couple of concerns here. If we need to add multiple annotations, we are likely to support a syntax like [no-exit-message, annotation2, annotation3] which we can't represent with annotation: Option<Annotation<'src>> any more. It will probably be something like annotations: BTreeSet<Annotation<'src>>. This won't be backward compatible with the json representation. Do you think we should now have annotations: BTreeSet<Annotation<'src>> within the recipe struct even though we currently have only one annotation type?

@casey
Copy link
Owner

casey commented Oct 15, 2022

@casey, I think it is ready to review. We can update the docs when we agree on the implementation.

Nice! I'm just heading out of town for the weekend, so I won't be able to look at this until next week when I get back. Apologies for the delay.

I have a couple of concerns here. If we need to add multiple annotations, we are likely to support a syntax like [no-exit-message, annotation2, annotation3] which we can't represent with annotation: Option<Annotation<'src>> any more. It will probably be something like annotations: BTreeSet<Annotation<'src>>. This won't be backward compatible with the json representation. Do you think we should now have annotations: BTreeSet<Annotation<'src>> within the recipe struct even though we currently have only one annotation type?

Good thinking. One note: The JSON dump format is unstable, i.e., requires the --unstable flag, so it can be changed in backwards incompatible ways if necessary. Although I agree it's a good idea to avoid that if possible.

I actually think that recipes probably shouldn't have a BTreeSet of annotations, but rather they should have a suppress_exit_error_messages: bool field. My thinking is that by the time you have a Recipe struct, the actual annotations aren't important, just their semantics.

Copy link
Owner

@casey casey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good, thanks for all the tests!

A few high level thoughts:

  • I wasn't sure whether we should call these "attributes" or "annotations", so I did a google search for programming "function attribute" and programming "function annotation", and the search with "attribute" got 8x more hits, so I think we should go with that and call them "attributes" instead of annotations.

  • Since the json now just includes a single suppress_exit_error_messages boolean, which will be backwards compatible with future additions, I think we should avoid parsing the comma-separate attribute list syntax, i.e., [a,b,c], for now. We can easily add it in the future if we need it.

  • Since an attribute is required to be directly in front of a recipe, instead of popping an attribute off the list of items, in the main parser loop, we can parse an attribute if we see a [, and then immediatly parse a recipe:

    } else if self.accepted(BracketL)? {
      items.push(Item::AnnotationContainer(self.parse_annotations()?));
      self.expect_eol()?;
      self.parse_recipe()?
    }

    This avoids the need to check for spurious newlines or make the pop_doc_comment function more complicated.

  • I think the signature of Parser::parse_atribute should be fn parse_attribute(&mut self) -> Result<Attribute>, and attribute can be:

    enum Attribute {
      NoExitMessage
    }

    This is nice because the parser returns a single, strongly-typed attribute, so downstream code doesn't need to worry about checking for invalid attributes.

@gokhanettin
Copy link
Contributor Author

Thanks for the review. I agree with all the thoughts. I will be back with the changes in a couple of days.

@gokhanettin gokhanettin force-pushed the suppress-command-exit-code-error-messages branch from 96f4ba0 to a54b8d7 Compare October 21, 2022 22:49
@gokhanettin gokhanettin marked this pull request as ready for review October 21, 2022 22:50
@gokhanettin gokhanettin force-pushed the suppress-command-exit-code-error-messages branch 2 times, most recently from 815dc97 to fd9ba23 Compare October 22, 2022 13:08
Copy link
Owner

@casey casey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice this is great! A few comments, but it's all small stuff.

README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
src/attribute.rs Outdated Show resolved Hide resolved
@@ -98,6 +98,9 @@ pub(crate) enum CompileErrorKind<'src> {
alias: &'src str,
target: &'src str,
},
UnknownAttribute {
attribute: &'src str,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
attribute: &'src str,
attribute: Name<'src>,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other CompileErrorKinds like UnknownFunction, UnknownSetting all have &'src str. I thought there was nothing special about the attribute. Considering your parse_attribute_name suggestion, do you find anything wrong or ugly with the code below @casey?

...
UnknownAttribute {
  attribute: &'src str,
},
  /// Parse a recipe attribute name
  fn parse_attribute_name(&mut self) -> CompileResult<'src, Attribute> {
    let name = self.parse_name()?;
    Attribute::from_name(name).ok_or_else(|| {
      name.error(CompileErrorKind::UnknownAttribute {
        attribute: name.lexeme(),
      })
    })
  }

I think we should have &'src str here. CompileError returned by error() method already has a token field. We should not have another Token or Name type field within the CompileErrorKind.

pub(crate) struct CompileError<'src> {
  pub(crate) token: Token<'src>,
  pub(crate) kind: Box<CompileErrorKind<'src>>,
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great point, makes sense to me!

src/error.rs Outdated Show resolved Hide resolved
src/justfile.rs Outdated
@@ -472,6 +472,7 @@ mod tests {
recipe,
line_number,
code,
..
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more verbose, but I think I'd extract and check these with an assert below:

Suggested change
..
suppress_error_message,

src/parser.rs Outdated Show resolved Hide resolved
src/parser.rs Outdated
Comment on lines 828 to 837
/// Parse a recipe attribute
fn parse_attribute(&mut self) -> CompileResult<'src, Attribute> {
let token = self.expect(Identifier)?;
let lexeme = token.lexeme();
if let Some(attribute) = Attribute::from_lexeme(lexeme) {
self.expect(BracketR)?;
Ok(attribute)
} else {
Err(token.error(CompileErrorKind::UnknownAttribute { attribute: lexeme }))
}
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not quite right, since you also need the token to produce the error message, but something like this:

Suggested change
/// Parse a recipe attribute
fn parse_attribute(&mut self) -> CompileResult<'src, Attribute> {
let token = self.expect(Identifier)?;
let lexeme = token.lexeme();
if let Some(attribute) = Attribute::from_lexeme(lexeme) {
self.expect(BracketR)?;
Ok(attribute)
} else {
Err(token.error(CompileErrorKind::UnknownAttribute { attribute: lexeme }))
}
}
/// Parse a recipe attribute name
fn parse_attribute_name(&mut self) -> CompileResult<'src, Attribute> {
let name = self.parse_name()?;
Attribute::from_lexeme(name).ok_or_else(|| token.error(CompileErrorKind::UnknownAttribute { attribute: lexeme }))
}

@gokhanettin gokhanettin force-pushed the suppress-command-exit-code-error-messages branch from fd9ba23 to cbb4a81 Compare October 23, 2022 22:55
@gokhanettin
Copy link
Contributor Author

I am not sure about CompileErrorKind::UnknownAttribute field type. Pushed all other suggestions. Also need to update GRAMMER.md.

Is this OK?

recipe        : attribute? '@'? NAME parameter* variadic? ':' dependency* body?

attribute     : '[' NAME ']' eol

Copy link
Owner

@casey casey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One minor comment, otherwise looks great. Your suggestion for representing attributes in the grammar looks good to me.

src/parser.rs Outdated
items.push(Item::Recipe(self.parse_recipe(doc, true)?));
items.push(Item::Recipe(self.parse_recipe(doc, true, false)?));
} else if self.accepted(BracketL)? {
self.parse_attribute_name()?;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This works, but it's a bit unclear what's going on. I'd do something like:

let Attribute::NoExitMessage = self.parse_atribute_name()?;

This makes it clear what's going on, and it will turn into a compile error when we add another variant.

When a recipe wraps cli tool and the tool exits with a non-zero code,
just adds its own extra exit error message along with the messages
from the tool. Introduce `no-exit-message` attribute to suppress this
additional message.
@gokhanettin gokhanettin force-pushed the suppress-command-exit-code-error-messages branch from 19a4f1f to ef0278a Compare October 25, 2022 19:09
@casey casey merged commit 8b7640b into casey:master Oct 25, 2022
@casey
Copy link
Owner

casey commented Oct 25, 2022

Very nice! Great work, especially for a non-trivial PR that touches the parser!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Supress the "error: Recipe ... failed on line ... with exit code ..." without suppressing the exit code
2 participants