diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index b3fa77d5cb5c..4e70432bb9a6 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -253,13 +253,13 @@
2b6ab8d727ce73a78bcbf026ac75ea8a7c804daf
-
+
https://github.com/dotnet/command-line-api
- 8374d5fca634a93458c84414b1604c12f765d1ab
+ 02fe27cd6a9b001c8feb7938e6ef4b3799745759
-
+
https://github.com/dotnet/command-line-api
- 8374d5fca634a93458c84414b1604c12f765d1ab
+ 02fe27cd6a9b001c8feb7938e6ef4b3799745759
diff --git a/eng/Versions.props b/eng/Versions.props
index f2cd910fe14c..a63b3ca95fc0 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -39,7 +39,7 @@
7.0.2
8.0.0-preview.6.23307.1
4.6.0
- 2.0.0-beta4.22564.1
+ 2.0.0-beta4.23307.1
1.0.0-preview.6.23206.1
3.2.2146
diff --git a/src/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ValidatePackage.cs b/src/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ValidatePackage.cs
index 2b188c5b9d5d..5eda083935cc 100644
--- a/src/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ValidatePackage.cs
+++ b/src/ApiCompat/Microsoft.DotNet.ApiCompat.Shared/ValidatePackage.cs
@@ -22,7 +22,7 @@ public static void Run(Func logFactory,
bool enableRuleAttributesMustMatch,
string[]? excludeAttributesFiles,
bool enableRuleCannotChangeParameterName,
- string packagePath,
+ string? packagePath,
bool runApiCompat,
bool enableStrictModeForCompatibleTfms,
bool enableStrictModeForCompatibleFrameworksInPackage,
diff --git a/src/ApiCompat/Microsoft.DotNet.ApiCompat.Tool/Program.cs b/src/ApiCompat/Microsoft.DotNet.ApiCompat.Tool/Program.cs
index fd5d65184279..1f4089b2ea34 100644
--- a/src/ApiCompat/Microsoft.DotNet.ApiCompat.Tool/Program.cs
+++ b/src/ApiCompat/Microsoft.DotNet.ApiCompat.Tool/Program.cs
@@ -4,12 +4,10 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
-using System.CommandLine.Invocation;
using System.CommandLine.Parsing;
using System.IO;
using Microsoft.DotNet.ApiCompatibility.Logging;
using Microsoft.DotNet.ApiSymbolExtensions.Logging;
-using Microsoft.DotNet.PackageValidation;
using NuGet.Frameworks;
namespace Microsoft.DotNet.ApiCompat.Tool
@@ -22,137 +20,168 @@ static int Main(string[] args)
// Important: Keep parameters exposed in sync with the msbuild task frontend.
// Global options
- Option generateSuppressionFileOption = new("--generate-suppression-file",
- "If true, generates a compatibility suppression file.");
- Option suppressionFilesOption = new("--suppression-file",
- "The path to one or more suppression files to read from.")
+ CliOption generateSuppressionFileOption = new("--generate-suppression-file")
{
- AllowMultipleArgumentsPerToken= true,
+ Description = "If true, generates a compatibility suppression file.",
+ Recursive = true
+ };
+ CliOption suppressionFilesOption = new("--suppression-file")
+ {
+ Description = "The path to one or more suppression files to read from.",
+ AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore,
- ArgumentHelpName = "file"
+ HelpName = "file",
+ Recursive = true
+ };
+ CliOption suppressionOutputFileOption = new("--suppression-output-file")
+ {
+ Description = "The path to a suppression file to write to when --generate-suppression-file is true.",
+ Recursive = true
+ };
+ CliOption noWarnOption = new("--noWarn")
+ {
+ Description = "A NoWarn string that allows to disable specific rules.",
+ Recursive = true
+ };
+ CliOption respectInternalsOption = new("--respect-internals")
+ {
+ Description = "If true, includes both internal and public API.",
+ Recursive = true
+ };
+ CliOption roslynAssembliesPathOption = new("--roslyn-assemblies-path")
+ {
+ Description = "The path to the directory that contains the Microsoft.CodeAnalysis assemblies.",
+ HelpName = "file",
+ Recursive = true
+ };
+ CliOption verbosityOption = new("--verbosity", "-v")
+ {
+ Description = "Controls the log level verbosity. Allowed values are high, normal, and low.",
+ DefaultValueFactory = _ => MessageImportance.High,
+ Recursive = true
+ };
+ CliOption enableRuleAttributesMustMatchOption = new("--enable-rule-attributes-must-match")
+ {
+ Description = "If true, enables rule to check that attributes match.",
+ Recursive = true
+ };
+ CliOption excludeAttributesFilesOption = new("--exclude-attributes-file")
+ {
+ Description = "The path to one or more attribute exclusion files with types in DocId format.",
+ Recursive = true
};
- Option suppressionOutputFileOption = new("--suppression-output-file",
- "The path to a suppression file to write to when --generate-suppression-file is true.");
- Option noWarnOption = new("--noWarn",
- "A NoWarn string that allows to disable specific rules.");
- Option respectInternalsOption = new("--respect-internals",
- "If true, includes both internal and public API.");
- Option roslynAssembliesPathOption = new("--roslyn-assemblies-path",
- "The path to the directory that contains the Microsoft.CodeAnalysis assemblies.")
- {
- ArgumentHelpName = "file"
+ CliOption enableRuleCannotChangeParameterNameOption = new("--enable-rule-cannot-change-parameter-name")
+ {
+ Description = "If true, enables rule to check that the parameter names between public methods do not change.",
+ Recursive = true
};
- Option verbosityOption = new(new string[] { "--verbosity", "-v" },
- "Controls the log level verbosity. Allowed values are high, normal, and low.");
- verbosityOption.SetDefaultValue(MessageImportance.High);
- Option enableRuleAttributesMustMatchOption = new("--enable-rule-attributes-must-match",
- "If true, enables rule to check that attributes match.");
- Option excludeAttributesFilesOption = new("--exclude-attributes-file",
- "The path to one or more attribute exclusion files with types in DocId format.");
- Option enableRuleCannotChangeParameterNameOption = new("--enable-rule-cannot-change-parameter-name",
- "If true, enables rule to check that the parameter names between public methods do not change.");
// Root command
- Option leftAssembliesOption = new(new string[] { "--left-assembly", "--left", "-l" },
- description: "The path to one or more assemblies that serve as the left side to compare.",
- parseArgument: ParseAssemblyArgument)
+ CliOption leftAssembliesOption = new("--left-assembly", "--left", "-l")
{
+ Description = "The path to one or more assemblies that serve as the left side to compare.",
+ CustomParser = ParseAssemblyArgument,
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.OneOrMore,
- IsRequired = true
+ Required = true
};
- Option rightAssembliesOption = new(new string[] { "--right-assembly", "--right", "-r" },
- description: "The path to one or more assemblies that serve as the right side to compare.",
- parseArgument: ParseAssemblyArgument)
+ CliOption rightAssembliesOption = new("--right-assembly", "--right", "-r")
{
+ Description = "The path to one or more assemblies that serve as the right side to compare.",
+ CustomParser = ParseAssemblyArgument,
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.OneOrMore,
- IsRequired = true
+ Required = true
+ };
+ CliOption strictModeOption = new("--strict-mode")
+ {
+ Description = "If true, performs api compatibility checks in strict mode"
};
- Option strictModeOption = new("--strict-mode",
- "If true, performs api compatibility checks in strict mode");
- Option leftAssembliesReferencesOption = new(new string[] { "--left-assembly-references", "--lref" },
- description: "Paths to assembly references or the underlying directories for a given left. Values must be separated by commas: ','.",
- parseArgument: ParseAssemblyReferenceArgument)
+ CliOption leftAssembliesReferencesOption = new("--left-assembly-references", "--lref")
+
{
+ Description = "Paths to assembly references or the underlying directories for a given left. Values must be separated by commas: ','.",
+ CustomParser = ParseAssemblyReferenceArgument,
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore,
- ArgumentHelpName = "file1,file2,..."
+ HelpName = "file1,file2,..."
};
- Option rightAssembliesReferencesOption = new(new string[] { "--right-assembly-references", "--rref" },
- description: "Paths to assembly references or the underlying directories for a given right. Values must be separated by commas: ','.",
- parseArgument: ParseAssemblyReferenceArgument)
+ CliOption rightAssembliesReferencesOption = new("--right-assembly-references", "--rref")
{
+ Description = "Paths to assembly references or the underlying directories for a given right. Values must be separated by commas: ','.",
+ CustomParser = ParseAssemblyReferenceArgument,
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore,
- ArgumentHelpName = "file1,file2,..."
+ HelpName = "file1,file2,..."
+ };
+ CliOption createWorkItemPerAssemblyOption = new("--create-work-item-per-assembly")
+ {
+ Description = "If true, enqueues a work item per passed in left and right assembly."
};
- Option createWorkItemPerAssemblyOption = new("--create-work-item-per-assembly",
- "If true, enqueues a work item per passed in left and right assembly.");
- Option<(string, string)[]?> leftAssembliesTransformationPatternOption = new("--left-assemblies-transformation-pattern",
- description: "A transformation pattern for the left side assemblies.",
- parseArgument: ParseTransformationPattern)
+ CliOption<(string, string)[]?> leftAssembliesTransformationPatternOption = new("--left-assemblies-transformation-pattern")
{
+ Description = "A transformation pattern for the left side assemblies.",
+ CustomParser = ParseTransformationPattern,
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore
};
- Option<(string, string)[]?> rightAssembliesTransformationPatternOption = new("--right-assemblies-transformation-pattern",
- description: "A transformation pattern for the right side assemblies.",
- parseArgument: ParseTransformationPattern)
+ CliOption<(string, string)[]?> rightAssembliesTransformationPatternOption = new("--right-assemblies-transformation-pattern")
{
+ Description = "A transformation pattern for the right side assemblies.",
+ CustomParser = ParseTransformationPattern,
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore
};
- RootCommand rootCommand = new("Microsoft.DotNet.ApiCompat v" + Environment.Version.ToString(2))
+ CliRootCommand rootCommand = new("Microsoft.DotNet.ApiCompat v" + Environment.Version.ToString(2))
{
TreatUnmatchedTokensAsErrors = true
};
- rootCommand.AddGlobalOption(generateSuppressionFileOption);
- rootCommand.AddGlobalOption(suppressionFilesOption);
- rootCommand.AddGlobalOption(suppressionOutputFileOption);
- rootCommand.AddGlobalOption(noWarnOption);
- rootCommand.AddGlobalOption(respectInternalsOption);
- rootCommand.AddGlobalOption(roslynAssembliesPathOption);
- rootCommand.AddGlobalOption(verbosityOption);
- rootCommand.AddGlobalOption(enableRuleAttributesMustMatchOption);
- rootCommand.AddGlobalOption(excludeAttributesFilesOption);
- rootCommand.AddGlobalOption(enableRuleCannotChangeParameterNameOption);
-
- rootCommand.AddOption(leftAssembliesOption);
- rootCommand.AddOption(rightAssembliesOption);
- rootCommand.AddOption(strictModeOption);
- rootCommand.AddOption(leftAssembliesReferencesOption);
- rootCommand.AddOption(rightAssembliesReferencesOption);
- rootCommand.AddOption(createWorkItemPerAssemblyOption);
- rootCommand.AddOption(leftAssembliesTransformationPatternOption);
- rootCommand.AddOption(rightAssembliesTransformationPatternOption);
-
- rootCommand.SetHandler((InvocationContext context) =>
+ rootCommand.Options.Add(generateSuppressionFileOption);
+ rootCommand.Options.Add(suppressionFilesOption);
+ rootCommand.Options.Add(suppressionOutputFileOption);
+ rootCommand.Options.Add(noWarnOption);
+ rootCommand.Options.Add(respectInternalsOption);
+ rootCommand.Options.Add(roslynAssembliesPathOption);
+ rootCommand.Options.Add(verbosityOption);
+ rootCommand.Options.Add(enableRuleAttributesMustMatchOption);
+ rootCommand.Options.Add(excludeAttributesFilesOption);
+ rootCommand.Options.Add(enableRuleCannotChangeParameterNameOption);
+
+ rootCommand.Options.Add(leftAssembliesOption);
+ rootCommand.Options.Add(rightAssembliesOption);
+ rootCommand.Options.Add(strictModeOption);
+ rootCommand.Options.Add(leftAssembliesReferencesOption);
+ rootCommand.Options.Add(rightAssembliesReferencesOption);
+ rootCommand.Options.Add(createWorkItemPerAssemblyOption);
+ rootCommand.Options.Add(leftAssembliesTransformationPatternOption);
+ rootCommand.Options.Add(rightAssembliesTransformationPatternOption);
+
+ rootCommand.SetAction((ParseResult parseResult) =>
{
// If a roslyn assemblies path isn't provided, use the compiled against version from a subfolder.
- string roslynAssembliesPath = context.ParseResult.GetValue(roslynAssembliesPathOption) ??
+ string roslynAssembliesPath = parseResult.GetValue(roslynAssembliesPathOption) ??
Path.Combine(AppContext.BaseDirectory, "codeanalysis");
RoslynResolver roslynResolver = RoslynResolver.Register(roslynAssembliesPath);
- MessageImportance verbosity = context.ParseResult.GetValue(verbosityOption);
- bool generateSuppressionFile = context.ParseResult.GetValue(generateSuppressionFileOption);
- string[]? suppressionFiles = context.ParseResult.GetValue(suppressionFilesOption);
- string? suppressionOutputFile = context.ParseResult.GetValue(suppressionOutputFileOption);
- string? noWarn = context.ParseResult.GetValue(noWarnOption);
- bool respectInternals = context.ParseResult.GetValue(respectInternalsOption);
- bool enableRuleAttributesMustMatch = context.ParseResult.GetValue(enableRuleAttributesMustMatchOption);
- string[]? excludeAttributesFiles = context.ParseResult.GetValue(excludeAttributesFilesOption);
- bool enableRuleCannotChangeParameterName = context.ParseResult.GetValue(enableRuleCannotChangeParameterNameOption);
-
- string[] leftAssemblies = context.ParseResult.GetValue(leftAssembliesOption)!;
- string[] rightAssemblies = context.ParseResult.GetValue(rightAssembliesOption)!;
- bool strictMode = context.ParseResult.GetValue(strictModeOption);
- string[][]? leftAssembliesReferences = context.ParseResult.GetValue(leftAssembliesReferencesOption);
- string[][]? rightAssembliesReferences = context.ParseResult.GetValue(rightAssembliesReferencesOption);
- bool createWorkItemPerAssembly = context.ParseResult.GetValue(createWorkItemPerAssemblyOption);
- (string, string)[]? leftAssembliesTransformationPattern = context.ParseResult.GetValue(leftAssembliesTransformationPatternOption);
- (string, string)[]? rightAssembliesTransformationPattern = context.ParseResult.GetValue(rightAssembliesTransformationPatternOption);
+ MessageImportance verbosity = parseResult.GetValue(verbosityOption);
+ bool generateSuppressionFile = parseResult.GetValue(generateSuppressionFileOption);
+ string[]? suppressionFiles = parseResult.GetValue(suppressionFilesOption);
+ string? suppressionOutputFile = parseResult.GetValue(suppressionOutputFileOption);
+ string? noWarn = parseResult.GetValue(noWarnOption);
+ bool respectInternals = parseResult.GetValue(respectInternalsOption);
+ bool enableRuleAttributesMustMatch = parseResult.GetValue(enableRuleAttributesMustMatchOption);
+ string[]? excludeAttributesFiles = parseResult.GetValue(excludeAttributesFilesOption);
+ bool enableRuleCannotChangeParameterName = parseResult.GetValue(enableRuleCannotChangeParameterNameOption);
+
+ string[] leftAssemblies = parseResult.GetValue(leftAssembliesOption)!;
+ string[] rightAssemblies = parseResult.GetValue(rightAssembliesOption)!;
+ bool strictMode = parseResult.GetValue(strictModeOption);
+ string[][]? leftAssembliesReferences = parseResult.GetValue(leftAssembliesReferencesOption);
+ string[][]? rightAssembliesReferences = parseResult.GetValue(rightAssembliesReferencesOption);
+ bool createWorkItemPerAssembly = parseResult.GetValue(createWorkItemPerAssemblyOption);
+ (string, string)[]? leftAssembliesTransformationPattern = parseResult.GetValue(leftAssembliesTransformationPatternOption);
+ (string, string)[]? rightAssembliesTransformationPattern = parseResult.GetValue(rightAssembliesTransformationPatternOption);
Func logFactory = (suppressionEngine) => new(suppressionEngine, verbosity);
ValidateAssemblies.Run(logFactory,
@@ -177,83 +206,91 @@ static int Main(string[] args)
});
// Package command
- Argument packageArgument = new("--package",
- "The path to the package that should be validated")
+ CliArgument packageArgument = new("--package")
{
+ Description = "The path to the package that should be validated",
Arity = ArgumentArity.ExactlyOne
};
- Option runtimeGraphOption = new("--runtime-graph",
- "The path to the runtime graph to read from.")
+ CliOption runtimeGraphOption = new("--runtime-graph")
+ {
+ Description = "The path to the runtime graph to read from.",
+ HelpName = "json"
+ };
+ CliOption runApiCompatOption = new("--run-api-compat")
+ {
+ Description = "If true, performs api compatibility checks on the package assets.",
+ DefaultValueFactory = _ => true
+ };
+ CliOption enableStrictModeForCompatibleTfmsOption = new("--enable-strict-mode-for-compatible-tfms")
+ {
+ Description = "Validates api compatibility in strict mode for contract and implementation assemblies for all compatible target frameworks."
+ };
+ CliOption enableStrictModeForCompatibleFrameworksInPackageOption = new("--enable-strict-mode-for-compatible-frameworks-in-package")
+ {
+ Description = "Validates api compatibility in strict mode for assemblies that are compatible based on their target framework."
+ };
+ CliOption enableStrictModeForBaselineValidationOption = new("--enable-strict-mode-for-baseline-validation")
{
- ArgumentHelpName = "json"
+ Description = "Validates api compatibility in strict mode for package baseline checks."
};
- Option runApiCompatOption = new("--run-api-compat",
- "If true, performs api compatibility checks on the package assets.");
- runApiCompatOption.SetDefaultValue(true);
- Option enableStrictModeForCompatibleTfmsOption = new("--enable-strict-mode-for-compatible-tfms",
- "Validates api compatibility in strict mode for contract and implementation assemblies for all compatible target frameworks.");
- Option enableStrictModeForCompatibleFrameworksInPackageOption = new("--enable-strict-mode-for-compatible-frameworks-in-package",
- "Validates api compatibility in strict mode for assemblies that are compatible based on their target framework.");
- Option enableStrictModeForBaselineValidationOption = new("--enable-strict-mode-for-baseline-validation",
- "Validates api compatibility in strict mode for package baseline checks.");
- Option baselinePackageOption = new("--baseline-package",
- "The path to a baseline package to validate against the current package.")
- {
- ArgumentHelpName = "nupkg"
+ CliOption baselinePackageOption = new("--baseline-package")
+ {
+ Description = "The path to a baseline package to validate against the current package.",
+ HelpName = "nupkg"
};
- Option>?> packageAssemblyReferencesOption = new("--package-assembly-references",
- description: "Paths to assembly references or their underlying directories for a specific target framework in the package. Values must be separated by commas: ','.",
- parseArgument: ParsePackageAssemblyReferenceArgument)
+ CliOption>?> packageAssemblyReferencesOption = new("--package-assembly-references")
{
+ Description = "Paths to assembly references or their underlying directories for a specific target framework in the package. Values must be separated by commas: ','.",
+ CustomParser = ParsePackageAssemblyReferenceArgument,
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore,
- ArgumentHelpName = "tfm=file1,file2,..."
+ HelpName = "tfm=file1,file2,..."
};
- Option>?> baselinePackageAssemblyReferencesOption = new("--baseline-package-assembly-references",
- description: "Paths to assembly references or their underlying directories for a specific target framework in the baseline package. Values must be separated by commas: ','.",
- parseArgument: ParsePackageAssemblyReferenceArgument)
+ CliOption>?> baselinePackageAssemblyReferencesOption = new("--baseline-package-assembly-references")
{
+ Description = "Paths to assembly references or their underlying directories for a specific target framework in the baseline package. Values must be separated by commas: ','.",
+ CustomParser = ParsePackageAssemblyReferenceArgument,
AllowMultipleArgumentsPerToken = true,
Arity = ArgumentArity.ZeroOrMore,
- ArgumentHelpName = "tfm=file1,file2,..."
+ HelpName = "tfm=file1,file2,..."
};
- Command packageCommand = new("package", "Validates the compatibility of package assets");
- packageCommand.AddArgument(packageArgument);
- packageCommand.AddOption(runtimeGraphOption);
- packageCommand.AddOption(runApiCompatOption);
- packageCommand.AddOption(enableStrictModeForCompatibleTfmsOption);
- packageCommand.AddOption(enableStrictModeForCompatibleFrameworksInPackageOption);
- packageCommand.AddOption(enableStrictModeForBaselineValidationOption);
- packageCommand.AddOption(baselinePackageOption);
- packageCommand.AddOption(packageAssemblyReferencesOption);
- packageCommand.AddOption(baselinePackageAssemblyReferencesOption);
- packageCommand.SetHandler((InvocationContext context) =>
+ CliCommand packageCommand = new("package", "Validates the compatibility of package assets");
+ packageCommand.Arguments.Add(packageArgument);
+ packageCommand.Options.Add(runtimeGraphOption);
+ packageCommand.Options.Add(runApiCompatOption);
+ packageCommand.Options.Add(enableStrictModeForCompatibleTfmsOption);
+ packageCommand.Options.Add(enableStrictModeForCompatibleFrameworksInPackageOption);
+ packageCommand.Options.Add(enableStrictModeForBaselineValidationOption);
+ packageCommand.Options.Add(baselinePackageOption);
+ packageCommand.Options.Add(packageAssemblyReferencesOption);
+ packageCommand.Options.Add(baselinePackageAssemblyReferencesOption);
+ packageCommand.SetAction((ParseResult parseResult) =>
{
// If a roslyn assemblies path isn't provided, use the compiled against version from a subfolder.
- string roslynAssembliesPath = context.ParseResult.GetValue(roslynAssembliesPathOption) ??
+ string roslynAssembliesPath = parseResult.GetValue(roslynAssembliesPathOption) ??
Path.Combine(AppContext.BaseDirectory, "codeanalysis");
RoslynResolver roslynResolver = RoslynResolver.Register(roslynAssembliesPath);
- MessageImportance verbosity = context.ParseResult.GetValue(verbosityOption);
- bool generateSuppressionFile = context.ParseResult.GetValue(generateSuppressionFileOption);
- string[]? suppressionFiles = context.ParseResult.GetValue(suppressionFilesOption);
- string? suppressionOutputFile = context.ParseResult.GetValue(suppressionOutputFileOption);
- string? noWarn = context.ParseResult.GetValue(noWarnOption);
- bool respectInternals = context.ParseResult.GetValue(respectInternalsOption);
- bool enableRuleAttributesMustMatch = context.ParseResult.GetValue(enableRuleAttributesMustMatchOption);
- string[]? excludeAttributesFiles = context.ParseResult.GetValue(excludeAttributesFilesOption);
- bool enableRuleCannotChangeParameterName = context.ParseResult.GetValue(enableRuleCannotChangeParameterNameOption);
-
- string package = context.ParseResult.GetValue(packageArgument);
- bool runApiCompat = context.ParseResult.GetValue(runApiCompatOption);
- bool enableStrictModeForCompatibleTfms = context.ParseResult.GetValue(enableStrictModeForCompatibleTfmsOption);
- bool enableStrictModeForCompatibleFrameworksInPackage = context.ParseResult.GetValue(enableStrictModeForCompatibleFrameworksInPackageOption);
- bool enableStrictModeForBaselineValidation = context.ParseResult.GetValue(enableStrictModeForBaselineValidationOption);
- string? baselinePackage = context.ParseResult.GetValue(baselinePackageOption);
- string? runtimeGraph = context.ParseResult.GetValue(runtimeGraphOption);
- Dictionary>? packageAssemblyReferences = context.ParseResult.GetValue(packageAssemblyReferencesOption);
- Dictionary>? baselinePackageAssemblyReferences = context.ParseResult.GetValue(baselinePackageAssemblyReferencesOption);
+ MessageImportance verbosity = parseResult.GetValue(verbosityOption);
+ bool generateSuppressionFile = parseResult.GetValue(generateSuppressionFileOption);
+ string[]? suppressionFiles = parseResult.GetValue(suppressionFilesOption);
+ string? suppressionOutputFile = parseResult.GetValue(suppressionOutputFileOption);
+ string? noWarn = parseResult.GetValue(noWarnOption);
+ bool respectInternals = parseResult.GetValue(respectInternalsOption);
+ bool enableRuleAttributesMustMatch = parseResult.GetValue(enableRuleAttributesMustMatchOption);
+ string[]? excludeAttributesFiles = parseResult.GetValue(excludeAttributesFilesOption);
+ bool enableRuleCannotChangeParameterName = parseResult.GetValue(enableRuleCannotChangeParameterNameOption);
+
+ string? package = parseResult.GetValue(packageArgument);
+ bool runApiCompat = parseResult.GetValue(runApiCompatOption);
+ bool enableStrictModeForCompatibleTfms = parseResult.GetValue(enableStrictModeForCompatibleTfmsOption);
+ bool enableStrictModeForCompatibleFrameworksInPackage = parseResult.GetValue(enableStrictModeForCompatibleFrameworksInPackageOption);
+ bool enableStrictModeForBaselineValidation = parseResult.GetValue(enableStrictModeForBaselineValidationOption);
+ string? baselinePackage = parseResult.GetValue(baselinePackageOption);
+ string? runtimeGraph = parseResult.GetValue(runtimeGraphOption);
+ Dictionary>? packageAssemblyReferences = parseResult.GetValue(packageAssemblyReferencesOption);
+ Dictionary>? baselinePackageAssemblyReferences = parseResult.GetValue(baselinePackageAssemblyReferencesOption);
Func logFactory = (suppressionEngine) => new(suppressionEngine, verbosity);
ValidatePackage.Run(logFactory,
@@ -278,14 +315,14 @@ static int Main(string[] args)
roslynResolver.Unregister();
});
- rootCommand.AddCommand(packageCommand);
- return rootCommand.Invoke(args);
+ rootCommand.Subcommands.Add(packageCommand);
+ return rootCommand.Parse(args).Invoke();
}
private static string[][] ParseAssemblyReferenceArgument(ArgumentResult argumentResult)
{
List args = new();
- foreach (Token token in argumentResult.Tokens)
+ foreach (var token in argumentResult.Tokens)
{
args.Add(token.Value.Split(','));
}
@@ -296,7 +333,7 @@ private static string[][] ParseAssemblyReferenceArgument(ArgumentResult argument
private static string[] ParseAssemblyArgument(ArgumentResult argumentResult)
{
List args = new();
- foreach (Token token in argumentResult.Tokens)
+ foreach (var token in argumentResult.Tokens)
{
args.AddRange(token.Value.Split(','));
}
@@ -312,7 +349,7 @@ private static (string CaptureGroupPattern, string ReplacementString)[]? ParseTr
string[] parts = argumentResult.Tokens[i].Value.Split(';');
if (parts.Length != 2)
{
- argumentResult.ErrorMessage = "Invalid assemblies transformation pattern. Usage: {regex-pattern};{replacement-string}";
+ argumentResult.AddError("Invalid assemblies transformation pattern. Usage: {regex-pattern};{replacement-string}");
continue;
}
@@ -327,12 +364,12 @@ private static (string CaptureGroupPattern, string ReplacementString)[]? ParseTr
const string invalidPackageAssemblyReferenceFormatMessage = "Invalid package assembly reference format {TargetFrameworkMoniker(+TargetPlatformMoniker)=assembly1,assembly2,assembly3,...}";
Dictionary> packageAssemblyReferencesDict = new(argumentResult.Tokens.Count);
- foreach (Token token in argumentResult.Tokens)
+ foreach (var token in argumentResult.Tokens)
{
string[] parts = token.Value.Split('=');
if (parts.Length != 2)
{
- argumentResult.ErrorMessage = invalidPackageAssemblyReferenceFormatMessage;
+ argumentResult.AddError(invalidPackageAssemblyReferenceFormatMessage);
continue;
}
@@ -342,7 +379,7 @@ private static (string CaptureGroupPattern, string ReplacementString)[]? ParseTr
string[] tfmInformationParts = tfmInformation.Split('+');
if (tfmInformationParts.Length < 1 || tfmInformationParts.Length > 2)
{
- argumentResult.ErrorMessage = invalidPackageAssemblyReferenceFormatMessage;
+ argumentResult.AddError(invalidPackageAssemblyReferenceFormatMessage);
}
string targetFrameworkMoniker = tfmInformationParts[0];
diff --git a/src/BlazorWasmSdk/Tool/Program.cs b/src/BlazorWasmSdk/Tool/Program.cs
index 0aeaba796e70..640554c95aa6 100644
--- a/src/BlazorWasmSdk/Tool/Program.cs
+++ b/src/BlazorWasmSdk/Tool/Program.cs
@@ -4,8 +4,6 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
-using System.CommandLine.Invocation;
-using System.CommandLine.Parsing;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
@@ -16,23 +14,22 @@ internal static class Program
{
public static int Main(string[] args)
{
- var rootCommand = new RootCommand();
- var brotli = new Command("brotli");
+ CliRootCommand rootCommand = new();
+ CliCommand brotli = new("brotli");
- var compressionLevelOption = new Option(
- "-c",
- defaultValueFactory: () => CompressionLevel.SmallestSize,
- description: "System.IO.Compression.CompressionLevel for the Brotli compression algorithm.");
- var sourcesOption = new Option>(
- "-s",
- description: "A list of files to compress.")
+ CliOption compressionLevelOption = new("-c")
{
+ DefaultValueFactory = _ => CompressionLevel.SmallestSize,
+ Description = "System.IO.Compression.CompressionLevel for the Brotli compression algorithm.",
+ };
+ CliOption> sourcesOption = new("-s")
+ {
+ Description = "A list of files to compress.",
AllowMultipleArgumentsPerToken = false
};
- var outputsOption = new Option>(
- "-o",
- "The filenames to output the compressed file to.")
+ CliOption> outputsOption = new("-o")
{
+ Description = "The filenames to output the compressed file to.",
AllowMultipleArgumentsPerToken = false
};
@@ -42,12 +39,11 @@ public static int Main(string[] args)
rootCommand.Add(brotli);
- brotli.SetHandler((InvocationContext context) =>
+ brotli.SetAction((ParseResult parseResult) =>
{
- var parseResults = context.ParseResult;
- var c = parseResults.GetValue(compressionLevelOption);
- var s = parseResults.GetValue(sourcesOption);
- var o = parseResults.GetValue(outputsOption);
+ var c = parseResult.GetValue(compressionLevelOption);
+ var s = parseResult.GetValue(sourcesOption);
+ var o = parseResult.GetValue(outputsOption);
Parallel.For(0, s.Count, i =>
{
@@ -69,7 +65,7 @@ public static int Main(string[] args)
});
});
- return rootCommand.InvokeAsync(args).Result;
+ return rootCommand.Parse(args).Invoke();
}
}
}
diff --git a/src/BuiltInTools/dotnet-watch/CommandLineOptions.cs b/src/BuiltInTools/dotnet-watch/CommandLineOptions.cs
index 5bf4f7798baf..b61050e57f06 100644
--- a/src/BuiltInTools/dotnet-watch/CommandLineOptions.cs
+++ b/src/BuiltInTools/dotnet-watch/CommandLineOptions.cs
@@ -6,10 +6,8 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
-using System.CommandLine.Invocation;
+using System.IO;
using System.Linq;
-
-using Microsoft.AspNetCore.Authentication;
using Microsoft.DotNet.Watcher.Tools;
using Microsoft.Extensions.Tools.Internal;
@@ -77,112 +75,131 @@ dotnet watch test
public required IReadOnlyList RemainingArguments { get; init; }
public RunCommandLineOptions? RunOptions { get; init; }
- public static CommandLineOptions? Parse(string[] args, IReporter reporter, out int errorCode, System.CommandLine.IConsole? console = null)
+ public static CommandLineOptions? Parse(string[] args, IReporter reporter, out int errorCode, TextWriter? output = null, TextWriter? error = null)
{
- var quietOption = new Option(new[] { "--quiet", "-q" }, "Suppresses all output except warnings and errors");
- var verboseOption = new Option(new[] { "--verbose", "-v" }, "Show verbose output");
+ var quietOption = new CliOption("--quiet", "-q")
+ {
+ Description = "Suppresses all output except warnings and errors"
+ };
- verboseOption.AddValidator(v =>
+ var verboseOption = new CliOption("--verbose", "-v")
{
- if (v.FindResultFor(quietOption) is not null && v.FindResultFor(verboseOption) is not null)
+ Description = "Show verbose output"
+ };
+
+ verboseOption.Validators.Add(v =>
+ {
+ if (v.GetResult(quietOption) is not null && v.GetResult(verboseOption) is not null)
{
- v.ErrorMessage = Resources.Error_QuietAndVerboseSpecified;
+ v.AddError(Resources.Error_QuietAndVerboseSpecified);
}
});
- var listOption = new Option("--list", "Lists all discovered files without starting the watcher.");
- var shortProjectOption = new Option("-p", "The project to watch.") { IsHidden = true };
- var longProjectOption = new Option("--project", "The project to watch");
+ var listOption = new CliOption("--list") { Description = "Lists all discovered files without starting the watcher." };
+ var shortProjectOption = new CliOption("-p") { Description = "The project to watch.", Hidden = true };
+ var longProjectOption = new CliOption("--project") { Description = "The project to watch" };
// launch profile used by dotnet-watch
- var launchProfileWatchOption = new Option(new[] { "-lp", LaunchProfileOptionName }, "The launch profile to start the project with (case-sensitive).");
- var noLaunchProfileWatchOption = new Option(new[] { NoLaunchProfileOptionName }, "Do not attempt to use launchSettings.json to configure the application.");
+ var launchProfileWatchOption = new CliOption(LaunchProfileOptionName, "-lp")
+ {
+ Description = "The launch profile to start the project with (case-sensitive)."
+ };
+ var noLaunchProfileWatchOption = new CliOption(NoLaunchProfileOptionName)
+ {
+ Description = "Do not attempt to use launchSettings.json to configure the application."
+ };
// launch profile used by dotnet-run
- var launchProfileRunOption = new Option(new[] { "-lp", LaunchProfileOptionName }) { IsHidden = true };
- var noLaunchProfileRunOption = new Option(new[] { NoLaunchProfileOptionName }) { IsHidden = true };
+ var launchProfileRunOption = new CliOption(LaunchProfileOptionName, "-lp") { Hidden = true };
+ var noLaunchProfileRunOption = new CliOption(NoLaunchProfileOptionName) { Hidden = true };
- var targetFrameworkOption = new Option(new[] { "-f", "--framework" }, "The target framework to run for. The target framework must also be specified in the project file.");
- var propertyOption = new Option(new[] { "--property" }, "Properties to be passed to MSBuild.");
+ var targetFrameworkOption = new CliOption("--framework", "-f")
+ {
+ Description = "The target framework to run for. The target framework must also be specified in the project file."
+ };
+ var propertyOption = new CliOption("--property")
+ {
+ Description = "Properties to be passed to MSBuild."
+ };
- propertyOption.AddValidator(v =>
+ propertyOption.Validators.Add(v =>
{
var invalidProperty = v.GetValue(propertyOption)?.FirstOrDefault(
property => !(property.IndexOf('=') is > 0 and var index && index < property.Length - 1 && property[..index].Trim().Length > 0));
if (invalidProperty != null)
{
- v.ErrorMessage = $"Invalid property format: '{invalidProperty}'. Expected 'name=value'.";
+ v.AddError($"Invalid property format: '{invalidProperty}'. Expected 'name=value'.");
}
});
- var noHotReloadOption = new Option("--no-hot-reload", "Suppress hot reload for supported apps.");
- var nonInteractiveOption = new Option(
- "--non-interactive",
- "Runs dotnet-watch in non-interactive mode. This option is only supported when running with Hot Reload enabled. " +
- "Use this option to prevent console input from being captured.");
+ var noHotReloadOption = new CliOption("--no-hot-reload") { Description = "Suppress hot reload for supported apps." };
+ var nonInteractiveOption = new CliOption("--non-interactive")
+ {
+ Description = "Runs dotnet-watch in non-interactive mode. This option is only supported when running with Hot Reload enabled. " +
+ "Use this option to prevent console input from being captured."
+ };
- var remainingWatchArgs = new Argument("forwardedArgs", "Arguments to pass to the child dotnet process.");
- var remainingRunArgs = new Argument(name: null);
+ var remainingWatchArgs = new CliArgument("forwardedArgs") { Description = "Arguments to pass to the child dotnet process." };
+ var remainingRunArgs = new CliArgument("remainingRunArgs");
- var runCommand = new Command("run") { IsHidden = true };
- var rootCommand = new RootCommand(Description);
- addOptions(runCommand);
- addOptions(rootCommand);
+ var runCommand = new CliCommand("run") { Hidden = true };
+ var rootCommand = new CliRootCommand(Description);
+ AddSymbols(runCommand);
+ AddSymbols(rootCommand);
- void addOptions(Command command)
+ void AddSymbols(CliCommand command)
{
- command.Add(quietOption);
- command.Add(verboseOption);
- command.Add(noHotReloadOption);
- command.Add(nonInteractiveOption);
- command.Add(longProjectOption);
- command.Add(shortProjectOption);
+ command.Options.Add(quietOption);
+ command.Options.Add(verboseOption);
+ command.Options.Add(noHotReloadOption);
+ command.Options.Add(nonInteractiveOption);
+ command.Options.Add(longProjectOption);
+ command.Options.Add(shortProjectOption);
if (command == runCommand)
{
- command.Add(launchProfileRunOption);
- command.Add(noLaunchProfileRunOption);
+ command.Options.Add(launchProfileRunOption);
+ command.Options.Add(noLaunchProfileRunOption);
}
else
{
- command.Add(launchProfileWatchOption);
- command.Add(noLaunchProfileWatchOption);
+ command.Options.Add(launchProfileWatchOption);
+ command.Options.Add(noLaunchProfileWatchOption);
}
- command.Add(targetFrameworkOption);
- command.Add(propertyOption);
+ command.Options.Add(targetFrameworkOption);
+ command.Options.Add(propertyOption);
- command.Add(listOption);
+ command.Options.Add(listOption);
if (command == runCommand)
{
- command.Add(remainingRunArgs);
+ command.Arguments.Add(remainingRunArgs);
}
else
{
- command.Add(runCommand);
- command.Add(remainingWatchArgs);
+ command.Subcommands.Add(runCommand);
+ command.Arguments.Add(remainingWatchArgs);
}
};
CommandLineOptions? options = null;
- runCommand.SetHandler(context =>
+ runCommand.SetAction(parseResult =>
{
- RootHandler(context, new()
+ RootHandler(parseResult, new()
{
- LaunchProfileName = context.ParseResult.GetValue(launchProfileRunOption),
- NoLaunchProfile = context.ParseResult.GetValue(noLaunchProfileRunOption),
- RemainingArguments = context.ParseResult.GetValue(remainingRunArgs),
+ LaunchProfileName = parseResult.GetValue(launchProfileRunOption),
+ NoLaunchProfile = parseResult.GetValue(noLaunchProfileRunOption),
+ RemainingArguments = parseResult.GetValue(remainingRunArgs) ?? Array.Empty(),
});
});
- rootCommand.SetHandler(context => RootHandler(context, runOptions: null));
+ rootCommand.SetAction(parseResult => RootHandler(parseResult, runOptions: null));
- void RootHandler(InvocationContext context, RunCommandLineOptions? runOptions)
+ void RootHandler(ParseResult parseResults, RunCommandLineOptions? runOptions)
{
- var parseResults = context.ParseResult;
var projectValue = parseResults.GetValue(longProjectOption);
if (string.IsNullOrEmpty(projectValue))
{
@@ -207,12 +224,17 @@ void RootHandler(InvocationContext context, RunCommandLineOptions? runOptions)
TargetFramework = parseResults.GetValue(targetFrameworkOption),
BuildProperties = parseResults.GetValue(propertyOption)?
.Select(p => (p[..p.IndexOf('=')].Trim(), p[(p.IndexOf('=') + 1)..])).ToArray(),
- RemainingArguments = parseResults.GetValue(remainingWatchArgs),
+ RemainingArguments = parseResults.GetValue(remainingWatchArgs) ?? Array.Empty(),
RunOptions = runOptions,
};
}
- errorCode = rootCommand.Invoke(args, console);
+ errorCode = new CliConfiguration(rootCommand)
+ {
+ Output = output ?? Console.Out,
+ Error = error ?? Console.Error
+ }.Invoke(args);
+
return options;
}
diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Extensions/StringExtensions.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Extensions/StringExtensions.cs
new file mode 100644
index 000000000000..9c03f3efb233
--- /dev/null
+++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Extensions/StringExtensions.cs
@@ -0,0 +1,34 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.DotNet.Cli.Utils.Extensions
+{
+ public static class StringExtensions
+ {
+ public static string RemovePrefix(this string name)
+ {
+ int prefixLength = GetPrefixLength(name);
+
+ return prefixLength > 0
+ ? name.Substring(prefixLength)
+ : name;
+
+ static int GetPrefixLength(string name)
+ {
+ if (name[0] == '-')
+ {
+ return name.Length > 1 && name[1] == '-'
+ ? 2
+ : 1;
+ }
+
+ if (name[0] == '/')
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+ }
+}
diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/ChoiceTemplateParameter.cs b/src/Cli/Microsoft.TemplateEngine.Cli/ChoiceTemplateParameter.cs
index 9e8dff82f3f8..e24b7928e774 100644
--- a/src/Cli/Microsoft.TemplateEngine.Cli/ChoiceTemplateParameter.cs
+++ b/src/Cli/Microsoft.TemplateEngine.Cli/ChoiceTemplateParameter.cs
@@ -49,35 +49,38 @@ internal ChoiceTemplateParameter(ChoiceTemplateParameter choiceTemplateParameter
{
return (context) =>
{
- string standardUsage = HelpBuilder.Default.GetIdentifierSymbolUsageLabel(o.Option, context);
+ string standardUsage = HelpBuilder.Default.GetOptionUsageLabel(o.Option);
if (standardUsage.Length > context.HelpBuilder.MaxWidth / 3)
{
if (Choices.Count > 2)
{
- o.Option.ArgumentHelpName = $"{string.Join("|", Choices.Keys.Take(2))}|...";
- string updatedFirstColumn = HelpBuilder.Default.GetIdentifierSymbolUsageLabel(o.Option, context);
+ o.Option.HelpName = $"{string.Join("|", Choices.Keys.Take(2))}|...";
+ string updatedFirstColumn = HelpBuilder.Default.GetOptionUsageLabel(o.Option);
if (updatedFirstColumn.Length <= context.HelpBuilder.MaxWidth / 3)
{
return updatedFirstColumn;
}
}
- o.Option.ArgumentHelpName = HelpStrings.Text_ChoiceArgumentHelpName;
- return HelpBuilder.Default.GetIdentifierSymbolUsageLabel(o.Option, context);
+ o.Option.HelpName = HelpStrings.Text_ChoiceArgumentHelpName;
+ return HelpBuilder.Default.GetOptionUsageLabel(o.Option);
}
return standardUsage;
};
}
- protected override Option GetBaseOption(IReadOnlySet aliases)
+ protected override CliOption GetBaseOption(IReadOnlySet aliases)
{
- Option option = new Option(
- aliases.ToArray(),
- parseArgument: result => GetParseChoiceArgument(this)(result))
+ string name = GetName(aliases);
+
+ CliOption option = new(name)
{
+ CustomParser = result => GetParseChoiceArgument(this)(result),
Arity = new ArgumentArity(DefaultIfOptionWithoutValue == null ? 1 : 0, AllowMultipleValues ? _choices.Count : 1),
AllowMultipleArgumentsPerToken = AllowMultipleValues
};
+ AddAliases(option, aliases);
+
// empty string for the explicit unset option
option.FromAmongCaseInsensitive(Choices.Keys.ToArray(), allowedHiddenValue: string.Empty);
@@ -99,7 +102,7 @@ private static Func GetParseChoiceArgument(ChoiceTemplat
if (argumentResult.Tokens.Count == 0)
{
- if (or.IsImplicit)
+ if (or.Implicit)
{
if (!string.IsNullOrWhiteSpace(parameter.DefaultValue))
{
@@ -108,16 +111,16 @@ private static Func GetParseChoiceArgument(ChoiceTemplat
return defaultValue;
}
//Cannot parse default value '{0}' for option '{1}' as expected type '{2}': {3}.
- argumentResult.ErrorMessage = string.Format(
+ argumentResult.AddError(string.Format(
LocalizableStrings.ParseChoiceTemplateOption_Error_InvalidDefaultValue,
parameter.DefaultValue,
- or.Token?.Value,
+ or.IdentifierToken?.Value,
"choice",
- error);
+ error));
return string.Empty;
}
//Default value for argument missing for option: '{0}'.
- argumentResult.ErrorMessage = string.Format(LocalizableStrings.ParseTemplateOption_Error_MissingDefaultValue, or.Token?.Value);
+ argumentResult.AddError(string.Format(LocalizableStrings.ParseTemplateOption_Error_MissingDefaultValue, or.IdentifierToken?.Value));
return string.Empty;
}
if (parameter.DefaultIfOptionWithoutValue != null)
@@ -127,22 +130,22 @@ private static Func GetParseChoiceArgument(ChoiceTemplat
return defaultValue;
}
//Cannot parse default if option without value '{0}' for option '{1}' as expected type '{2}': {3}.
- argumentResult.ErrorMessage = string.Format(
+ argumentResult.AddError(string.Format(
LocalizableStrings.ParseChoiceTemplateOption_Error_InvalidDefaultIfNoOptionValue,
parameter.DefaultIfOptionWithoutValue,
- or.Token?.Value,
+ or.IdentifierToken?.Value,
"choice",
- error);
+ error));
return string.Empty;
}
//Required argument missing for option: '{0}'.
- argumentResult.ErrorMessage = string.Format(LocalizableStrings.ParseTemplateOption_Error_MissingDefaultIfNoOptionValue, or.Token?.Value);
+ argumentResult.AddError(string.Format(LocalizableStrings.ParseTemplateOption_Error_MissingDefaultIfNoOptionValue, or.IdentifierToken?.Value));
return string.Empty;
}
else if (!parameter.AllowMultipleValues && argumentResult.Tokens.Count != 1)
{
//Using more than 1 argument is not allowed for '{0}', used: {1}.
- argumentResult.ErrorMessage = string.Format(LocalizableStrings.ParseTemplateOption_Error_InvalidCount, or.Token?.Value, argumentResult.Tokens.Count);
+ argumentResult.AddError(string.Format(LocalizableStrings.ParseTemplateOption_Error_InvalidCount, or.IdentifierToken?.Value, argumentResult.Tokens.Count));
return string.Empty;
}
else
@@ -150,12 +153,12 @@ private static Func GetParseChoiceArgument(ChoiceTemplat
if (!TryConvertValueToChoice(argumentResult.Tokens.Select(t => t.Value), parameter, out string value, out string error))
{
//Cannot parse argument '{0}' for option '{1}' as expected type '{2}': {3}.
- argumentResult.ErrorMessage = string.Format(
+ argumentResult.AddError(string.Format(
LocalizableStrings.ParseChoiceTemplateOption_Error_InvalidArgument,
argumentResult.Tokens[0].Value,
- or.Token?.Value,
+ or.IdentifierToken?.Value,
"choice",
- error);
+ error));
return string.Empty;
}
diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/CliTemplateParameter.cs b/src/Cli/Microsoft.TemplateEngine.Cli/CliTemplateParameter.cs
index 5d315915dd6e..5c2b8b70c997 100644
--- a/src/Cli/Microsoft.TemplateEngine.Cli/CliTemplateParameter.cs
+++ b/src/Cli/Microsoft.TemplateEngine.Cli/CliTemplateParameter.cs
@@ -4,6 +4,7 @@
using System.CommandLine;
using System.CommandLine.Help;
using System.CommandLine.Parsing;
+using System.Diagnostics;
using System.Globalization;
using System.Text;
using Microsoft.TemplateEngine.Abstractions;
@@ -134,13 +135,13 @@ internal CliTemplateParameter(CliTemplateParameter other)
protected bool AllowMultipleValues { get; private init; }
///
- /// Creates for template parameter.
+ /// Creates for template parameter.
///
/// aliases to be used for option.
- internal Option GetOption(IReadOnlySet aliases)
+ internal CliOption GetOption(IReadOnlySet aliases)
{
- Option option = GetBaseOption(aliases);
- option.IsHidden = IsHidden;
+ CliOption option = GetBaseOption(aliases);
+ option.Hidden = IsHidden;
//if parameter is required, the default value is ignored.
//the user should always specify the parameter, so the default value is not even shown.
@@ -149,7 +150,34 @@ internal Option GetOption(IReadOnlySet aliases)
if (!string.IsNullOrWhiteSpace(DefaultValue)
|| (Type == ParameterType.String || Type == ParameterType.Choice) && DefaultValue != null)
{
- option.SetDefaultValue(DefaultValue);
+ switch (option)
+ {
+ case CliOption stringOption:
+ stringOption.DefaultValueFactory = (_) => DefaultValue;
+ break;
+ case CliOption booleanOption:
+ booleanOption.DefaultValueFactory = (_) => bool.Parse(DefaultValue);
+ break;
+ case CliOption integerOption:
+ if (Type == ParameterType.Hex)
+ {
+ integerOption.DefaultValueFactory = (_) => Convert.ToInt64(DefaultValue, 16);
+ }
+ else
+ {
+ integerOption.DefaultValueFactory = (_) => long.Parse(DefaultValue);
+ }
+ break;
+ case CliOption floatOption:
+ floatOption.DefaultValueFactory = (_) => float.Parse(DefaultValue);
+ break;
+ case CliOption doubleOption:
+ doubleOption.DefaultValueFactory = (_) => double.Parse(DefaultValue);
+ break;
+ default:
+ Debug.Fail($"Unexpected Option type: {option.GetType()}");
+ break;
+ }
}
}
option.Description = GetOptionDescription();
@@ -176,40 +204,83 @@ internal Option GetOption(IReadOnlySet aliases)
};
}
- protected virtual Option GetBaseOption(IReadOnlySet aliases)
+ protected virtual CliOption GetBaseOption(IReadOnlySet aliases)
{
- return Type switch
+ string name = GetName(aliases);
+ CliOption cliOption = Type switch
{
- ParameterType.Boolean => new Option(aliases.ToArray())
+ ParameterType.Boolean => new CliOption(name)
{
Arity = new ArgumentArity(0, 1)
},
- ParameterType.Integer => new Option(
- aliases.ToArray(),
- parseArgument: result => GetParseArgument(this, ConvertValueToInt)(result))
+ ParameterType.Integer => new CliOption(name)
{
+ CustomParser = result => GetParseArgument(this, ConvertValueToInt)(result),
Arity = new ArgumentArity(string.IsNullOrWhiteSpace(DefaultIfOptionWithoutValue) ? 1 : 0, 1)
},
- ParameterType.String => new Option(
- aliases.ToArray(),
- parseArgument: result => GetParseArgument(this, ConvertValueToString)(result))
+ ParameterType.String => new CliOption(name)
{
+ CustomParser = result => GetParseArgument(this, ConvertValueToString)(result),
Arity = new ArgumentArity(DefaultIfOptionWithoutValue == null ? 1 : 0, 1)
},
- ParameterType.Float => new Option(
- aliases.ToArray(),
- parseArgument: result => GetParseArgument(this, ConvertValueToFloat)(result))
+ ParameterType.Float => new CliOption(name)
{
+ CustomParser = result => GetParseArgument(this, ConvertValueToFloat)(result),
Arity = new ArgumentArity(string.IsNullOrWhiteSpace(DefaultIfOptionWithoutValue) ? 1 : 0, 1)
},
- ParameterType.Hex => new Option(
- aliases.ToArray(),
- parseArgument: result => GetParseArgument(this, ConvertValueToHex)(result))
+ ParameterType.Hex => new CliOption(name)
{
+ CustomParser = result => GetParseArgument(this, ConvertValueToHex)(result),
Arity = new ArgumentArity(string.IsNullOrWhiteSpace(DefaultIfOptionWithoutValue) ? 1 : 0, 1)
},
_ => throw new Exception($"Unexpected value for {nameof(ParameterType)}: {Type}.")
};
+ AddAliases(cliOption, aliases);
+ return cliOption;
+ }
+
+ ///
+ /// Returns the longest alias without prefix.
+ /// This is how System.CommandLine used to choose Name from aliases before Name and Aliases separation.
+ ///
+ protected string GetName(IReadOnlySet aliases)
+ {
+ string name = "-";
+
+ foreach (string alias in aliases)
+ {
+ if ((alias.Length - GetPrefixLength(alias)) > (name.Length - GetPrefixLength(name)))
+ {
+ name = alias;
+ }
+ }
+
+ return name;
+
+ static int GetPrefixLength(string alias)
+ {
+ if (alias[0] == '-')
+ {
+ return alias.Length > 1 && alias[1] == '-' ? 2 : 1;
+ }
+ else if (alias[0] == '/')
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+
+ protected void AddAliases(CliOption option, IReadOnlySet aliases)
+ {
+ foreach (string alias in aliases)
+ {
+ if (alias != option.Name)
+ {
+ option.Aliases.Add(alias);
+ }
+ }
}
private static string ParameterTypeToString(ParameterType dataType)
@@ -251,7 +322,7 @@ private static Func GetParseArgument(CliTemplateParameter
if (argumentResult.Tokens.Count == 0)
{
- if (or.IsImplicit)
+ if (or.Implicit)
{
if (parameter.DefaultValue != null)
{
@@ -262,18 +333,18 @@ private static Func GetParseArgument(CliTemplateParameter
}
//Cannot parse default value '{0}' for option '{1}' as expected type '{2}'.
- argumentResult.ErrorMessage = string.Format(
+ argumentResult.AddError(string.Format(
LocalizableStrings.ParseTemplateOption_Error_InvalidDefaultValue,
parameter.DefaultValue,
- or.Token?.Value,
- typeof(T).Name);
+ or.IdentifierToken?.Value,
+ typeof(T).Name));
//https://github.com/dotnet/command-line-api/blob/5eca6545a0196124cc1a66d8bd43db8945f1f1b7/src/System.CommandLine/Argument%7BT%7D.cs#L99-L113
//system-command-line can handle null.
return default!;
}
//Default value for argument missing for option: '{0}'.
- argumentResult.ErrorMessage = string.Format(LocalizableStrings.ParseTemplateOption_Error_MissingDefaultValue, or.Token?.Value);
+ argumentResult.AddError(string.Format(LocalizableStrings.ParseTemplateOption_Error_MissingDefaultValue, or.IdentifierToken?.Value));
return default!;
}
if (parameter.DefaultIfOptionWithoutValue != null)
@@ -284,15 +355,15 @@ private static Func GetParseArgument(CliTemplateParameter
return value;
}
//Cannot parse default if option without value '{0}' for option '{1}' as expected type '{2}'.
- argumentResult.ErrorMessage = string.Format(
+ argumentResult.AddError(string.Format(
LocalizableStrings.ParseTemplateOption_Error_InvalidDefaultIfNoOptionValue,
parameter.DefaultIfOptionWithoutValue,
- or.Token?.Value,
- typeof(T).Name);
+ or.IdentifierToken?.Value,
+ typeof(T).Name));
return default!;
}
//Required argument missing for option: '{0}'.
- argumentResult.ErrorMessage = string.Format(LocalizableStrings.ParseTemplateOption_Error_MissingDefaultIfNoOptionValue, or.Token?.Value);
+ argumentResult.AddError(string.Format(LocalizableStrings.ParseTemplateOption_Error_MissingDefaultIfNoOptionValue, or.IdentifierToken?.Value));
return default!;
}
else if (argumentResult.Tokens.Count == 1)
@@ -303,17 +374,17 @@ private static Func GetParseArgument(CliTemplateParameter
return value;
}
//Cannot parse argument '{0}' for option '{1}' as expected type '{2}'.
- argumentResult.ErrorMessage = string.Format(
+ argumentResult.AddError(string.Format(
LocalizableStrings.ParseTemplateOption_Error_InvalidArgument,
argumentResult.Tokens[0].Value,
- or.Token?.Value,
- typeof(T).Name);
+ or.IdentifierToken?.Value,
+ typeof(T).Name));
return default!;
}
else
{
//Using more than 1 argument is not allowed for '{0}', used: {1}.
- argumentResult.ErrorMessage = string.Format(LocalizableStrings.ParseTemplateOption_Error_InvalidCount, or.Token?.Value, argumentResult.Tokens.Count);
+ argumentResult.AddError(string.Format(LocalizableStrings.ParseTemplateOption_Error_InvalidCount, or.IdentifierToken?.Value, argumentResult.Tokens.Count));
return default!;
}
};
diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs
index db54ccdcdb76..df7a47958b89 100644
--- a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs
+++ b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/BaseCommand.cs
@@ -3,7 +3,6 @@
using System.CommandLine;
using System.CommandLine.Completions;
-using System.CommandLine.Invocation;
using System.Reflection;
using Microsoft.DotNet.Cli.Utils;
using Microsoft.TemplateEngine.Abstractions;
@@ -12,11 +11,10 @@
using Microsoft.TemplateEngine.Edge;
using Microsoft.TemplateEngine.Edge.Settings;
using Microsoft.TemplateEngine.Utils;
-using Command = System.CommandLine.Command;
namespace Microsoft.TemplateEngine.Cli.Commands
{
- internal abstract class BaseCommand : Command
+ internal abstract class BaseCommand : CliCommand
{
private readonly Func _hostBuilder;
@@ -49,7 +47,7 @@ protected IEngineEnvironmentSettings CreateEnvironmentSettings(GlobalArgs args,
}
}
- internal abstract class BaseCommand : BaseCommand, ICommandHandler where TArgs : GlobalArgs
+ internal abstract class BaseCommand : BaseCommand where TArgs : GlobalArgs
{
internal BaseCommand(
Func hostBuilder,
@@ -57,70 +55,9 @@ internal BaseCommand(
string description)
: base(hostBuilder, name, description)
{
- this.Handler = this;
+ Action = new CommandAction(this);
}
- public async Task InvokeAsync(InvocationContext context)
- {
- TArgs args = ParseContext(context.ParseResult);
- using IEngineEnvironmentSettings environmentSettings = CreateEnvironmentSettings(args, context.ParseResult);
- using TemplatePackageManager templatePackageManager = new(environmentSettings);
- CancellationToken cancellationToken = context.GetCancellationToken();
-
- NewCommandStatus returnCode;
-
- try
- {
- using (Timing.Over(environmentSettings.Host.Logger, "Execute"))
- {
- await HandleGlobalOptionsAsync(args, environmentSettings, templatePackageManager, cancellationToken).ConfigureAwait(false);
- returnCode = await ExecuteAsync(args, environmentSettings, templatePackageManager, context).ConfigureAwait(false);
- }
- }
- catch (Exception ex)
- {
- AggregateException? ax = ex as AggregateException;
-
- while (ax != null && ax.InnerExceptions.Count == 1 && ax.InnerException is not null)
- {
- ex = ax.InnerException;
- ax = ex as AggregateException;
- }
-
- Reporter.Error.WriteLine(ex.Message.Bold().Red());
-
- while (ex.InnerException != null)
- {
- ex = ex.InnerException;
- ax = ex as AggregateException;
-
- while (ax != null && ax.InnerExceptions.Count == 1 && ax.InnerException is not null)
- {
- ex = ax.InnerException;
- ax = ex as AggregateException;
- }
-
- Reporter.Error.WriteLine(ex.Message.Bold().Red());
- }
-
- if (!string.IsNullOrWhiteSpace(ex.StackTrace))
- {
- Reporter.Error.WriteLine(ex.StackTrace.Bold().Red());
- }
- returnCode = NewCommandStatus.Unexpected;
- }
-
- if (returnCode != NewCommandStatus.Success)
- {
- Reporter.Error.WriteLine();
- Reporter.Error.WriteLine(LocalizableStrings.BaseCommand_ExitCodeHelp, (int)returnCode);
- }
-
- return (int)returnCode;
- }
-
- public int Invoke(InvocationContext context) => InvokeAsync(context).GetAwaiter().GetResult();
-
public override IEnumerable GetCompletions(CompletionContext context)
{
if (context.ParseResult == null)
@@ -147,7 +84,7 @@ protected internal static async Task CheckTemplatesWithSubCommandName(
CancellationToken cancellationToken)
{
IReadOnlyList availableTemplates = await templatePackageManager.GetTemplatesAsync(cancellationToken).ConfigureAwait(false);
- string usedCommandAlias = args.ParseResult.CommandResult.Token.Value;
+ string usedCommandAlias = args.ParseResult.CommandResult.IdentifierToken.Value;
if (!availableTemplates.Any(t => t.ShortNameList.Any(sn => string.Equals(sn, usedCommandAlias, StringComparison.OrdinalIgnoreCase))))
{
return;
@@ -158,7 +95,9 @@ protected internal static async Task CheckTemplatesWithSubCommandName(
Reporter.Output.WriteLine();
}
- protected static void PrintDeprecationMessage(ParseResult parseResult, Option? additionalOption = null) where TDepr : Command where TNew : Command
+ protected static void PrintDeprecationMessage(ParseResult parseResult, CliOption? additionalOption = null)
+ where TDepr : CliCommand
+ where TNew : CliCommand
{
var newCommandExample = Example.For(parseResult);
if (additionalOption != null)
@@ -176,22 +115,22 @@ protected static void PrintDeprecationMessage(ParseResult parseResu
Reporter.Output.WriteLine();
}
- protected abstract Task ExecuteAsync(TArgs args, IEngineEnvironmentSettings environmentSettings, TemplatePackageManager templatePackageManager, InvocationContext context);
+ protected abstract Task ExecuteAsync(TArgs args, IEngineEnvironmentSettings environmentSettings, TemplatePackageManager templatePackageManager, ParseResult parseResult, CancellationToken cancellationToken);
protected abstract TArgs ParseContext(ParseResult parseResult);
- protected virtual Option GetFilterOption(FilterOptionDefinition def)
+ protected virtual CliOption GetFilterOption(FilterOptionDefinition def)
{
return def.OptionFactory();
}
- protected IReadOnlyDictionary SetupFilterOptions(IReadOnlyList filtersToSetup)
+ protected IReadOnlyDictionary SetupFilterOptions(IReadOnlyList filtersToSetup)
{
- Dictionary options = new();
+ Dictionary options = new();
foreach (FilterOptionDefinition filterDef in filtersToSetup)
{
- Option newOption = GetFilterOption(filterDef);
- this.AddOption(newOption);
+ CliOption newOption = GetFilterOption(filterDef);
+ this.Options.Add(newOption);
options[filterDef] = newOption;
}
return options;
@@ -202,8 +141,8 @@ protected IReadOnlyDictionary SetupFilterOptions
///
protected void SetupTabularOutputOptions(ITabularOutputCommand command)
{
- this.AddOption(command.ColumnsAllOption);
- this.AddOption(command.ColumnsOption);
+ this.Options.Add(command.ColumnsAllOption);
+ this.Options.Add(command.ColumnsOption);
}
private static async Task HandleGlobalOptionsAsync(
@@ -279,5 +218,72 @@ private static void HandleDebugShowConfig(TArgs args, IEngineEnvironmentSettings
Reporter.Output.WriteLine(generatorsFormatter.Layout());
Reporter.Output.WriteLine();
}
+
+ private sealed class CommandAction : CliAction
+ {
+ private readonly BaseCommand _command;
+
+ public CommandAction(BaseCommand command) => _command = command;
+
+ public override async Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken)
+ {
+ TArgs args = _command.ParseContext(parseResult);
+ using IEngineEnvironmentSettings environmentSettings = _command.CreateEnvironmentSettings(args, parseResult);
+ using TemplatePackageManager templatePackageManager = new(environmentSettings);
+
+ NewCommandStatus returnCode;
+
+ try
+ {
+ using (Timing.Over(environmentSettings.Host.Logger, "Execute"))
+ {
+ await HandleGlobalOptionsAsync(args, environmentSettings, templatePackageManager, cancellationToken).ConfigureAwait(false);
+ returnCode = await _command.ExecuteAsync(args, environmentSettings, templatePackageManager, parseResult, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch (Exception ex)
+ {
+ AggregateException? ax = ex as AggregateException;
+
+ while (ax != null && ax.InnerExceptions.Count == 1 && ax.InnerException is not null)
+ {
+ ex = ax.InnerException;
+ ax = ex as AggregateException;
+ }
+
+ Reporter.Error.WriteLine(ex.Message.Bold().Red());
+
+ while (ex.InnerException != null)
+ {
+ ex = ex.InnerException;
+ ax = ex as AggregateException;
+
+ while (ax != null && ax.InnerExceptions.Count == 1 && ax.InnerException is not null)
+ {
+ ex = ax.InnerException;
+ ax = ex as AggregateException;
+ }
+
+ Reporter.Error.WriteLine(ex.Message.Bold().Red());
+ }
+
+ if (!string.IsNullOrWhiteSpace(ex.StackTrace))
+ {
+ Reporter.Error.WriteLine(ex.StackTrace.Bold().Red());
+ }
+ returnCode = NewCommandStatus.Unexpected;
+ }
+
+ if (returnCode != NewCommandStatus.Success)
+ {
+ Reporter.Error.WriteLine();
+ Reporter.Error.WriteLine(LocalizableStrings.BaseCommand_ExitCodeHelp, (int)returnCode);
+ }
+
+ return (int)returnCode;
+ }
+
+ public override int Invoke(ParseResult parseResult) => InvokeAsync(parseResult, CancellationToken.None).GetAwaiter().GetResult();
+ }
}
}
diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/BaseFilterableArgs.cs b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/BaseFilterableArgs.cs
index 10e26ef2da00..8fb691a304d3 100644
--- a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/BaseFilterableArgs.cs
+++ b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/BaseFilterableArgs.cs
@@ -47,7 +47,7 @@ internal string GetFilterValue(FilterOptionDefinition filter)
/// Token or null when token cannot be evaluated.
internal string? GetFilterToken(FilterOptionDefinition filter)
{
- return _filters[filter].Token?.Value;
+ return _filters[filter].IdentifierToken?.Value;
}
private static IReadOnlyDictionary ParseFilters(IFilterableCommand filterableCommand, ParseResult parseResult)
@@ -55,7 +55,7 @@ private static IReadOnlyDictionary ParseFi
Dictionary filterValues = new();
foreach (var filter in filterableCommand.Filters)
{
- OptionResult? value = parseResult.FindResultFor(filter.Value);
+ OptionResult? value = parseResult.GetResult(filter.Value);
if (value != null)
{
filterValues[filter.Key] = value;
diff --git a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/CommandLineUtils.cs b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/CommandLineUtils.cs
index a12cd8081ac5..657f2a6451fa 100644
--- a/src/Cli/Microsoft.TemplateEngine.Cli/Commands/CommandLineUtils.cs
+++ b/src/Cli/Microsoft.TemplateEngine.Cli/Commands/CommandLineUtils.cs
@@ -10,7 +10,7 @@ internal class CommandLineUtils
{
// This code is from System.CommandLine, HelpBuilder class.
// Ideally those methods are exposed, we may switch to use them.
- internal static string FormatArgumentUsage(IReadOnlyList arguments)
+ internal static string FormatArgumentUsage(IReadOnlyList arguments)
{
var sb = new StringBuilder();
var end = default(Stack);
@@ -18,7 +18,7 @@ internal static string FormatArgumentUsage(IReadOnlyList arguments)
for (var i = 0; i < arguments.Count; i++)
{
var argument = arguments[i];
- if (argument.IsHidden)
+ if (argument.Hidden)
{
continue;
}
@@ -57,20 +57,20 @@ internal static string FormatArgumentUsage(IReadOnlyList arguments)
}
return sb.ToString();
- bool IsMultiParented(Argument argument) =>
+ bool IsMultiParented(CliArgument argument) =>
argument.Parents.Count() > 1;
- bool IsOptional(Argument argument) =>
+ bool IsOptional(CliArgument argument) =>
IsMultiParented(argument) ||
argument.Arity.MinimumNumberOfValues == 0;
}
- internal static string FormatArgumentUsage(Argument argument) => FormatArgumentUsage(new[] { argument });
+ internal static string FormatArgumentUsage(CliArgument argument) => FormatArgumentUsage(new[] { argument });
- internal static string FormatArgumentUsage(Option option) => FormatArgumentUsage(new[] { option });
+ internal static string FormatArgumentUsage(CliOption option) => FormatArgumentUsage(new[] { option });
// separate instance as Option.Argument is internal.
- internal static string FormatArgumentUsage(IReadOnlyList