-
Notifications
You must be signed in to change notification settings - Fork 10
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
feat: customize error formatting and styling #72
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thanks for doing this! I appreciate that you were able to keep the implementation simple and straightforward.
I just have a handful of comments/questions. Let me know what you think. Thanks!
api.js
Outdated
@@ -40,6 +40,7 @@ class Api { | |||
this._strictMode = 'strictMode' in opts ? opts.strictMode : false | |||
this._magicCommandAdded = false | |||
this._modulesSeen = opts.modulesSeen || [] | |||
this._errorFormatter = undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could move this to the configure
method, so there's an alternate way of setting/overriding it without calling .unexpectedErrorFormatter(fn)
.
Perhaps something like this:
this._errorFormatter = opts.errorFormatter || this._errorFormatter // expects a function
api.js
Outdated
@@ -258,6 +259,11 @@ class Api { | |||
return this | |||
} | |||
|
|||
unexpectedErrorFormatter (fn) { | |||
this._errorFormatter = fn |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kinda wish the name of the variable more closely resembled the name of the Api method, but I also prefer shorter names to longer ones.
I don't know, this is probably fine. (No action needed.)
api.js
Outdated
// part, these options will be ignored, as the Context usually does not care about | ||
// styling - but it will pick up individual options if appropriate (such as | ||
// `styleUnexpectedError` if there is unexpected error). | ||
...this.helpOpts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a little odd/unnecessary/inefficient to me to pass all helpOpts
to the Context, but I guess it doesn't hurt anything. (No action needed.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like, once you take the plunge to say "we sometimes require styling in Api
", you should just pass it all.
I'd be totally comfortable including it nested if you felt that was better encapsulated -- i.e. instead of ...helpOpts
pass in helpOpts
as a key.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd be totally comfortable including it nested if you felt that was better encapsulated -- i.e. instead of
...helpOpts
pass inhelpOpts
as a key.
My gut instinct tells me this would be better than how it currently is, though I can't objectively defend that.
If you're willing to make that change, I'd appreciate it.
context.js
Outdated
this.output = typeof this.styleUnexpectedError === 'function' | ||
? this.styleUnexpectedError(message) | ||
: message |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wish there was a way to do this where we didn't have to split style hook executions between different locations, but I can't think of a better way of doing it than this.
The only potential problem I see is that this doesn't leverage the all
style hook (as a final wrapper for all other styled strings), but it wasn't doing that before, and I'm not sure if a CLI author would expect the all
hook to apply to a formatted error message or not (currently it only applies to help text and expected CLI messages). What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it depends what you're doing with all
. Possible use case:
all: text => {
// Example weird use case: always return output as a JSON blob containing lines as an array,
// for consumption by some internal API.
return JSON.stringify({ lines: text.split('\n') })
}
If this is how I was using sywac, then I would want any output error messages to be run through it as well (although, in this case, I'm probably using parse()
and could just examine .errors
instead of the output).
My concern about just wrapping it with all
is that you would potentially run afoul of all
handers written just to handle the expected help text screen and not a generic error. I almost feel you need to pass a second boolean option to tell the function whether this is a "help text" context or "unexpected error" context... but if you did that, and the user actually uses the boolean to make a decision, then I feel you could just as easily require them to put the logic in the unexpected error handler:
all: text => {
return JSON.stringify({ lines: text.split('\n') })
},
unexpectedError: text => {
return JSON.stringify({ lines: text.split('\n') })
},
TLDR: This is a good point but I think I land on the side of "all does not apply to unexpected errors, because that's more flexible than the alternative".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I agree with you. Thanks for the feedback!
|
||
t.end() | ||
}) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be good to also have a test that applies formatting/styling to an error thrown from a (nested) command.
api.js
Outdated
@@ -40,6 +40,7 @@ class Api { | |||
this._strictMode = 'strictMode' in opts ? opts.strictMode : false | |||
this._magicCommandAdded = false | |||
this._modulesSeen = opts.modulesSeen || [] | |||
this._errorFormatter = undefined |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On another note, when I first saw this, I thought the _errorFormatter
property should be propagated down to child Api instances via newChild
, but then I realized that it's only applied at the top-level since that's where the single Context instance (per parse) is initialized.
When documenting this, we should note that both .unexpectedErrorFormatter(fn)
and the unexpectedError
style hook can only be applied at the top-level (i.e. cannot be applied/overridden by command configuration), because a single "top-level" Context instance is used for all (potentially nested) command levels.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In preparation for implementing #65, perhaps we could go ahead and throw an error from .unexpectedErrorFormatter(fn)
if it's called on a child Api instance?
Just trying to think in terms of setting a precedent for the future. Otherwise, I'm completely fine if the calls simply get ignored in the initial implementation of this feature.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference is to start with no error for now -- in my opinion, if people complained about being bitten by this bug, then the "right fix" isn't to prevent people from passing the error formatter to subcommands, it's to fix it so it works as the user was assuming.
(That's out of scope for this PR, but I think it's possible if we cared enough -- we'd need to capture an unexpected error thrown at the Command
level and capture the error formatter from that context, and then ensure that one got used if and when an unexpected error was printed. I consider this a "potential future todo" that's better than just preventing people from using the option.)
New commit pushed:
|
SUMMARY
.unexpectedErrorFormatter()
.unexpectedError
style hook.DETAILS
This PR gives two ways to customize the formatting and/or the styling of the error output of an unexpected error (an error thrown by a
run()
orcheck()
handler, or added manually usingcontext.unexpectedError
).