Skip to content

introduce Command.Parse(CommandLineConfiguration) #2062

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

Merged
merged 14 commits into from
Feb 22, 2023

Conversation

adamsitnik
Copy link
Member

@adamsitnik adamsitnik commented Feb 20, 2023

Initially, I just wanted to extend Symbol with Parse(string[], CommandLineConfiguration).

I've realized that the existing Argument.Parse and Option.Parse methods have side effects, namely creating a RootCommand for given option/argument which sets the Symbol parent:

static RootCommand Create(Symbol notCommand)
=> notCommand is Option option
? new RootCommand { option }

public void Add(T item)
{
item.AddParent(_parent);
_children.Add(item);
}

Since these methods were used only for testing and can be very easily replaced with explicit one liner I've decided to remove them:

- argument.Parse("x")
+ new RootCommand { argument }.Parse("x")

After doing that I've just moved CommandExtensions.Parse to Command to make it easier to discover (and follow BCL guidelines).

When I wanted to make Parser a static class, I've realized that we had two different default configs:

parser = new Parser(new CommandLineConfiguration(root));

parser = new CommandLineBuilder(root).UseDefaults().Build();

I've decided to use the same default config everywhere (the one that uses UseDefaults) and CompletionTests tests started failing, as they were not expecting the suggestions to contain --help and --version (global options added by default in the default config). I've just fixed the tests by passing the "simple" config in explicit way.

Another change that I've made was making Command a mandatory argument for CommandLineBuilder ctor:

public CommandLineBuilder(Command? rootCommand = null)
{
Command = rootCommand ?? new RootCommand();

I did that because it was making it easy to fall into a trap of not passing the existing command to the ctor and ending up with "disconnected" root command being used by the config:

RootCommand command = new()
{
    new Option<bool>(new[] { "--bool", "-b" }, "Bool option");
};

var builder = new CommandLineBuilder(); // not passing the command we already have, BUG
return builder.UseDefaults().Build().Invoke(args);

Since Command.Parse accepts CommandLineConfiguration and the class is not mutable yet, I wanted to make it easier to discover the builder type, so I've added:

public class CommandLineConfiguration
{
     public static CommandLineBuilder CreateBuilder(Command rootCommand);
}

I've also moved a bunch of extension methods to the types they were extending (BCL guideline).

I hope that all these changes are going to improve the discoverability of:

  • configuration and its customization
  • the separation of parsing and execution

fixes #2054
makes a progress toward #2057

public void SetupParser()
{
SetupRootCommand();
_testParser = new Parser(_rootCommand);
Copy link
Member Author

Choose a reason for hiding this comment

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

Parser is now static, we can't benchmark it's creation ;)

/// Provides extension methods for <see cref="CommandLineBuilder"/>.
/// </summary>
public static class CommandLineBuilderExtensions
public partial class CommandLineBuilder
Copy link
Member Author

Choose a reason for hiding this comment

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

to keep the diff smaller, I've decided to make the class partial rather than moving all the code to CommandLineBuilder

@@ -12,10 +12,10 @@ namespace System.CommandLine
/// <summary>
/// Enables composition of command line configurations.
/// </summary>
public class CommandLineBuilder
public partial class CommandLineBuilder
Copy link
Member Author

Choose a reason for hiding this comment

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

this type was building a parser, but it was called CommandLineBuilder. Now it could be called CommandLineConfigurationBuilder, but this name is long and I expect that in the near future we are going to start using Cli name prefixes for types, so I've decided to keep the name for now.

CommandLineConfiguration => CliConfiguration
CommandLineBuilder => CliConfigurationBuilder

Copy link
Contributor

@jonsequitur jonsequitur Feb 21, 2023

Choose a reason for hiding this comment

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

Our hope is to remove the CommandLineBuilder class in any case, yes?

Copy link
Member Author

Choose a reason for hiding this comment

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

Our hope is to remove the CommandLineBuilder class in any case, yes?

The tricky part is to expose only properties, so we can configure everything by just using the setters and not needing fluent APIs. I am currently working on implementing Directive symbol and it looks very promising in that regard.

@adamsitnik adamsitnik marked this pull request as ready for review February 20, 2023 14:44
adamsitnik and others added 2 commits February 20, 2023 22:42
Co-authored-by: Kevin B <Keboo@users.noreply.github.com>
* use name "arguments" rather than "args"
* don't accept null
* accept IReadOnlyList rather than array
# Conflicts:
#	src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt
#	src/System.CommandLine.Tests/Invocation/InvocationPipelineTests.cs
#	src/System.CommandLine.Tests/ResourceLocalizationTests.cs
#	src/System.CommandLine/Builder/CommandLineBuilder.cs
#	src/System.CommandLine/Builder/CommandLineBuilderExtensions.cs
#	src/System.CommandLine/Invocation/InvocationContext.cs
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.

Add Symbol.Parse, make Parser static
3 participants