-
Notifications
You must be signed in to change notification settings - Fork 402
Separate parse from invocation configurations #2606
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
base: main
Are you sure you want to change the base?
Separate parse from invocation configurations #2606
Conversation
I was going to comment that a suggest directive or a help action would need to be able to read the parsing configuration at invocation time, such as EnablePosixBundling. But I see ParseResult does have public properties for both configurations. |
CommandLineConfiguration
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.
Overall it LGTM, but please see my comments with some suggestions for improvements. Thank you @jonsequitur !
public System.Threading.Tasks.Task<System.Int32> InvokeAsync(System.Threading.CancellationToken cancellationToken = null) | ||
public System.Threading.Tasks.Task<System.Int32> InvokeAsync(InvocationConfiguration configuration, System.Threading.CancellationToken cancellationToken = null) |
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.
For Parse
methods, the configuration
argument is optional (null
by default, mapped into default config by the implementation). Perhaps Invoke
should follow the same patter for consistency?
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.
Or should we change Parse
to this overload approach versus coalescing the null parameter at runtime?
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.
If consistency is important here, another reason we might consider consolidating by splitting out Parse into two overloads (Parse()
and Parse(ParserConfiguration)
) is that that would not be a code-breaking change. Invoke
has an InvokeAsync
variant and this already has an optional CancellationToken
argument, so adding an overload avoids breaking existing callers, since the InvocationConfiguration
parameter comes before the CancellationToken
parameter. (Of course there are a lot of breaking changes here so maybe this doesn't matter.)
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.
Or should we change
Parse
to this overload approach versus coalescing the null parameter at runtime?
I strongly believe that we should not do that. IIRC this shape was approved in the API Design, it would be another breaking change and also another public API.
Last, but not least my goal with exposing Parse(CommandLineConfiguration? config = null)
as the only overload was to make it easier for the end users to discover the fact that CommandLineConfiguration
exists (in the past, it was quite hard to find out how to customize parsing: you would need to know about the existance of builder type etc).
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.
Invoke
has anInvokeAsync
variant and this already has an optionalCancellationToken
argument, so adding an overload avoids breaking existing callers, since theInvocationConfiguration
parameter comes before theCancellationToken
parameter. (Of course there are a lot of breaking changes here so maybe this doesn't matter.)
That is very good point. However, from my observation it seems that users hardly ever provide CancellationToken
to InvokeAsync
as they usually call it from Main
where they don't have any token.
public System.Boolean EnablePosixBundling { get; set; } | ||
public System.CommandLine.Parsing.TryReplaceToken ResponseFileTokenReplacer { get; set; } | ||
public System.Void ThrowIfInvalid(Command command) | ||
public class ParserConfigurationException : System.Exception, System.Runtime.Serialization.ISerializable |
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 almost forgot about #1909. Should we simply remove this custom exception type and throw something defined by the BCL?
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 had the same thought. I'd love suggestions on what would be an appropriate exception type.
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.
@KalleOlaviNiemitalo has a very good breakdown here: #1909 (comment)
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.
InvalidOperationException
would be my best bet for now.
Or... even just removing ThrowIfInvalid
? We don't use it anywhere outside of tests and it can be implemented with the public APIs we provide. It's also a bit confusing that it's part of the Config
class, while what it does it checks the symbol hierarchy (and RootCommand
is no longer part of the config)
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 like this suggestion. This method has always been awkward.
We used to do this validation at runtime. Then we decided it was too performance-intensive so we said it should be used only in tests. My worry has been that people will assume the parser will catch these kinds of mistakes automatically, and it's not intuitive to implement some of the checks. (And I'm pretty sure we missed some cases.) Of course, ASP.NET left the analogous issue (routing validation) as an exercise for the user, so probably System.CommandLine should do the same.
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
775d19a
to
1f37d7d
Compare
This PR's goal is to separate the
CommandLineConfiguration
class into two separate classes, one of which configures parsing behaviors (now calledParserConfiguration
) and the other of which configures invocation behaviors (InvocationConfiguration
).InvocationConfiguration
has four properties which were moved over fromCommandLineConfiguration
:EnableDefaultExceptionHandler
ProcessTerminationTimeout
Output
Error
I've also removed the
Parse
,Invoke
, andInvokeAsync
methods from the formerCommandLineConfiguration
, since they're also available onParseResult
. The redundancy seemed unnecessary, although these invoke methods did include aParse
call, making the calling pattern more like the old, more succinctCommand.Invoke
methods.Next steps:
RootCommand
to theCommandLineConfiguration
constructor should not be necessary, and can lead to some unclear situations, such as: