-
Notifications
You must be signed in to change notification settings - Fork 401
Move most of the built in middlewares out of middleware #2043
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
Changes from all commits
98311ec
4023a31
24d2d33
bea86a2
e54d965
19be15b
4053a9e
846233d
cbadeca
aadf6c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,33 @@ namespace System.CommandLine | |
/// </summary> | ||
public class CommandLineBuilder | ||
{ | ||
/// <inheritdoc cref="CommandLineConfiguration.EnablePosixBundling"/> | ||
internal bool EnablePosixBundling = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because it's a field, the naming convention would typically be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @jonsequitur for now I would prefer to stay with the current name, as I expect that next week we are going to introduce mutable |
||
|
||
/// <inheritdoc cref="CommandLineConfiguration.EnableTokenReplacement"/> | ||
internal bool EnableTokenReplacement = true; | ||
|
||
/// <inheritdoc cref="CommandLineConfiguration.ParseErrorReportingExitCode"/> | ||
internal int? ParseErrorReportingExitCode; | ||
|
||
/// <inheritdoc cref="CommandLineConfiguration.EnableDirectives"/> | ||
internal bool EnableDirectives = true; | ||
|
||
/// <inheritdoc cref="CommandLineConfiguration.EnableEnvironmentVariableDirective"/> | ||
internal bool EnableEnvironmentVariableDirective; | ||
|
||
/// <inheritdoc cref="CommandLineConfiguration.ParseDirectiveExitCode"/> | ||
internal int? ParseDirectiveExitCode; | ||
|
||
/// <inheritdoc cref="CommandLineConfiguration.EnableSuggestDirective"/> | ||
internal bool EnableSuggestDirective; | ||
|
||
/// <inheritdoc cref="CommandLineConfiguration.MaxLevenshteinDistance"/> | ||
internal int MaxLevenshteinDistance; | ||
|
||
/// <inheritdoc cref="CommandLineConfiguration.ExceptionHandler"/> | ||
internal Action<Exception, InvocationContext>? ExceptionHandler; | ||
|
||
// for every generic type with type argument being struct JIT needs to compile a dedicated version | ||
// (because each struct is of a different size) | ||
// that is why we don't use List<ValueTuple> for middleware | ||
|
@@ -33,19 +60,6 @@ public CommandLineBuilder(Command? rootCommand = null) | |
/// </summary> | ||
public Command Command { get; } | ||
|
||
/// <summary> | ||
/// Determines whether the parser recognizes command line directives. | ||
/// </summary> | ||
/// <seealso cref="DirectiveCollection"/> | ||
internal bool EnableDirectives { get; set; } = true; | ||
|
||
/// <summary> | ||
/// Determines whether the parser recognize and expands POSIX-style bundled options. | ||
/// </summary> | ||
internal bool EnablePosixBundling { get; set; } = true; | ||
|
||
internal bool EnableTokenReplacement { get; set; } = true; | ||
|
||
internal void CustomizeHelpLayout(Action<HelpContext> customize) => | ||
_customizeHelpBuilder = customize; | ||
|
||
|
@@ -68,19 +82,19 @@ HelpBuilder CreateHelpBuilder(BindingContext bindingContext) | |
} | ||
} | ||
|
||
internal HelpOption? HelpOption { get; set; } | ||
internal HelpOption? HelpOption; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fields: no need to JIT a property. A micro optimization for internal properites only |
||
|
||
internal VersionOption? VersionOption { get; set; } | ||
internal VersionOption? VersionOption; | ||
|
||
internal int? MaxHelpWidth { get; set; } | ||
internal int? MaxHelpWidth; | ||
|
||
internal LocalizationResources LocalizationResources | ||
{ | ||
get => _localizationResources ??= LocalizationResources.Instance; | ||
set => _localizationResources = value; | ||
} | ||
|
||
internal TryReplaceToken? TokenReplacer { get; set; } | ||
internal TryReplaceToken? TokenReplacer; | ||
|
||
/// <summary> | ||
/// Creates a parser based on the configuration of the command line builder. | ||
|
@@ -92,6 +106,12 @@ public Parser Build() => | |
enablePosixBundling: EnablePosixBundling, | ||
enableDirectives: EnableDirectives, | ||
enableTokenReplacement: EnableTokenReplacement, | ||
enableEnvironmentVariableDirective: EnableEnvironmentVariableDirective, | ||
parseDirectiveExitCode: ParseDirectiveExitCode, | ||
enableSuggestDirective: EnableSuggestDirective, | ||
parseErrorReportingExitCode: ParseErrorReportingExitCode, | ||
maxLevenshteinDistance: MaxLevenshteinDistance, | ||
exceptionHandler: ExceptionHandler, | ||
resources: LocalizationResources, | ||
middlewarePipeline: _middlewareList is null | ||
? Array.Empty<InvocationMiddleware>() | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -8,7 +8,6 @@ | |||||||||||||
using System.CommandLine.IO; | ||||||||||||||
using System.CommandLine.Parsing; | ||||||||||||||
using System.IO; | ||||||||||||||
using System.Linq; | ||||||||||||||
using System.Threading; | ||||||||||||||
using System.Threading.Tasks; | ||||||||||||||
using static System.Environment; | ||||||||||||||
|
@@ -230,27 +229,7 @@ await feature.EnsureRegistered(async () => | |||||||||||||
public static CommandLineBuilder UseEnvironmentVariableDirective( | ||||||||||||||
this CommandLineBuilder builder) | ||||||||||||||
{ | ||||||||||||||
builder.AddMiddleware((context, next) => | ||||||||||||||
{ | ||||||||||||||
if (context.ParseResult.Directives.TryGetValue("env", out var keyValuePairs)) | ||||||||||||||
{ | ||||||||||||||
for (var i = 0; i < keyValuePairs.Count; i++) | ||||||||||||||
{ | ||||||||||||||
var envDirective = keyValuePairs[i]; | ||||||||||||||
var components = envDirective.Split(new[] { '=' }, count: 2); | ||||||||||||||
var variable = components.Length > 0 ? components[0].Trim() : string.Empty; | ||||||||||||||
if (string.IsNullOrEmpty(variable) || components.Length < 2) | ||||||||||||||
{ | ||||||||||||||
continue; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
var value = components[1].Trim(); | ||||||||||||||
SetEnvironmentVariable(variable, value); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return next(context); | ||||||||||||||
}, MiddlewareOrderInternal.EnvironmentVariableDirective); | ||||||||||||||
builder.EnableEnvironmentVariableDirective = true; | ||||||||||||||
|
||||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
@@ -302,17 +281,7 @@ public static CommandLineBuilder UseExceptionHandler( | |||||||||||||
Action<Exception, InvocationContext>? onException = null, | ||||||||||||||
int? errorExitCode = null) | ||||||||||||||
{ | ||||||||||||||
builder.AddMiddleware(async (context, next) => | ||||||||||||||
{ | ||||||||||||||
try | ||||||||||||||
{ | ||||||||||||||
await next(context); | ||||||||||||||
} | ||||||||||||||
catch (Exception exception) | ||||||||||||||
{ | ||||||||||||||
(onException ?? Default)(exception, context); | ||||||||||||||
} | ||||||||||||||
}, MiddlewareOrderInternal.ExceptionHandler); | ||||||||||||||
builder.ExceptionHandler = onException ?? Default; | ||||||||||||||
|
||||||||||||||
return builder; | ||||||||||||||
|
||||||||||||||
|
@@ -397,14 +366,6 @@ internal static CommandLineBuilder UseHelp( | |||||||||||||
builder.HelpOption = helpOption; | ||||||||||||||
builder.Command.AddGlobalOption(helpOption); | ||||||||||||||
builder.MaxHelpWidth = maxWidth; | ||||||||||||||
|
||||||||||||||
builder.AddMiddleware(async (context, next) => | ||||||||||||||
{ | ||||||||||||||
if (!ShowHelp(context, builder.HelpOption)) | ||||||||||||||
{ | ||||||||||||||
await next(context); | ||||||||||||||
} | ||||||||||||||
}, MiddlewareOrderInternal.HelpOption); | ||||||||||||||
} | ||||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
@@ -475,19 +436,9 @@ public static CommandLineBuilder AddMiddleware( | |||||||||||||
/// <returns>The same instance of <see cref="CommandLineBuilder"/>.</returns> | ||||||||||||||
public static CommandLineBuilder UseParseDirective( | ||||||||||||||
this CommandLineBuilder builder, | ||||||||||||||
int? errorExitCode = null) | ||||||||||||||
int errorExitCode = 1) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO it's better to make the default value explicit. It comes from
|
||||||||||||||
{ | ||||||||||||||
builder.AddMiddleware(async (context, next) => | ||||||||||||||
{ | ||||||||||||||
if (context.ParseResult.Directives.ContainsKey("parse")) | ||||||||||||||
{ | ||||||||||||||
context.InvocationResult = ctx => ParseDirectiveResult.Apply(ctx, errorExitCode); | ||||||||||||||
} | ||||||||||||||
else | ||||||||||||||
{ | ||||||||||||||
await next(context); | ||||||||||||||
} | ||||||||||||||
}, MiddlewareOrderInternal.ParseDirective); | ||||||||||||||
builder.ParseDirectiveExitCode = errorExitCode; | ||||||||||||||
|
||||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
@@ -500,19 +451,9 @@ public static CommandLineBuilder UseParseDirective( | |||||||||||||
/// <returns>The same instance of <see cref="CommandLineBuilder"/>.</returns> | ||||||||||||||
public static CommandLineBuilder UseParseErrorReporting( | ||||||||||||||
this CommandLineBuilder builder, | ||||||||||||||
int? errorExitCode = null) | ||||||||||||||
int errorExitCode = 1) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||
{ | ||||||||||||||
builder.AddMiddleware(async (context, next) => | ||||||||||||||
{ | ||||||||||||||
if (context.ParseResult.Errors.Count > 0) | ||||||||||||||
{ | ||||||||||||||
context.InvocationResult = ctx => ParseErrorResult.Apply(ctx, errorExitCode); | ||||||||||||||
} | ||||||||||||||
else | ||||||||||||||
{ | ||||||||||||||
await next(context); | ||||||||||||||
} | ||||||||||||||
}, MiddlewareOrderInternal.ParseErrorReporting); | ||||||||||||||
builder.ParseErrorReportingExitCode = errorExitCode; | ||||||||||||||
|
||||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
@@ -526,28 +467,7 @@ public static CommandLineBuilder UseParseErrorReporting( | |||||||||||||
public static CommandLineBuilder UseSuggestDirective( | ||||||||||||||
this CommandLineBuilder builder) | ||||||||||||||
{ | ||||||||||||||
builder.AddMiddleware(async (context, next) => | ||||||||||||||
{ | ||||||||||||||
if (context.ParseResult.Directives.TryGetValue("suggest", out var values)) | ||||||||||||||
{ | ||||||||||||||
int position; | ||||||||||||||
|
||||||||||||||
if (values.FirstOrDefault() is { } positionString) | ||||||||||||||
{ | ||||||||||||||
position = int.Parse(positionString); | ||||||||||||||
} | ||||||||||||||
else | ||||||||||||||
{ | ||||||||||||||
position = context.ParseResult.CommandLineText?.Length ?? 0; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
context.InvocationResult = ctx => SuggestDirectiveResult.Apply(ctx, position); | ||||||||||||||
} | ||||||||||||||
else | ||||||||||||||
{ | ||||||||||||||
await next(context); | ||||||||||||||
} | ||||||||||||||
}, MiddlewareOrderInternal.SuggestDirective); | ||||||||||||||
builder.EnableSuggestDirective = true; | ||||||||||||||
|
||||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
@@ -562,17 +482,12 @@ public static CommandLineBuilder UseTypoCorrections( | |||||||||||||
this CommandLineBuilder builder, | ||||||||||||||
int maxLevenshteinDistance = 3) | ||||||||||||||
{ | ||||||||||||||
builder.AddMiddleware(async (context, next) => | ||||||||||||||
if (maxLevenshteinDistance <= 0) | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we should throw immediately rather than when doing first correction: command-line-api/src/System.CommandLine/Invocation/TypoCorrection.cs Lines 14 to 19 in 7030070
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not clear to me that this even needs to be configurable. I'd recommend hard-coding the distance. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's add it to a list of braking changes related to configuration and include in one PR with config re-design (somewhen next week?) |
||||||||||||||
{ | ||||||||||||||
if (context.ParseResult.CommandResult.Command.TreatUnmatchedTokensAsErrors && | ||||||||||||||
context.ParseResult.UnmatchedTokens.Count > 0) | ||||||||||||||
{ | ||||||||||||||
var typoCorrection = new TypoCorrection(maxLevenshteinDistance); | ||||||||||||||
throw new ArgumentOutOfRangeException(nameof(maxLevenshteinDistance)); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
typoCorrection.ProvideSuggestions(context.ParseResult, context.Console); | ||||||||||||||
} | ||||||||||||||
await next(context); | ||||||||||||||
}, MiddlewareOrderInternal.TypoCorrection); | ||||||||||||||
builder.MaxLevenshteinDistance = maxLevenshteinDistance; | ||||||||||||||
|
||||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
@@ -619,10 +534,8 @@ public static CommandLineBuilder UseVersionOption( | |||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
var versionOption = new VersionOption(builder); | ||||||||||||||
|
||||||||||||||
builder.VersionOption = versionOption; | ||||||||||||||
builder.Command.Options.Add(versionOption); | ||||||||||||||
builder.VersionOption = new (builder); | ||||||||||||||
builder.Command.Options.Add(builder.VersionOption); | ||||||||||||||
|
||||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
@@ -634,32 +547,15 @@ public static CommandLineBuilder UseVersionOption( | |||||||||||||
this CommandLineBuilder builder, | ||||||||||||||
params string[] aliases) | ||||||||||||||
{ | ||||||||||||||
var command = builder.Command; | ||||||||||||||
|
||||||||||||||
if (builder.VersionOption is not null) | ||||||||||||||
{ | ||||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
var versionOption = new VersionOption(aliases, builder); | ||||||||||||||
|
||||||||||||||
builder.VersionOption = versionOption; | ||||||||||||||
command.Options.Add(versionOption); | ||||||||||||||
builder.VersionOption = new (aliases, builder); | ||||||||||||||
builder.Command.Options.Add(builder.VersionOption); | ||||||||||||||
|
||||||||||||||
return builder; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private static bool ShowHelp( | ||||||||||||||
InvocationContext context, | ||||||||||||||
Option helpOption) | ||||||||||||||
{ | ||||||||||||||
if (context.ParseResult.FindResultFor(helpOption) is { }) | ||||||||||||||
{ | ||||||||||||||
context.InvocationResult = HelpResult.Apply; | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return false; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} |
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.
we were missing test coverage scenario for cases when users combine
[parse]
and--help
or--version