Skip to content

Add a way to create options and arguments for any type that implements IParseable/ISpanParseable #2658

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

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@

<ItemGroup>
<!-- Roslyn dependencies -->
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.0.1" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.14.0" />
<!-- Runtime dependencies -->
<PackageVersion Include="Microsoft.Bcl.Memory" Version="9.0.6" />
<!-- external dependencies -->
<PackageVersion Include="ApprovalTests" Version="7.0.0" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.1" />
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" />
<PackageVersion Include="AwesomeAssertions" Version="8.1.0" />
<PackageVersion Include="Microsoft.DotNet.PlatformAbstractions" Version="3.1.6" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@
public static Option<System.IO.DirectoryInfo> AcceptExistingOnly(this Option<System.IO.DirectoryInfo> option)
public static Option<System.IO.FileSystemInfo> AcceptExistingOnly(this Option<System.IO.FileSystemInfo> option)
public static Option<T> AcceptExistingOnly<T>(this Option<T> option)
public class ParsableArgument<T> : Argument<T>
.ctor(System.String name)
public class ParsableListArgument<T> : Argument<List<T>>
.ctor(System.String name)
public class ParsableListOption<T> : Option<List<T>>
.ctor(System.String name, System.String[] aliases)
public class ParsableOption<T> : Option<T>
.ctor(System.String name, System.String[] aliases)
public class ParserConfiguration
.ctor()
public System.Boolean EnablePosixBundling { get; set; }
Expand Down Expand Up @@ -140,6 +148,10 @@
.ctor(System.String description = )
public System.Collections.Generic.IList<Directive> Directives { get; }
public System.Void Add(Directive directive)
public class SpanParsableArgument<T> : Argument<T>
.ctor(System.String name)
public class SpanParsableOption<T> : Option<T>
.ctor(System.String name, System.String[] aliases)
public abstract class Symbol
public System.String Description { get; set; }
public System.Boolean Hidden { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,22 @@ namespace System.CommandLine.Benchmarks.CommandLine
public class Perf_Parser_CustomScenarios
{
private string _testSymbolsAsString;
private CliCommand _rootCommand;
private CliConfiguration _configuration;

private Command _rootCommand;

[GlobalSetup(Target = nameof(OneOptWithNestedCommand_Parse))]
public void SetupOneOptWithNestedCommand()
{
_rootCommand = new CliCommand("root_command");
var nestedCommand = new CliCommand("nested_command");
var option = new CliOption<int>("-opt1") { DefaultValueFactory = (_) => 123 };
_rootCommand = new Command("root_command");
var nestedCommand = new Command("nested_command");
var option = new Option<int>("-opt1") { DefaultValueFactory = (_) => 123 };
nestedCommand.Options.Add(option);
_rootCommand.Subcommands.Add(nestedCommand);

_testSymbolsAsString = "root_command nested_command -opt1 321";
_configuration = new CliConfiguration(_rootCommand);
}

[Benchmark]
public ParseResult OneOptWithNestedCommand_Parse()
=> _rootCommand.Parse(_testSymbolsAsString, _configuration);
=> _rootCommand.Parse(_testSymbolsAsString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,22 @@ namespace System.CommandLine.Benchmarks.CommandLine
[BenchmarkCategory(Categories.CommandLine)]
public class Perf_Parser_Directives_Suggest
{
private CliConfiguration _configuration;
public Command eatCommand;

[GlobalSetup]
public void Setup()
{
CliOption<string> fruitOption = new("--fruit");
Option<string> fruitOption = new("--fruit");
fruitOption.CompletionSources.Add("apple", "banana", "cherry");

CliOption<string> vegetableOption = new("--vegetable");
Option<string> vegetableOption = new("--vegetable");
vegetableOption.CompletionSources.Add("asparagus", "broccoli", "carrot");

var eatCommand = new CliCommand("eat")
eatCommand = new Command("eat")
{
fruitOption,
vegetableOption
};

_configuration = new CliConfiguration(eatCommand)
{
Directives = { new SuggestDirective() },
Output = System.IO.TextWriter.Null
};
}

[Params(
Expand All @@ -45,7 +39,7 @@ public void Setup()

[Benchmark]
public Task InvokeSuggest()
=> _configuration.InvokeAsync(TestCmdArgs);
=> eatCommand.Parse(TestCmdArgs).InvokeAsync();

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ namespace System.CommandLine.Benchmarks.CommandLine
public class Perf_Parser_NestedCommands
{
private string _testSymbolsAsString;
private CliCommand _rootCommand;
private CliConfiguration _configuration;
private Command _rootCommand;

/// <remarks>
/// 1 - cmd-root
Expand All @@ -31,15 +30,15 @@ public class Perf_Parser_NestedCommands
[Params(1, 2, 5)]
public int TestCommandsDepth;

private void GenerateTestNestedCommands(CliCommand parent, int depth, int countPerLevel)
private void GenerateTestNestedCommands(Command parent, int depth, int countPerLevel)
{
if (depth == 0)
return;

for (int i = 0; i < countPerLevel; i++)
{
string cmdName = $"{parent.Name}_{depth}.{i}";
CliCommand cmd = new(cmdName);
Command cmd = new(cmdName);
parent.Subcommands.Add(cmd);
GenerateTestNestedCommands(cmd, depth - 1, countPerLevel);
}
Expand All @@ -49,23 +48,22 @@ private void GenerateTestNestedCommands(CliCommand parent, int depth, int countP
public void SetupRootCommand()
{
string rootCommandName = "root";
var rootCommand = new CliCommand(rootCommandName);
var rootCommand = new Command(rootCommandName);
_testSymbolsAsString = rootCommandName;
GenerateTestNestedCommands(rootCommand, TestCommandsDepth, TestCommandsDepth);

// Choose only one path from the commands tree for the test arguments string
CliCommand currentCmd = rootCommand;
Command currentCmd = rootCommand;
while (currentCmd is not null && currentCmd.Subcommands.Count > 0)
{
currentCmd = currentCmd.Subcommands[0];
_testSymbolsAsString = string.Join(" ", _testSymbolsAsString, currentCmd.Name);
}

_rootCommand = rootCommand;
_configuration = new CliConfiguration(rootCommand);
}

[Benchmark]
public ParseResult Parser_Parse() => CliParser.Parse(_rootCommand, _testSymbolsAsString, _configuration);
public ParseResult Parser_Parse() => _rootCommand.Parse(_testSymbolsAsString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ namespace System.CommandLine.Benchmarks.CommandLine
[BenchmarkCategory(Categories.CommandLine)]
public class Perf_Parser_Options_Bare
{
private IEnumerable<CliOption> _testSymbols;
private IEnumerable<Option> _testSymbols;
private Command _testCommand;
private string _testSymbolsAsString;
private CliConfiguration _testConfiguration;

private IEnumerable<CliOption> GenerateTestOptions(int count, ArgumentArity arity)
private IEnumerable<Option> GenerateTestOptions(int count, ArgumentArity arity)
=> Enumerable.Range(0, count)
.Select(i =>
new CliOption<string>($"-option{i}")
new Option<string>($"-option{i}")
{
Arity = arity,
Description = $"Description for -option {i} ...."
Expand All @@ -49,7 +49,7 @@ public void SetupTestOptions()
}

[Benchmark]
public CliConfiguration ParserFromOptions_Ctor()
public Command ParserFromOptions_Ctor()
{
return _testSymbols.CreateConfiguration();
}
Expand All @@ -58,11 +58,11 @@ public CliConfiguration ParserFromOptions_Ctor()
public void SetupParserFromOptions_Parse()
{
var testSymbolsArr = GenerateTestOptions(TestSymbolsCount, ArgumentArity.Zero).ToArray();
_testConfiguration = testSymbolsArr.CreateConfiguration();
_testCommand = testSymbolsArr.CreateConfiguration();
_testSymbolsAsString = GenerateTestOptionsAsStringExpr(testSymbolsArr.Length);
}

[Benchmark]
public ParseResult ParserFromOptions_Parse() => _testConfiguration.Parse(_testSymbolsAsString);
public ParseResult ParserFromOptions_Parse() => _testCommand.Parse(_testSymbolsAsString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ namespace System.CommandLine.Benchmarks.CommandLine
public class Perf_Parser_Options_With_Arguments
{
private string _testSymbolsAsString;
private CliConfiguration _configuration;
private Command _command;

private IEnumerable<CliOption> GenerateTestOptions(int count, ArgumentArity arity)
private IEnumerable<Option> GenerateTestOptions(int count, ArgumentArity arity)
=> Enumerable.Range(0, count)
.Select(i => new CliOption<string>($"-option{i}")
.Select(i => new Option<string>($"-option{i}")
{
Arity = arity,
Description = $"Description for -option {i} ...."
Expand Down Expand Up @@ -54,11 +54,11 @@ private string GenerateTestOptionsWithArgumentsAsStringExpr(int optionsCount, in
public void SetupParserFromOptionsWithArguments_Parse()
{
var testSymbolsArr = GenerateTestOptions(TestOptionsCount, ArgumentArity.OneOrMore).ToArray();
_configuration = testSymbolsArr.CreateConfiguration();
_command = testSymbolsArr.CreateConfiguration();
_testSymbolsAsString = GenerateTestOptionsWithArgumentsAsStringExpr(testSymbolsArr.Length, TestArgumentsCount);
}

[Benchmark]
public ParseResult ParserFromOptionsWithArguments_Parse() => _configuration.Parse(_testSymbolsAsString);
public ParseResult ParserFromOptionsWithArguments_Parse() => _command.Parse(_testSymbolsAsString);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.CommandLine.Benchmarks.Helpers;
using System.CommandLine.Parsing;
using System.Linq;
using BenchmarkDotNet.Attributes;

namespace System.CommandLine.Benchmarks.CommandLine
{
/// <summary>
/// Measures the performance of <see cref="CliParser"/> when parsing options with arguments.
/// </summary>
[BenchmarkCategory(Categories.CommandLine)]
public class Perf_Parser_Options_With_Parsed_Arguments
{
private string _testSymbolsAsString;
private Command _command;

private IEnumerable<Option> GenerateTestOptions(int count)
=> Enumerable.Range(0, count)
.Select(i => new Option<DateOnly>($"-option{i}")
{
Arity = ArgumentArity.ExactlyOne,
Description = $"Description for -option {i} ...."
}
);

/// <remarks>
/// For optionsCount: 5, argumentsCount: 5 will return:
/// -option0 arg0..arg4 -option1 arg0..arg4 ... -option4 arg0..arg4
/// </remarks>
private string GenerateTestOptionsWithArgumentsAsStringExpr(int optionsCount)
{
return Enumerable
.Range(0, optionsCount)
.Select(i => $"-option{i} 2022/02/02 ")
.Aggregate("", (ac, next) => ac + " " + next);
}

[Params(1, 5, 20, 50, 100)]
public int TestOptionsCount;

[GlobalSetup(Target = nameof(ParserFromOptionsWithArguments_Parse))]
public void SetupParserFromOptionsWithArguments_Parse()
{
var testSymbolsArr = GenerateTestOptions(TestOptionsCount).ToArray();
_command = testSymbolsArr.CreateConfiguration();
_testSymbolsAsString = GenerateTestOptionsWithArgumentsAsStringExpr(testSymbolsArr.Length);
}

[Benchmark]
public ParseResult ParserFromOptionsWithArguments_Parse() => _command.Parse(_testSymbolsAsString);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.CommandLine.Benchmarks.Helpers;
using System.CommandLine.Parsing;
using System.Linq;
using BenchmarkDotNet.Attributes;

namespace System.CommandLine.Benchmarks.CommandLine
{
internal enum Kind { Default, Parseable, SpanParseable }

/// <summary>
/// Measures the performance of <see cref="CliParser"/> when parsing options with arguments.
/// </summary>
[BenchmarkCategory(Categories.CommandLine)]
public class Perf_Parser_Options_With_Parsed_Arguments_Variants
{
private string _testSymbolsAsString;
private Command _defaultCommand;
private RootCommand _parseableCommand;
private RootCommand _spanParseableCommand;

private IEnumerable<Option> GenerateTestOptions(int count, Kind kind)
=> Enumerable.Range(0, count)
.Select(i =>
kind switch
{
Kind.Default => new Option<DateOnly>($"-option{i}")
{
Arity = ArgumentArity.ExactlyOne,
Description = $"Description for -option {i} ...."
},
Kind.Parseable => new ParsableOption<DateOnly>($"-option{i}")
{
Arity = ArgumentArity.ExactlyOne,
Description = $"Description for -option {i} ...."
},
Kind.SpanParseable => new SpanParsableOption<DateOnly>($"-option{i}")
}
);

/// <remarks>
/// For optionsCount: 5, argumentsCount: 5 will return:
/// -option0 arg0..arg4 -option1 arg0..arg4 ... -option4 arg0..arg4
/// </remarks>
private string GenerateTestOptionsWithArgumentsAsStringExpr(int optionsCount)
{
return Enumerable
.Range(0, optionsCount)
.Select(i => $"-option{i} 2022/02/02 ")
.Aggregate("", (ac, next) => ac + " " + next);
}

[Params(1, 5, 20, 50, 100)]
public int TestOptionsCount;

[GlobalSetup]
public void SetupParserFromOptionsWithArguments_Parse()
{
_defaultCommand = GenerateTestOptions(TestOptionsCount, Kind.Default).ToArray().CreateConfiguration();
_parseableCommand = GenerateTestOptions(TestOptionsCount, Kind.Parseable).ToArray().CreateConfiguration();
_spanParseableCommand = GenerateTestOptions(TestOptionsCount, Kind.SpanParseable).ToArray().CreateConfiguration();
_testSymbolsAsString = GenerateTestOptionsWithArgumentsAsStringExpr(TestOptionsCount);
}

[Benchmark]
public ParseResult Default() => _defaultCommand.Parse(_testSymbolsAsString);

[Benchmark]
public ParseResult Parseable() => _parseableCommand.Parse(_testSymbolsAsString);

[Benchmark]
public ParseResult SpanParseable() => _spanParseableCommand.Parse(_testSymbolsAsString);
}
}
Loading