Skip to content

Commit c63e231

Browse files
authored
Introduce Directive Symbol type (#2063)
* define Directive symbols * tokenization * parsing * configuration * address code review feedback
1 parent bf523f8 commit c63e231

28 files changed

+432
-400
lines changed

samples/RenderingPlayground/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static void Main(
5454

5555
var consoleRenderer = new ConsoleRenderer(
5656
console,
57-
mode: invocationContext.BindingContext.OutputMode(),
57+
mode: OutputMode.Auto,
5858
resetAfterRender: true);
5959

6060
switch (sample)

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_Hosting_api_is_not_changed.approved.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
System.CommandLine.Hosting
22
public static class DirectiveConfigurationExtensions
3-
public static Microsoft.Extensions.Configuration.IConfigurationBuilder AddCommandLineDirectives(this Microsoft.Extensions.Configuration.IConfigurationBuilder config, System.CommandLine.ParseResult commandline, System.String name)
3+
public static Microsoft.Extensions.Configuration.IConfigurationBuilder AddCommandLineDirectives(this Microsoft.Extensions.Configuration.IConfigurationBuilder config, System.CommandLine.ParseResult commandline, System.CommandLine.Directive directive)
44
public static class HostingExtensions
55
public static OptionsBuilder<TOptions> BindCommandLine<TOptions>(this OptionsBuilder<TOptions> optionsBuilder)
66
public static Microsoft.Extensions.Hosting.IHost GetHost(this System.CommandLine.Invocation.InvocationContext invocationContext)

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,11 +58,11 @@ System.CommandLine
5858
public class CommandLineBuilder
5959
.ctor(Command rootCommand)
6060
public Command Command { get; }
61+
public System.Collections.Generic.List<Directive> Directives { get; }
6162
public CommandLineBuilder AddMiddleware(System.CommandLine.Invocation.InvocationMiddleware middleware, System.CommandLine.Invocation.MiddlewareOrder order = Default)
6263
public CommandLineBuilder AddMiddleware(System.Action<System.CommandLine.Invocation.InvocationContext> onInvoke, System.CommandLine.Invocation.MiddlewareOrder order = Default)
6364
public CommandLineConfiguration Build()
6465
public CommandLineBuilder CancelOnProcessTermination(System.Nullable<System.TimeSpan> timeout = null)
65-
public CommandLineBuilder EnableDirectives(System.Boolean value = True)
6666
public CommandLineBuilder EnablePosixBundling(System.Boolean value = True)
6767
public CommandLineBuilder RegisterWithDotnetSuggest()
6868
public CommandLineBuilder UseDefaults()
@@ -81,8 +81,8 @@ System.CommandLine
8181
public CommandLineBuilder UseVersionOption(System.String name, System.String[] aliases)
8282
public class CommandLineConfiguration
8383
public static CommandLineBuilder CreateBuilder(Command rootCommand)
84-
.ctor(Command command, System.Boolean enablePosixBundling = True, System.Boolean enableDirectives = True, System.Boolean enableTokenReplacement = True, System.Collections.Generic.IReadOnlyList<System.CommandLine.Invocation.InvocationMiddleware> middlewarePipeline = null, System.Func<System.CommandLine.Binding.BindingContext,System.CommandLine.Help.HelpBuilder> helpBuilderFactory = null, System.CommandLine.Parsing.TryReplaceToken tokenReplacer = null)
85-
public System.Boolean EnableDirectives { get; }
84+
.ctor(Command command, System.Boolean enablePosixBundling = True, System.Boolean enableTokenReplacement = True, System.Collections.Generic.IReadOnlyList<System.CommandLine.Invocation.InvocationMiddleware> middlewarePipeline = null, System.Func<System.CommandLine.Binding.BindingContext,System.CommandLine.Help.HelpBuilder> helpBuilderFactory = null, System.CommandLine.Parsing.TryReplaceToken tokenReplacer = null)
85+
public System.Collections.Generic.IReadOnlyList<Directive> Directives { get; }
8686
public System.Boolean EnablePosixBundling { get; }
8787
public System.Boolean EnableTokenReplacement { get; }
8888
public Command RootCommand { get; }
@@ -101,11 +101,13 @@ System.CommandLine
101101
public static class ConsoleExtensions
102102
public static System.Void Write(this IConsole console, System.String value)
103103
public static System.Void WriteLine(this IConsole console, System.String value)
104-
public class DirectiveCollection, System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String,System.Collections.Generic.IEnumerable<System.String>>>, System.Collections.IEnumerable
104+
public class Directive : Symbol
105+
.ctor(System.String name, System.Action<System.CommandLine.Invocation.InvocationContext> syncHandler = null, System.Func<System.CommandLine.Invocation.InvocationContext,System.Threading.CancellationToken,System.Threading.Tasks.Task> asyncHandler = null)
106+
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
107+
public System.Void SetAsynchronousHandler(System.Func<System.CommandLine.Invocation.InvocationContext,System.Threading.CancellationToken,System.Threading.Tasks.Task> handler)
108+
public System.Void SetSynchronousHandler(System.Action<System.CommandLine.Invocation.InvocationContext> handler)
109+
public class EnvironmentVariablesDirective : Directive
105110
.ctor()
106-
public System.Boolean Contains(System.String name)
107-
public System.Collections.Generic.IEnumerator<System.Collections.Generic.KeyValuePair<System.String,System.Collections.Generic.IEnumerable<System.String>>> GetEnumerator()
108-
public System.Boolean TryGetValues(System.String name, ref System.Collections.Generic.IReadOnlyList<System.String> values)
109111
public static class Handler
110112
public static System.Void SetHandler(this Command command, System.Action<System.CommandLine.Invocation.InvocationContext> handle)
111113
public static System.Void SetHandler(this Command command, System.Action handle)
@@ -154,17 +156,19 @@ System.CommandLine
154156
public static Option<System.IO.DirectoryInfo> AcceptExistingOnly(this Option<System.IO.DirectoryInfo> option)
155157
public static Option<System.IO.FileSystemInfo> AcceptExistingOnly(this Option<System.IO.FileSystemInfo> option)
156158
public static Option<T> AcceptExistingOnly<T>(this Option<T> option)
159+
public class ParseDirective : Directive
160+
.ctor(System.Int32 errorExitCode = 1)
157161
public class ParseResult
158162
public System.CommandLine.Parsing.CommandResult CommandResult { get; }
159163
public CommandLineConfiguration Configuration { get; }
160-
public System.Collections.Generic.IReadOnlyDictionary<System.String,System.Collections.Generic.IReadOnlyList<System.String>> Directives { get; }
161164
public System.Collections.Generic.IReadOnlyList<System.CommandLine.Parsing.ParseError> Errors { get; }
162165
public System.CommandLine.Parsing.CommandResult RootCommandResult { get; }
163166
public System.Collections.Generic.IReadOnlyList<System.CommandLine.Parsing.Token> Tokens { get; }
164167
public System.Collections.Generic.IReadOnlyList<System.String> UnmatchedTokens { get; }
165168
public System.CommandLine.Parsing.ArgumentResult FindResultFor(Argument argument)
166169
public System.CommandLine.Parsing.CommandResult FindResultFor(Command command)
167170
public System.CommandLine.Parsing.OptionResult FindResultFor(Option option)
171+
public System.CommandLine.Parsing.DirectiveResult FindResultFor(Directive directive)
168172
public System.CommandLine.Parsing.SymbolResult FindResultFor(Symbol symbol)
169173
public System.CommandLine.Completions.CompletionContext GetCompletionContext()
170174
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.Nullable<System.Int32> position = null)
@@ -177,6 +181,8 @@ System.CommandLine
177181
public static System.String ExecutableName { get; }
178182
public static System.String ExecutablePath { get; }
179183
.ctor(System.String description = )
184+
public class SuggestDirective : Directive
185+
.ctor()
180186
public abstract class Symbol
181187
public System.String Description { get; set; }
182188
public System.Boolean IsHidden { get; set; }
@@ -337,6 +343,10 @@ System.CommandLine.Parsing
337343
public System.CommandLine.Command Command { get; }
338344
public Token Token { get; }
339345
public System.String ToString()
346+
public class DirectiveResult : SymbolResult
347+
public System.CommandLine.Directive Directive { get; }
348+
public Token Token { get; }
349+
public System.Collections.Generic.IReadOnlyList<System.String> Values { get; }
340350
public class OptionResult : SymbolResult
341351
public System.Boolean IsImplicit { get; }
342352
public System.CommandLine.Option Option { get; }
@@ -360,6 +370,7 @@ System.CommandLine.Parsing
360370
public ArgumentResult FindResultFor(System.CommandLine.Argument argument)
361371
public CommandResult FindResultFor(System.CommandLine.Command command)
362372
public OptionResult FindResultFor(System.CommandLine.Option option)
373+
public DirectiveResult FindResultFor(System.CommandLine.Directive directive)
363374
public T GetValue<T>(Argument<T> argument)
364375
public T GetValue<T>(Option<T> option)
365376
public class Token, System.IEquatable<Token>

src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_ParseResult.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ public IEnumerable<object> GenerateTestParseResults()
4343

4444
[Benchmark]
4545
[ArgumentsSource(nameof(GenerateTestInputs))]
46-
public IReadOnlyDictionary<string, IReadOnlyList<string>> ParseResult_Directives(string input)
47-
=> _configuration.RootCommand.Parse(input, _configuration).Directives;
46+
public ParseResult ParseResult_Directives(string input)
47+
=> _configuration.RootCommand.Parse(input, _configuration);
4848

4949
[Benchmark]
5050
[ArgumentsSource(nameof(GenerateTestParseResults))]

src/System.CommandLine.Hosting/DirectiveConfigurationExtensions.cs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.CommandLine.Parsing;
23
using System.Linq;
34

45
using Microsoft.Extensions.Configuration;
@@ -9,18 +10,21 @@ public static class DirectiveConfigurationExtensions
910
{
1011
public static IConfigurationBuilder AddCommandLineDirectives(
1112
this IConfigurationBuilder config, ParseResult commandline,
12-
string name)
13+
Directive directive)
1314
{
1415
if (commandline is null)
1516
throw new ArgumentNullException(nameof(commandline));
16-
if (name is null)
17-
throw new ArgumentNullException(nameof(name));
17+
if (directive is null)
18+
throw new ArgumentNullException(nameof(directive));
1819

19-
if (!commandline.Directives.TryGetValue(name, out var directives))
20+
if (commandline.FindResultFor(directive) is not DirectiveResult result
21+
|| result.Values.Count == 0)
22+
{
2023
return config;
24+
}
2125

2226
var kvpSeparator = new[] { '=' };
23-
return config.AddInMemoryCollection(directives.Select(s =>
27+
return config.AddInMemoryCollection(result.Values.Select(s =>
2428
{
2529
var parts = s.Split(kvpSeparator, count: 2);
2630
var key = parts[0];

src/System.CommandLine.Hosting/HostingExtensions.cs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ namespace System.CommandLine.Hosting
1313
{
1414
public static class HostingExtensions
1515
{
16-
private const string ConfigurationDirectiveName = "config";
17-
1816
public static CommandLineBuilder UseHost(this CommandLineBuilder builder,
1917
Func<string[], IHostBuilder> hostBuilderFactory,
20-
Action<IHostBuilder> configureHost = null) =>
21-
builder.AddMiddleware(async (invocation, cancellationToken, next) =>
18+
Action<IHostBuilder> configureHost = null)
19+
{
20+
Directive configurationDirective = new("config");
21+
builder.Directives.Add(configurationDirective);
22+
23+
return builder.AddMiddleware(async (invocation, cancellationToken, next) =>
2224
{
2325
var argsRemaining = invocation.ParseResult.UnmatchedTokens.ToArray();
2426
var hostBuilder = hostBuilderFactory?.Invoke(argsRemaining)
@@ -27,7 +29,7 @@ public static CommandLineBuilder UseHost(this CommandLineBuilder builder,
2729

2830
hostBuilder.ConfigureHostConfiguration(config =>
2931
{
30-
config.AddCommandLineDirectives(invocation.ParseResult, ConfigurationDirectiveName);
32+
config.AddCommandLineDirectives(invocation.ParseResult, configurationDirective);
3133
});
3234
hostBuilder.ConfigureServices(services =>
3335
{
@@ -50,6 +52,7 @@ public static CommandLineBuilder UseHost(this CommandLineBuilder builder,
5052

5153
await host.StopAsync(cancellationToken);
5254
});
55+
}
5356

5457
public static CommandLineBuilder UseHost(this CommandLineBuilder builder,
5558
Action<IHostBuilder> configureHost = null

src/System.CommandLine.Rendering/CommandLineBuilderExtensions.cs

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
using System.CommandLine.Binding;
5-
using System.Linq;
4+
using System.CommandLine.Parsing;
65

76
namespace System.CommandLine.Rendering
87
{
@@ -11,30 +10,33 @@ public static class CommandLineBuilderExtensions
1110
public static CommandLineBuilder UseAnsiTerminalWhenAvailable(
1211
this CommandLineBuilder builder)
1312
{
13+
Directive enableVtDirective = new ("enable-vt");
14+
Directive outputDirective = new ("output");
15+
builder.Directives.Add(enableVtDirective);
16+
builder.Directives.Add(outputDirective);
17+
1418
builder.AddMiddleware(context =>
1519
{
1620
var console = context.Console;
1721

1822
var terminal = console.GetTerminal(
19-
PreferVirtualTerminal(context.BindingContext),
20-
OutputMode(context.BindingContext));
23+
PreferVirtualTerminal(context.ParseResult, enableVtDirective),
24+
OutputMode(context.ParseResult, outputDirective));
2125

2226
context.Console = terminal ?? console;
2327
});
2428

2529
return builder;
2630
}
2731

28-
internal static bool PreferVirtualTerminal(
29-
this BindingContext context)
32+
private static bool PreferVirtualTerminal(ParseResult parseResult, Directive enableVtDirective)
3033
{
31-
if (context.ParseResult.Directives.TryGetValue(
32-
"enable-vt",
33-
out var trueOrFalse))
34+
if (parseResult.FindResultFor(enableVtDirective) is DirectiveResult result
35+
&& result.Values.Count == 1)
3436
{
35-
if (bool.TryParse(
36-
trueOrFalse.FirstOrDefault(),
37-
out var pvt))
37+
string trueOrFalse = result.Values[0];
38+
39+
if (bool.TryParse(trueOrFalse, out var pvt))
3840
{
3941
return pvt;
4042
}
@@ -43,15 +45,11 @@ internal static bool PreferVirtualTerminal(
4345
return true;
4446
}
4547

46-
public static OutputMode OutputMode(this BindingContext context)
48+
private static OutputMode OutputMode(ParseResult parseResult, Directive outputDirective)
4749
{
48-
if (context.ParseResult.Directives.TryGetValue(
49-
"output",
50-
out var modeString) &&
51-
Enum.TryParse<OutputMode>(
52-
modeString.FirstOrDefault(),
53-
true,
54-
out var mode))
50+
if (parseResult.FindResultFor(outputDirective) is DirectiveResult result
51+
&& result.Values.Count == 1
52+
&& Enum.TryParse<OutputMode>(result.Values[0], true, out var mode))
5553
{
5654
return mode;
5755
}

0 commit comments

Comments
 (0)