Skip to content

Commit ca3cbce

Browse files
committed
Upgrade System.CommandLine
1 parent ad5c8f8 commit ca3cbce

File tree

10 files changed

+69
-61
lines changed

10 files changed

+69
-61
lines changed

src/DotNext.MaintenanceServices/Maintenance/CommandLine/ApplicationMaintenanceCommand.Actions.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ public abstract class SynchronousAction : SynchronousCommandLineAction
2020

2121
/// <inheritdoc />
2222
public sealed override int Invoke(ParseResult result)
23-
=> result.Configuration is CommandContext context ? Invoke(context.Session, result) : -1;
23+
=> CommandContext.TryGetSession(result) is { } session
24+
? Invoke(session, result)
25+
: CommandContext.GenericErrorExitCode;
2426
}
25-
27+
2628
private sealed class DelegatingSynchronousAction(Action<IMaintenanceSession, ParseResult> action) : SynchronousAction
2729
{
2830
protected override int Invoke(IMaintenanceSession session, ParseResult result)
@@ -31,7 +33,7 @@ protected override int Invoke(IMaintenanceSession session, ParseResult result)
3133
return 0;
3234
}
3335
}
34-
36+
3537
/// <summary>
3638
/// Represents asynchronous command handler.
3739
/// </summary>
@@ -48,7 +50,9 @@ public abstract class AsynchronousAction : AsynchronousCommandLineAction
4850

4951
/// <inheritdoc />
5052
public override Task<int> InvokeAsync(ParseResult result, CancellationToken token = default)
51-
=> result.Configuration is CommandContext context ? InvokeAsync(context.Session, result, token) : Task.FromResult(-1);
53+
=> CommandContext.TryGetSession(result) is { } session
54+
? InvokeAsync(session, result, token)
55+
: Task.FromResult(CommandContext.GenericErrorExitCode);
5256
}
5357

5458
private sealed class DelegatingAsynchronousAction(Func<IMaintenanceSession, ParseResult, CancellationToken, ValueTask> action)

src/DotNext.MaintenanceServices/Maintenance/CommandLine/ApplicationMaintenanceCommand.HelpCommand.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ public static ApplicationMaintenanceCommand HelpCommand()
2525
file static class HelpOptionExtensions
2626
{
2727
public static int Invoke(this HelpOption option, ParseResult result)
28-
{
29-
var root = result.RootCommandResult;
30-
return result.Configuration.Invoke(option.Name);
31-
}
28+
=> result.RootCommandResult.Command is RootCommand root
29+
? root.Parse(option.Name).Invoke(result.InvocationConfiguration)
30+
: CommandContext.GenericErrorExitCode;
3231
}

src/DotNext.MaintenanceServices/Maintenance/CommandLine/ApplicationMaintenanceCommand.InteractiveCommand.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ public static ApplicationMaintenanceCommand EnterInteractiveModeCommand()
2020

2121
static Task EnterInteractiveMode(ParseResult result, CancellationToken token)
2222
{
23-
if (result.Configuration is CommandContext context)
24-
context.Session.IsInteractive = true;
23+
// TODO: Replace with null-conditional assignment
24+
if (CommandContext.TryGetSession(result) is { } session)
25+
session.IsInteractive = true;
2526

26-
return result.Configuration.Output.WriteLineAsync(
27+
return result.InvocationConfiguration.Output.WriteLineAsync(
2728
CommandResources.WelcomeMessage(RootCommand.ExecutableName).AsMemory(),
2829
token);
2930
}
@@ -42,8 +43,8 @@ public static ApplicationMaintenanceCommand LeaveInteractiveModeCommand()
4243

4344
command.SetAction(static result =>
4445
{
45-
if (result.Configuration is CommandContext context)
46-
context.Session.IsInteractive = false;
46+
if (CommandContext.TryGetSession(result) is { } session)
47+
session.IsInteractive = false;
4748
});
4849

4950
return command;

src/DotNext.MaintenanceServices/Maintenance/CommandLine/ApplicationMaintenanceCommand.Probe.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ Task InvokeAsync(ParseResult result, CancellationToken token) => provider.Invoke
7777
result.GetRequiredValue(successfulResponseOption),
7878
result.GetRequiredValue(failedResponseOption),
7979
result.GetRequiredValue(timeoutArg),
80-
result.Configuration.Output,
80+
result.InvocationConfiguration.Output,
8181
token
8282
);
8383
}

src/DotNext.MaintenanceServices/Maintenance/CommandLine/CommandContext.Directives.cs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,16 @@ namespace DotNext.Maintenance.CommandLine;
55

66
partial class CommandContext
77
{
8-
private sealed class DirectiveAction : SynchronousCommandLineAction
8+
private sealed class DirectiveAction(Action<CommandContext> action) : SynchronousCommandLineAction
99
{
10-
internal readonly Action<CommandContext> Action;
10+
internal Action<CommandContext> Action => action;
1111

12-
public DirectiveAction(Action<CommandContext> action)
13-
{
14-
Action = action;
15-
Terminating = false;
16-
}
12+
public override bool Terminating => false;
1713

1814
public override int Invoke(ParseResult parseResult)
1915
{
2016
int result;
21-
if (parseResult.Configuration is CommandContext context)
17+
if (parseResult.InvocationConfiguration is CommandContext context)
2218
{
2319
Action(context);
2420
result = 0;
@@ -38,9 +34,7 @@ private sealed class PrintErrorCodeDirective : Directive
3834

3935
public PrintErrorCodeDirective()
4036
: base(Name)
41-
{
42-
Action = new DirectiveAction(static context => context.printExitCode = true);
43-
}
37+
=> Action = new DirectiveAction(static context => context.printExitCode = true);
4438
}
4539

4640
private sealed class SuppressStandardOutputDirective : Directive
@@ -49,9 +43,7 @@ private sealed class SuppressStandardOutputDirective : Directive
4943

5044
public SuppressStandardOutputDirective()
5145
: base(Name)
52-
{
53-
Action = new DirectiveAction(static context => context.suppressOutputBuffer = true);
54-
}
46+
=> Action = new DirectiveAction(static context => context.suppressOutputBuffer = true);
5547
}
5648

5749
private sealed class SuppressStandardErrorDirective : Directive
@@ -60,9 +52,7 @@ private sealed class SuppressStandardErrorDirective : Directive
6052

6153
public SuppressStandardErrorDirective()
6254
: base(Name)
63-
{
64-
Action = new DirectiveAction(static context => context.suppressErrorBuffer = true);
65-
}
55+
=> Action = new DirectiveAction(static context => context.suppressErrorBuffer = true);
6656
}
6757

6858
internal static void RegisterDirectives(RootCommand root)

src/DotNext.MaintenanceServices/Maintenance/CommandLine/CommandContext.cs

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ namespace DotNext.Maintenance.CommandLine;
1212
/// <summary>
1313
/// Represents command invocation context.
1414
/// </summary>
15-
internal sealed partial class CommandContext : CommandLineConfiguration
15+
internal sealed partial class CommandContext : InvocationConfiguration
1616
{
17+
public const int GenericErrorExitCode = -1;
1718
private const int InvalidArgumentExitCode = 64; // EX_USAGE from sysexits.h
1819
private const int ForbiddenExitCode = 77; // EX_NOPERM
1920

@@ -24,10 +25,8 @@ internal sealed partial class CommandContext : CommandLineConfiguration
2425
private bool suppressOutputBuffer;
2526
private bool suppressErrorBuffer;
2627

27-
internal CommandContext(RootCommand root, IMaintenanceSession session)
28-
: base(root)
28+
internal CommandContext(IMaintenanceSession session)
2929
{
30-
Debug.Assert(root is not null);
3130
Debug.Assert(session is not null);
3231

3332
Session = session;
@@ -40,35 +39,37 @@ internal CommandContext(RootCommand root, IMaintenanceSession session)
4039
/// </summary>
4140
public IMaintenanceSession Session { get; }
4241

43-
internal async ValueTask<int> InvokeAsync(string input,
42+
internal async ValueTask<int> InvokeAsync(RootCommand root,
43+
string input,
44+
ParserConfiguration configuration,
4445
IAuthenticationHandler? authentication,
4546
AuthorizationCallback? authorization,
4647
CancellationToken token)
4748
{
4849
int exitCode;
49-
var result = Parse(input);
50+
var result = root.Parse(input, configuration);
5051

5152
if (result.Errors is { Count: > 0 } errors)
5253
{
53-
exitCode = ProcessParseErrors(errors, result.Tokens);
54+
exitCode = ProcessParseErrors(root, errors, result.Tokens);
5455
}
5556
else if (await AuthenticateAsync(Session, result, authentication, token).ConfigureAwait(false)
5657
&& await authorization.AuthorizeAsync(Session, result.CommandResult, token).ConfigureAwait(false)
5758
&& await AuthorizeAsync(Session, result.CommandResult, token).ConfigureAwait(false))
5859
{
59-
exitCode = await result.InvokeAsync(token).ConfigureAwait(false);
60+
exitCode = await result.InvokeAsync(this, token).ConfigureAwait(false);
6061
}
6162
else
6263
{
63-
exitCode = Forbid(result.Tokens);
64+
exitCode = Forbid(root, result.Tokens);
6465
}
6566

6667
return exitCode;
6768
}
6869

69-
private void ExecuteDirectives(IEnumerable<Token> tokens)
70+
private void ExecuteDirectives(RootCommand root, IEnumerable<Token> tokens)
7071
{
71-
var actions = from directive in (RootCommand as RootCommand)?.Directives ?? []
72+
var actions = from directive in root.Directives
7273
from token in tokens
7374
where Matches(token, directive)
7475
let action = (directive.Action as DirectiveAction)?.Action
@@ -141,9 +142,9 @@ internal void Exit(int exitCode, BufferWriter<char> output, BufferWriter<char> e
141142
}
142143
}
143144

144-
private int ProcessParseErrors(IReadOnlyList<ParseError> errors, IReadOnlyList<Token> tokens)
145+
private int ProcessParseErrors(RootCommand root, IReadOnlyList<ParseError> errors, IReadOnlyList<Token> tokens)
145146
{
146-
ExecuteDirectives(tokens);
147+
ExecuteDirectives(root, tokens);
147148

148149
foreach (var parseError in errors)
149150
{
@@ -153,13 +154,13 @@ private int ProcessParseErrors(IReadOnlyList<ParseError> errors, IReadOnlyList<T
153154
return InvalidArgumentExitCode;
154155
}
155156

156-
private int Forbid(IReadOnlyList<Token> tokens)
157+
private int Forbid(RootCommand root, IReadOnlyList<Token> tokens)
157158
{
158-
ExecuteDirectives(tokens);
159+
ExecuteDirectives(root, tokens);
159160
Error.WriteLine(CommandResources.AccessDenied);
160161
return ForbiddenExitCode;
161162
}
162163

163164
internal static IMaintenanceSession? TryGetSession(ParseResult result)
164-
=> (result.Configuration as CommandContext)?.Session;
165+
=> (result.InvocationConfiguration as CommandContext)?.Session;
165166
}

src/DotNext.MaintenanceServices/Maintenance/CommandLine/CommandLineMaintenanceInterfaceHost.cs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public sealed class CommandLineMaintenanceInterfaceHost : ApplicationMaintenance
2323
private readonly RootCommand root;
2424
private readonly IAuthenticationHandler? authentication;
2525
private readonly AuthorizationCallback? authorization;
26+
private readonly ParserConfiguration configuration;
2627

2728
/// <summary>
2829
/// Initializes a new host.
@@ -31,17 +32,22 @@ public sealed class CommandLineMaintenanceInterfaceHost : ApplicationMaintenance
3132
/// <param name="commands">A set of commands to be available for execution.</param>
3233
/// <param name="authentication">Optional authentication handler.</param>
3334
/// <param name="authorization">A set of global authorization rules to be applied to all commands.</param>
35+
/// <param name="configuration">Command-line parser configuration.</param>
3436
/// <param name="loggerFactory">Optional logger factory.</param>
3537
public CommandLineMaintenanceInterfaceHost(
3638
UnixDomainSocketEndPoint endPoint,
3739
IEnumerable<ApplicationMaintenanceCommand> commands,
3840
IAuthenticationHandler? authentication = null,
3941
AuthorizationCallback? authorization = null,
42+
ParserConfiguration? configuration = null,
4043
ILoggerFactory? loggerFactory = null)
4144
: base(endPoint, loggerFactory)
4245
{
43-
root = new RootCommand(RootCommand.ExecutableName + " Maintenance Interface");
44-
root.Action = new MyAction();
46+
root = new(RootCommand.ExecutableName + " Maintenance Interface")
47+
{
48+
Action = new MyAction()
49+
};
50+
4551
foreach (var subCommand in commands)
4652
root.Add(subCommand);
4753

@@ -52,15 +58,13 @@ public CommandLineMaintenanceInterfaceHost(
5258

5359
this.authentication = authentication;
5460
this.authorization = authorization;
61+
this.configuration = configuration ?? new();
5562
}
5663

5764
private sealed class MyAction : SynchronousCommandLineAction
5865
{
59-
public MyAction()
60-
{
61-
Terminating = false;
62-
}
63-
66+
public override bool Terminating => false;
67+
6468
public override int Invoke(ParseResult parseResult)
6569
{
6670
return 0;
@@ -74,10 +78,12 @@ protected override async ValueTask ExecuteCommandAsync(IMaintenanceSession sessi
7478
var error = new PoolingBufferWriter<char>(CharBufferAllocator) { Capacity = BufferSize };
7579
var outputWriter = output.AsTextWriter();
7680
var errorWriter = error.AsTextWriter();
77-
var context = new CommandContext(root, session) { Error = errorWriter, Output = outputWriter };
81+
var context = new CommandContext(session) { Error = errorWriter, Output = outputWriter };
7882
try
7983
{
80-
var exitCode = await context.InvokeAsync(command.ToString(), authentication, authorization, token).ConfigureAwait(false);
84+
var exitCode = await context
85+
.InvokeAsync(root, command.ToString(), configuration, authentication, authorization, token)
86+
.ConfigureAwait(false);
8187
context.Exit(exitCode, output, error);
8288
}
8389
catch (Exception e)

src/DotNext.MaintenanceServices/Maintenance/CommandLine/HostingServices.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.CommandLine;
12
using System.Diagnostics.CodeAnalysis;
23
using Microsoft.Extensions.DependencyInjection;
34
using Microsoft.Extensions.Hosting;
@@ -148,5 +149,11 @@ public static IServiceCollection UseApplicationMaintenanceInterface(this IServic
148149
=> services.AddSingleton<IHostedService, CommandLineMaintenanceInterfaceHost>(unixDomainSocketPath.CreateHost);
149150

150151
private static CommandLineMaintenanceInterfaceHost CreateHost(this string unixDomainSocketPath, IServiceProvider services)
151-
=> new(new(unixDomainSocketPath), services.GetServices<ApplicationMaintenanceCommand>(), authentication: services.GetService<IAuthenticationHandler>(), authorization: Delegate.Combine(services.GetServices<AuthorizationCallback>().ToArray()) as AuthorizationCallback, loggerFactory: services.GetService<ILoggerFactory>());
152+
=> new(
153+
new(unixDomainSocketPath),
154+
services.GetServices<ApplicationMaintenanceCommand>(),
155+
authentication: services.GetService<IAuthenticationHandler>(),
156+
authorization: Delegate.Combine(services.GetServices<AuthorizationCallback>().ToArray()) as AuthorizationCallback,
157+
configuration: services.GetService<ParserConfiguration>(),
158+
loggerFactory: services.GetService<ILoggerFactory>());
152159
}

src/DotNext.Tests/Maintenance/CommandLine/CommandLineMaintenanceInterfaceHostTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public static async Task UdsEndpointAuthentication()
7373
True(session.Identity.IsAuthenticated);
7474
var identity = IsType<LinuxUdsPeerIdentity>(session.Identity);
7575
Equal(Environment.UserName, identity.Name);
76-
result.Configuration.Output.Write(identity.ProcessId);
76+
result.InvocationConfiguration.Output.Write(identity.ProcessId);
7777
});
7878
});
7979
})
@@ -131,7 +131,7 @@ public static async Task PasswordAuthentication(string request, string response)
131131
{
132132
var x = result.GetRequiredValue(argX);
133133
var y = result.GetRequiredValue(argY);
134-
return new(result.Configuration.Output.WriteAsync((x + y).ToString().AsMemory(), token));
134+
return new(result.InvocationConfiguration.Output.WriteAsync((x + y).ToString().AsMemory(), token));
135135
});
136136
cmd.Authorization += static (user, _, _, _) => new(user.IsInRole("role2"));
137137
});

src/examples/CommandLineAMI/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ static void ConfigureAddCommand(ApplicationMaintenanceCommand command)
4040
{
4141
var x = result.GetRequiredValue(argX);
4242
var y = result.GetRequiredValue(argY);
43-
result.Configuration.Output.WriteLine(x + y);
43+
result.InvocationConfiguration.Output.WriteLine(x + y);
4444
});
4545
}
4646

@@ -66,7 +66,7 @@ static void ConfigureSubtractCommand(ApplicationMaintenanceCommand command)
6666
{
6767
var x = result.GetRequiredValue(argX);
6868
var y = result.GetRequiredValue(argY);
69-
result.Configuration.Output.WriteLine(x - y);
69+
result.InvocationConfiguration.Output.WriteLine(x - y);
7070
});
7171
}
7272

0 commit comments

Comments
 (0)