From 9719f1b3e59ea2f77cdacebc59e1f7fc53b1a394 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 13 Aug 2025 16:15:11 -0700 Subject: [PATCH 1/4] fix broken xrefs --- .../commandline/get-started-tutorial.md | 4 +-- .../commandline/how-to-parse-and-invoke.md | 6 ++-- .../migration-guide-2.0.0-beta5.md | 28 +++++++++---------- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/standard/commandline/get-started-tutorial.md b/docs/standard/commandline/get-started-tutorial.md index 0621c07245f7a..807d88f7f5f9e 100644 --- a/docs/standard/commandline/get-started-tutorial.md +++ b/docs/standard/commandline/get-started-tutorial.md @@ -1,7 +1,7 @@ --- title: "Tutorial: Get started with System.CommandLine" description: Learn how to use the System.CommandLine library for command-line apps. -ms.date: 04/07/2022 +ms.date: 08/13/2025 ms.topic: tutorial no-loc: [System.CommandLine] helpviewer_keywords: @@ -166,7 +166,7 @@ Options: --file The file to read and display on the conso ``` - by default provides [Help option](how-to-customize-help.md#customize-help-output), [Version option](syntax.md#version-option), and [Suggest directive](syntax.md#suggest-directive). The method is responsible for invoking the action of parsed symbol. It could be the action explicitly defined for the command, or the help action defined by `System.CommandLine` for `System.CommandLine.Help.HelpOption`. Moreover, when it detects any parse errors, it prints them to the standard error, prints help to standard output, and returns `1` as the exit code: + by default provides [Help option](how-to-customize-help.md#customize-help-output), [Version option](syntax.md#version-option), and [Suggest directive](syntax.md#suggest-directive). The method is responsible for invoking the action of parsed symbol. It could be the action explicitly defined for the command, or the help action defined by `System.CommandLine` for `System.CommandLine.Help.HelpOption`. Moreover, when it detects any parse errors, it prints them to the standard error, prints help to standard output, and returns `1` as the exit code: ```console scl --invalid bla diff --git a/docs/standard/commandline/how-to-parse-and-invoke.md b/docs/standard/commandline/how-to-parse-and-invoke.md index 8992e22c7df3d..b346f31794555 100644 --- a/docs/standard/commandline/how-to-parse-and-invoke.md +++ b/docs/standard/commandline/how-to-parse-and-invoke.md @@ -53,7 +53,7 @@ This overload of `GetValue` gets the parsed or default value for the specified s The property contains a list of parse errors that occurred during the parsing process. Each error is represented by a object, which contains information about the error, such as the error message and the token that caused the error. -When you call the method, it returns an exit code that indicates whether the parsing was successful or not. If there were any parse errors, the exit code is non-zero, and all the parse errors are printed to the standard error. +When you call the method, it returns an exit code that indicates whether the parsing was successful or not. If there were any parse errors, the exit code is non-zero, and all the parse errors are printed to the standard error. If you don't call the `ParseResult.Invoke` method, you need to handle the errors on your own, for example, by printing them: @@ -85,7 +85,7 @@ You don't need to create a derived type to define an action. You can use the ` exit code. Moreover, the that's passed to the action delegate needs to be passed further to all the methods that can be canceled, such as file I/O operations or network requests. -You also need to ensure that the method is used instead of `Invoke`. This method is asynchronous and returns a `Task` exit code. It also accepts an optional parameter that can be used to cancel the action. +You also need to ensure that the method is used instead of `Invoke`. This method is asynchronous and returns a `Task` exit code. It also accepts an optional parameter that can be used to cancel the action. The following code uses a `SetAction` overload that gets a [ParseResult](#parseresult) and a rather than just `ParseResult`: @@ -93,7 +93,7 @@ The following code uses a `SetAction` overload that gets a [ParseResult](#parser #### Process termination timeout - enables signaling and handling of process termination (Ctrl+C, `SIGINT`, `SIGTERM`) via a that's passed to every async action during invocation. It's enabled by default (2 seconds), but you can set it to `null` to disable it. + enables signaling and handling of process termination (Ctrl+C, `SIGINT`, `SIGTERM`) via a that's passed to every async action during invocation. It's enabled by default (2 seconds), but you can set it to `null` to disable it. When enabled, if the action doesn't complete within the specified timeout, the process will be terminated. This is useful for handling the termination gracefully, for example, by saving the state before the process is terminated. diff --git a/docs/standard/commandline/migration-guide-2.0.0-beta5.md b/docs/standard/commandline/migration-guide-2.0.0-beta5.md index 0b1b07ae07052..b8c2a6ffb628c 100644 --- a/docs/standard/commandline/migration-guide-2.0.0-beta5.md +++ b/docs/standard/commandline/migration-guide-2.0.0-beta5.md @@ -1,7 +1,7 @@ --- -title: System.CommandLine migration guide to 2.0.0-beta5 -description: "Learn about how to migrate to System.CommandLine 2.0.0-beta5." -ms.date: 06/19/2025 +title: System.CommandLine migration guide to 2.0.0-beta7 +description: "Learn about how to migrate to System.CommandLine 2.0.0-beta7." +ms.date: 08/13/2025 no-loc: [System.CommandLine] helpviewer_keywords: - "command line interface" @@ -9,15 +9,15 @@ helpviewer_keywords: - "System.CommandLine" --- -# System.CommandLine 2.0.0-beta5 migration guide +# System.CommandLine 2.0.0-beta7 migration guide [!INCLUDE [scl-preview](./includes/preview.md)] -The main focus for the 2.0.0-beta5 release was to improve the APIs and take a step toward releasing a stable version of System.CommandLine. The APIs have been simplified and made more coherent and consistent with the [Framework design guidelines](../design-guidelines/index.md). This article describes the breaking changes that were made in 2.0.0-beta5 and the reasoning behind them. +The main focus for the 2.0.0-beta7 release was to improve the APIs and take a step toward releasing a stable version of System.CommandLine. The APIs have been simplified and made more coherent and consistent with the [Framework design guidelines](../design-guidelines/index.md). This article describes the breaking changes that were made in 2.0.0-beta7 and the reasoning behind them. ## Renaming -In 2.0.0-beta4, not all types and members followed the [naming guidelines](../design-guidelines/naming-guidelines.md). Some were not consistent with the naming conventions, such as using the `Is` prefix for Boolean properties. In 2.0.0-beta5, some types and members have been renamed. The following table shows the old and new names: +In 2.0.0-beta4, not all types and members followed the [naming guidelines](../design-guidelines/naming-guidelines.md). Some were not consistent with the naming conventions, such as using the `Is` prefix for Boolean properties. In 2.0.0-beta7, some types and members have been renamed. The following table shows the old and new names: | Old name | New name | |--------------------------------------------------------|----------------------------------------------------------------| @@ -36,7 +36,7 @@ In 2.0.0-beta4, not all types and members followed the [naming guidelines](../de Version 2.0.0-beta4 had numerous `Add` methods that were used to add items to collections, such as arguments, options, subcommands, validators, and completions. Some of these collections were exposed via properties as read-only collections. Because of that, it was impossible to remove items from those collections. -In 2.0.0-beta5, the APIs were changed to expose mutable collections instead of `Add` methods and (sometimes) read-only collections. This allows you to not only add items or enumerate them, but also remove them. The following table shows the old method and new property names: +In 2.0.0-beta7, the APIs were changed to expose mutable collections instead of `Add` methods and (sometimes) read-only collections. This allows you to not only add items or enumerate them, but also remove them. The following table shows the old method and new property names: | Old method name | New property | |---------------------------|----------------------------------| @@ -56,7 +56,7 @@ The `RemoveAlias` and `HasAlias` methods were also removed, as the `Aliases` pro ## Names and aliases -Before 2.0.0-beta5, there was no clear separation between the name and [aliases](syntax.md#aliases) of a symbol. When `name` was not provided for the `Option` constructor, the symbol reported its name as the longest alias with prefixes like `--`, `-`, or `/` removed. That was confusing. +Before 2.0.0-beta7, there was no clear separation between the name and [aliases](syntax.md#aliases) of a symbol. When `name` was not provided for the `Option` constructor, the symbol reported its name as the longest alias with prefixes like `--`, `-`, or `/` removed. That was confusing. Moreover, to get the parsed value, users had to store a reference to an option or an argument and then use it to get the value from `ParseResult`. @@ -83,7 +83,7 @@ int number = parseResult.GetValue("--number"); In the past, `Option` exposed many constructors, some of which accepted the name. Since the name is now mandatory and aliases will frequently be provided for `Option`, there's only a single constructor. It accepts the name and a `params` array of aliases. -Before 2.0.0-beta5, `Option` had a constructor that took a name and a description. Because of that, the second argument might now be treated as an alias rather than a description. It's the only known breaking change in the API that doesn't cause a compiler error. +Before 2.0.0-beta7, `Option` had a constructor that took a name and a description. Because of that, the second argument might now be treated as an alias rather than a description. It's the only known breaking change in the API that doesn't cause a compiler error. Old code that used the constructor with a description should be updated to use the new constructor that takes a name and aliases, and then set the `Description` property separately. For example: @@ -92,7 +92,7 @@ Option beta4 = new("--help", "An option with aliases."); beta4b.Aliases.Add("-h"); beta4b.Aliases.Add("/h"); -Option beta5 = new("--help", "-h", "/h") +Option beta7 = new("--help", "-h", "/h") { Description = "An option with aliases." }; @@ -150,7 +150,7 @@ In 2.0.0-beta4, it was possible to separate the parsing and invoking of commands ### Configuration -Before 2.0.0-beta5, it was possible to customize the parsing, but only with some of the public `Parse` methods. There was a `Parser` class that exposed two public constructors: one accepting a `Command` and another accepting a `CommandLineConfiguration`. `CommandLineConfiguration` was immutable, and to create it, you had to use a builder pattern exposed by the `CommandLineBuilder` class. The following changes were made to simplify the API: +Before 2.0.0-beta7, it was possible to customize the parsing, but only with some of the public `Parse` methods. There was a `Parser` class that exposed two public constructors: one accepting a `Command` and another accepting a `CommandLineConfiguration`. `CommandLineConfiguration` was immutable, and to create it, you had to use a builder pattern exposed by the `CommandLineBuilder` class. The following changes were made to simplify the API: - `CommandLineConfiguration` was made mutable and `CommandLineBuilder` was removed. Creating a configuration is now as simple as creating an instance of `CommandLineConfiguration` and setting the properties you want to customize. Moreover, creating a new instance of configuration is the equivalent of calling `CommandLineBuilder`'s `UseDefaults` method. - Every `Parse` method now accepts an optional `CommandLineConfiguration` parameter that can be used to customize the parsing. When it's not provided, the default configuration is used. @@ -179,9 +179,9 @@ Before 2.0.0-beta5, it was possible to customize the parsing, but only with some ``` - `UseLocalizationResources` and `LocalizationResources` were removed. This feature was used mostly by the `dotnet` CLI to add missing translations to `System.CommandLine`. All those translations were moved to the System.CommandLine itself, so this feature is no longer needed. If support for your language is missing, please [report an issue](https://github.com/dotnet/command-line-api/issues/new/choose). -- `UseTokenReplacer` was removed. [Response files](syntax.md#response-files) are enabled by default, but you can disable them by setting the property to `null`. You can also provide a custom implementation to customize how response files are processed. +- `UseTokenReplacer` was removed. [Response files](syntax.md#response-files) are enabled by default, but you can disable them by setting the property to `null`. You can also provide a custom implementation to customize how response files are processed. -Last but not least, the `IConsole` and all related interfaces (`IStandardOut`, `IStandardError`, `IStandardIn`) were removed. exposes two `TextWriter` properties: and . You can set these properties to any instance, such as a `StringWriter`, which can be used to capture output for testing. The motivation for this change was to expose fewer types and reuse existing abstractions. +Last but not least, the `IConsole` and all related interfaces (`IStandardOut`, `IStandardError`, `IStandardIn`) were removed. exposes two `TextWriter` properties: and . You can set these properties to any instance, such as a `StringWriter`, which can be used to capture output for testing. The motivation for this change was to expose fewer types and reuse existing abstractions. ### Invocation @@ -233,7 +233,7 @@ For more details about how to use actions, see [How to parse and invoke commands ## The benefits of the simplified API -The changes made in 2.0.0-beta5 make the API more consistent, future-proof, and easier to use for existing and new users. +The changes made in 2.0.0-beta7 make the API more consistent, future-proof, and easier to use for existing and new users. New users need to learn fewer concepts and types, as the number of public interfaces decreased from 11 to 0, and public classes (and structs) decreased from 56 to 38. The public method count dropped from 378 to 235, and public properties from 118 to 99. From 85c060f881f91c23ad3b0c498c1252e20df4f22e Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 13 Aug 2025 17:16:37 -0700 Subject: [PATCH 2/4] more updates also to snippets --- .../how-to-configure-the-parser.md | 16 ++--- .../migration-guide-2.0.0-beta5.md | 66 ++++--------------- .../snippets/configuration/csharp/Program.cs | 12 ++-- .../snippets/configuration/csharp/scl.csproj | 2 +- 4 files changed, 25 insertions(+), 71 deletions(-) diff --git a/docs/standard/commandline/how-to-configure-the-parser.md b/docs/standard/commandline/how-to-configure-the-parser.md index 2d82fc3f87edd..c7d2bd35a0edb 100644 --- a/docs/standard/commandline/how-to-configure-the-parser.md +++ b/docs/standard/commandline/how-to-configure-the-parser.md @@ -14,41 +14,41 @@ ms.topic: how-to [!INCLUDE [scl-preview](./includes/preview.md)] - is a class that provides properties to configure the parser. It is an optional argument for every `Parse` method, such as and . When it isn't provided, the default configuration is used. + is a class that provides properties to configure the parser. It is an optional argument for every `Parse` method, such as and . When it isn't provided, the default configuration is used. has a property that returns the configuration used for parsing. ## Standard output and error -`CommandLineConfiguration` makes testing, as well as many extensibility scenarios, easier than using `System.Console`. It exposes two `TextWriter` properties: `Output` and `Error`. These can be set to any `TextWriter` instance, such as a `StringWriter`, which can be used to capture output for testing. + makes testing, as well as many extensibility scenarios, easier than using `System.Console`. It exposes two `TextWriter` properties: and . You can set these properties to any `TextWriter` instance, such as a `StringWriter`, which you can use to capture output for testing. Define a simple command that writes to standard output: :::code language="csharp" source="snippets/configuration/csharp/Program.cs" id="rootcommand"::: -Now, use `CommandLineConfiguration` to capture the output: +Now, use to capture the output: :::code language="csharp" source="snippets/configuration/csharp/Program.cs" id="captureoutput"::: ## EnablePosixBundling -[Bundling](syntax.md#option-bundling) of single-character options is enabled by default, but you can disable it by setting the `System.CommandLine.CommandLineConfiguration.EnablePosixBundling` property to `false`. +[Bundling](syntax.md#option-bundling) of single-character options is enabled by default, but you can disable it by setting the property to `false`. ## ProcessTerminationTimeout -[Process termination timeout](how-to-parse-and-invoke.md#process-termination-timeout) can be configured via the `System.CommandLine.CommandLineConfiguration.ProcessTerminationTimeout` property. The default value is 2 seconds. +[Process termination timeout](how-to-parse-and-invoke.md#process-termination-timeout) can be configured via the property. The default value is 2 seconds. ## ResponseFileTokenReplacer -[Response files](syntax.md#response-files) are enabled by default, but you can disable them by setting the `System.CommandLine.CommandLineConfiguration.ResponseFileTokenReplacer` property to `null`. You can also provide a custom implementation to customize how response files are processed. +[Response files](syntax.md#response-files) are enabled by default, but you can disable them by setting the property to `null`. You can also provide a custom implementation to customize how response files are processed. ## EnableDefaultExceptionHandler -By default, all unhandled exceptions thrown during the invocation of a command are caught and reported to the user. This behavior can be disabled by setting the `System.CommandLine.CommandLineConfiguration.EnableDefaultExceptionHandler` property to `false`. This is useful when you want to handle exceptions in a custom way, such as logging them or providing a different user experience. +By default, all unhandled exceptions thrown during the invocation of a command are caught and reported to the user. You can disable this behavior by setting the property to `false`. This is useful when you want to handle exceptions in a custom way, such as logging them or providing a different user experience. ## Derived classes -`System.CommandLine.CommandLineConfiguration` is not sealed, so you can derive from it to add custom properties or methods. This is useful when you want to provide additional configuration options specific to your application. + is not sealed, so you can derive from it to add custom properties or methods. This is useful when you want to provide additional configuration options specific to your application. ## See also diff --git a/docs/standard/commandline/migration-guide-2.0.0-beta5.md b/docs/standard/commandline/migration-guide-2.0.0-beta5.md index b8c2a6ffb628c..ca2563d94a0c7 100644 --- a/docs/standard/commandline/migration-guide-2.0.0-beta5.md +++ b/docs/standard/commandline/migration-guide-2.0.0-beta5.md @@ -150,24 +150,24 @@ In 2.0.0-beta4, it was possible to separate the parsing and invoking of commands ### Configuration -Before 2.0.0-beta7, it was possible to customize the parsing, but only with some of the public `Parse` methods. There was a `Parser` class that exposed two public constructors: one accepting a `Command` and another accepting a `CommandLineConfiguration`. `CommandLineConfiguration` was immutable, and to create it, you had to use a builder pattern exposed by the `CommandLineBuilder` class. The following changes were made to simplify the API: +Before 2.0.0-beta5, it was possible to customize the parsing, but only with some of the public `Parse` methods. There was a `Parser` class that exposed two public constructors: one accepting a `Command` and another accepting a `CommandLineConfiguration`. `CommandLineConfiguration` was immutable, and to create it, you had to use a builder pattern exposed by the `CommandLineBuilder` class. The following changes were made to simplify the API: -- `CommandLineConfiguration` was made mutable and `CommandLineBuilder` was removed. Creating a configuration is now as simple as creating an instance of `CommandLineConfiguration` and setting the properties you want to customize. Moreover, creating a new instance of configuration is the equivalent of calling `CommandLineBuilder`'s `UseDefaults` method. -- Every `Parse` method now accepts an optional `CommandLineConfiguration` parameter that can be used to customize the parsing. When it's not provided, the default configuration is used. -- `Parser` was renamed to `CommandLineParser` to disambiguate from other parser types to avoid name conflicts. Since it's stateless, it's now a static class with only static methods. It exposes two `Parse` parse methods: one accepting a `IReadOnlyList args` and another accepting a `string args`. The latter uses `CommandLineParser.SplitCommandLine` (also public) to split the command line input into [tokens](syntax.md#tokens) before parsing it. +- `CommandLineConfiguration` was split into two *mutable* classes (in beta7): and . Creating an invocation configuration is now as simple as creating an instance of `InvocationConfiguration` and setting the properties you want to customize. +- Every `Parse` method now accepts an optional parameter that can be used to customize the parsing. When it's not provided, the default configuration is used. +- To avoid name conflicts, `Parser` was renamed to to disambiguate from other parser types. Since it's stateless, it's now a static class with only static methods. It exposes two `Parse` parse methods: one accepting an `IReadOnlyList args` and another accepting a `string args`. The latter uses `CommandLineParser.SplitCommandLine` (also public) to split the command line input into [tokens](syntax.md#tokens) before parsing it. `CommandLineBuilderExtensions` was also removed. Here is how you can map its methods to the new APIs: -- `CancelOnProcessTermination` is now a property of `CommandLineConfiguration` called [ProcessTerminationTimeout](how-to-parse-and-invoke.md#process-termination-timeout). It's enabled by default, with a 2s timeout. Set it to `null` to disable it. -- `EnableDirectives`, `UseEnvironmentVariableDirective`, `UseParseDirective`, and `UseSuggestDirective` were removed. A new [Directive](syntax.md#directives) type was introduced and the [RootCommand](syntax.md#root-command) now exposes `System.CommandLine.RootCommand.Directives` property. You can add, remove, and iterate directives by using this collection. [Suggest directive](syntax.md#suggest-directive) is included by default; you can also use other directives like [DiagramDirective](syntax.md#the-diagram-directive) or `EnvironmentVariablesDirective`. -- `EnableLegacyDoubleDashBehavior` was removed. All unmatched tokens are now exposed by the [ParseResult.UnmatchedTokens](how-to-parse-and-invoke.md#unmatched-tokens) property. -- `EnablePosixBundling` was removed. The bundling is now enabled by default, you can disable it by setting the [CommandLineConfiguration.EnableBundling](how-to-configure-the-parser.md#enableposixbundling) property to `false`. +- `CancelOnProcessTermination` is now a property of called [ProcessTerminationTimeout](how-to-parse-and-invoke.md#process-termination-timeout). It's enabled by default, with a 2 second timeout. To disable it, set it to `null`. +- `EnableDirectives`, `UseEnvironmentVariableDirective`, `UseParseDirective`, and `UseSuggestDirective` were removed. A new [Directive](syntax.md#directives) type was introduced and [RootCommand](syntax.md#root-command) now exposes a property. You can add, remove, and iterate directives by using this collection. [Suggest directive](syntax.md#suggest-directive) is included by default; you can also use other directives like [DiagramDirective](syntax.md#the-diagram-directive) or . +- `EnableLegacyDoubleDashBehavior` was removed. All unmatched tokens are now exposed by the property. For more information, see [Unmatched tokens](how-to-parse-and-invoke.md#unmatched-tokens). +- `EnablePosixBundling` was removed. The bundling is now enabled by default, you can disable it by setting the property to `false`. For more information, see [EnablePosixBundling](how-to-configure-the-parser.md#enableposixbundling). - `RegisterWithDotnetSuggest` was removed as it performed an expensive operation, typically during application startup. Now you must register commands with `dotnet suggest` [manually](how-to-enable-tab-completion.md#enable-tab-completion). -- `UseExceptionHandler` was removed. The default exception handler is now enabled by default, you can disable it by setting the [CommandLineConfiguration.EnableDefaultExceptionHandler](how-to-configure-the-parser.md#enabledefaultexceptionhandler) property to `false`. This is useful when you want to handle exceptions in a custom way, by just wrapping the `Invoke` or `InvokeAsync` methods in a try-catch block. -- `UseHelp` and `UseVersion` were removed. The help and version are now exposed by the [HelpOption](how-to-customize-help.md#customize-help-output) and [VersionOption](syntax.md#version-option) public types. They are both included by default in the options defined by [RootCommand](syntax.md#root-command). +- `UseExceptionHandler` was removed. The default exception handler is now enabled by default; you can disable it by setting the property to `false`. This is useful when you want to handle exceptions in a custom way, by just wrapping the `Invoke` or `InvokeAsync` methods in a try-catch block. For more information, see [EnableDefaultExceptionHandler](how-to-configure-the-parser.md#enabledefaultexceptionhandler). +- `UseHelp` and `UseVersion` were removed. The help and version are now exposed by the and public types. They are both included by default in the options defined by [RootCommand](syntax.md#root-command). For more information, see [Customize help output](how-to-customize-help.md#customize-help-output) and [Version option](syntax.md#version-option). - `UseHelpBuilder` was removed. For more information on how to customize the help output, see [How to customize help in System.CommandLine](how-to-customize-help.md). - `AddMiddleware` was removed. It slowed down the application startup, and features can be expressed without it. -- `UseParseErrorReporting` and `UseTypoCorrections` were removed. The parse errors are now reported by default when invoking `ParseResult`. You can configure it by using the action exposed by `ParseResult.Action` property. +- `UseParseErrorReporting` and `UseTypoCorrections` were removed. The parse errors are now reported by default when invoking `ParseResult`. You can configure it by using the action exposed by the property. ```csharp ParseResult result = rootCommand.Parse("myArgs", config); @@ -254,49 +254,7 @@ System.Runtime - System.Threading ``` -It allowed us to reduce the size of the library by 32% and the size of the following NativeAOT app by 20%: - -```csharp -Option boolOption = new Option(new[] { "--bool", "-b" }, "Bool option"); -Option stringOption = new Option(new[] { "--string", "-s" }, "String option"); - -RootCommand command = new RootCommand -{ - boolOption, - stringOption -}; - -command.SetHandler(Run, boolOption, stringOption); - -return new CommandLineBuilder(command).UseDefaults().Build().Invoke(args); - -static void Run(bool boolean, string text) -{ - Console.WriteLine($"Bool option: {text}"); - Console.WriteLine($"String option: {boolean}"); -} -``` - -```csharp -Option boolOption = new Option("--bool", "-b") { Description = "Bool option" }; -Option stringOption = new Option("--string", "-s") { Description = "String option" }; - -RootCommand command = new () -{ - boolOption, - stringOption, -}; - -command.SetAction(parseResult => Run(parseResult.GetValue(boolOption), parseResult.GetValue(stringOption))); - -return new CommandLineConfiguration(command).Invoke(args); - -static void Run(bool boolean, string text) -{ - Console.WriteLine($"Bool option: {text}"); - Console.WriteLine($"String option: {boolean}"); -} -``` +The size of the library is reduced (by 32%) and so is the size of NativeAOT apps that use the library. Simplicity has also improved the performance of the library (it's a side effect of the work, not the main goal of it). The [benchmarks](https://github.com/adamsitnik/commandline-perf/tree/update) show that the parsing and invoking of commands is now faster than in 2.0.0-beta4, especially for large commands with many options and arguments. The performance improvements are visible in both synchronous and asynchronous scenarios. diff --git a/docs/standard/commandline/snippets/configuration/csharp/Program.cs b/docs/standard/commandline/snippets/configuration/csharp/Program.cs index ba0bc8947fc7e..6897b72089be3 100644 --- a/docs/standard/commandline/snippets/configuration/csharp/Program.cs +++ b/docs/standard/commandline/snippets/configuration/csharp/Program.cs @@ -20,19 +20,15 @@ static void Main(string[] args) rootCommand.SetAction((parseResult) => { FileInfo? fileOptionValue = parseResult.GetValue(fileOption); - parseResult.Configuration.Output.WriteLine($"File option value: {fileOptionValue?.FullName}"); + parseResult.InvocationConfiguration.Output.WriteLine( + $"File option value: {fileOptionValue?.FullName}" + ); }); // // StringWriter output = new(); - CommandLineConfiguration configuration = new(rootCommand) - { - Output = output, - Error = TextWriter.Null - }; - - configuration.Parse("-h").Invoke(); + rootCommand.Parse("-h").Invoke(new() { Output = output }); Debug.Assert(output.ToString().Contains("Configuration sample")); // } diff --git a/docs/standard/commandline/snippets/configuration/csharp/scl.csproj b/docs/standard/commandline/snippets/configuration/csharp/scl.csproj index 3fa68e1bda8bd..88b6015180700 100644 --- a/docs/standard/commandline/snippets/configuration/csharp/scl.csproj +++ b/docs/standard/commandline/snippets/configuration/csharp/scl.csproj @@ -8,7 +8,7 @@ - + From 7f21d80143e6fa0fd2c32d565d3cc26310921c73 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:43:25 -0700 Subject: [PATCH 3/4] more improvements --- .../migration-guide-2.0.0-beta5.md | 63 ++++++++++--------- .../snippets/configuration/csharp/scl.csproj | 2 +- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/docs/standard/commandline/migration-guide-2.0.0-beta5.md b/docs/standard/commandline/migration-guide-2.0.0-beta5.md index ca2563d94a0c7..98e6633afaf91 100644 --- a/docs/standard/commandline/migration-guide-2.0.0-beta5.md +++ b/docs/standard/commandline/migration-guide-2.0.0-beta5.md @@ -1,6 +1,6 @@ --- -title: System.CommandLine migration guide to 2.0.0-beta7 -description: "Learn about how to migrate to System.CommandLine 2.0.0-beta7." +title: System.CommandLine migration guide to 2.0.0-beta5+ +description: "Learn about how to migrate to System.CommandLine 2.0.0-beta5+." ms.date: 08/13/2025 no-loc: [System.CommandLine] helpviewer_keywords: @@ -9,15 +9,15 @@ helpviewer_keywords: - "System.CommandLine" --- -# System.CommandLine 2.0.0-beta7 migration guide +# System.CommandLine 2.0.0-beta5+ migration guide [!INCLUDE [scl-preview](./includes/preview.md)] -The main focus for the 2.0.0-beta7 release was to improve the APIs and take a step toward releasing a stable version of System.CommandLine. The APIs have been simplified and made more coherent and consistent with the [Framework design guidelines](../design-guidelines/index.md). This article describes the breaking changes that were made in 2.0.0-beta7 and the reasoning behind them. +The main focus for the 2.0.0-beta5 release was to improve the APIs and take a step toward releasing a stable version of System.CommandLine. The APIs have been simplified and made more coherent and consistent with the [Framework design guidelines](../design-guidelines/index.md). This article describes the breaking changes that were made in 2.0.0-beta5 and 2.0.0-beta7, and the reasoning behind them. ## Renaming -In 2.0.0-beta4, not all types and members followed the [naming guidelines](../design-guidelines/naming-guidelines.md). Some were not consistent with the naming conventions, such as using the `Is` prefix for Boolean properties. In 2.0.0-beta7, some types and members have been renamed. The following table shows the old and new names: +In 2.0.0-beta4, not all types and members followed the [naming guidelines](../design-guidelines/naming-guidelines.md). Some weren't consistent with the naming conventions, such as using the `Is` prefix for Boolean properties. In 2.0.0-beta5, some types and members have been renamed. The following table shows the old and new names: | Old name | New name | |--------------------------------------------------------|----------------------------------------------------------------| @@ -36,7 +36,7 @@ In 2.0.0-beta4, not all types and members followed the [naming guidelines](../de Version 2.0.0-beta4 had numerous `Add` methods that were used to add items to collections, such as arguments, options, subcommands, validators, and completions. Some of these collections were exposed via properties as read-only collections. Because of that, it was impossible to remove items from those collections. -In 2.0.0-beta7, the APIs were changed to expose mutable collections instead of `Add` methods and (sometimes) read-only collections. This allows you to not only add items or enumerate them, but also remove them. The following table shows the old method and new property names: +In 2.0.0-beta5, the APIs were changed to expose mutable collections instead of `Add` methods and (sometimes) read-only collections. This allows you to not only add items or enumerate them, but also remove them. The following table shows the old method and new property names: | Old method name | New property | |---------------------------|----------------------------------| @@ -56,14 +56,14 @@ The `RemoveAlias` and `HasAlias` methods were also removed, as the `Aliases` pro ## Names and aliases -Before 2.0.0-beta7, there was no clear separation between the name and [aliases](syntax.md#aliases) of a symbol. When `name` was not provided for the `Option` constructor, the symbol reported its name as the longest alias with prefixes like `--`, `-`, or `/` removed. That was confusing. +Before 2.0.0-beta5, there was no clear separation between the name and [aliases](syntax.md#aliases) of a symbol. When `name` wasn't provided for the `Option` constructor, the symbol reported its name as the longest alias with prefixes like `--`, `-`, or `/` removed. That was confusing. -Moreover, to get the parsed value, users had to store a reference to an option or an argument and then use it to get the value from `ParseResult`. +Moreover, to get the parsed value, you had to store a reference to an option or an argument and then use it to get the value from `ParseResult`. To promote simplicity and explicitness, the name of a symbol is now a mandatory parameter for every symbol constructor (including `Argument`). The concept of a name and aliases is now separate: aliases are just aliases and don't include the name of the symbol. Of course, they're optional. As a result, the following changes were made: - `name` is now a mandatory argument for every public constructor of , , and . In the case of `Argument`, it isn't used for parsing, but to generate the help. In the case of `Option` and `Command`, it's used to identify the symbol during parsing and also for help and completions. -- The `Symbol.Name` property is no longer `virtual`; it's now read-only and returns the name as it was provided when the symbol was created. Because of that, `Symbol.DefaultName` was removed and `Option.Name` no longer removes the `--`, `-`, or `/` or any other prefix from the longest alias. +- The property is no longer `virtual`; it's now read-only and returns the name as it was provided when the symbol was created. Because of that, `Symbol.DefaultName` was removed and no longer removes the `--`, `-`, or `/` or any other prefix from the longest alias. - The `Aliases` property exposed by [`Option`](xref:System.CommandLine.Option.Aliases) and [`Command`](xref:System.CommandLine.Command.Aliases) is now a mutable collection. This collection no longer includes the name of the symbol. - `System.CommandLine.Parsing.IdentifierSymbol` was removed (it was a base type for both `Command` and `Option`). @@ -81,18 +81,18 @@ int number = parseResult.GetValue("--number"); ### Creating options with aliases -In the past, `Option` exposed many constructors, some of which accepted the name. Since the name is now mandatory and aliases will frequently be provided for `Option`, there's only a single constructor. It accepts the name and a `params` array of aliases. +In the past, exposed many constructors, some of which accepted the name. Since the name is now mandatory and aliases will frequently be provided for `Option`, there's only a single constructor. It accepts the name and a `params` array of aliases. -Before 2.0.0-beta7, `Option` had a constructor that took a name and a description. Because of that, the second argument might now be treated as an alias rather than a description. It's the only known breaking change in the API that doesn't cause a compiler error. - -Old code that used the constructor with a description should be updated to use the new constructor that takes a name and aliases, and then set the `Description` property separately. For example: +> [!NOTE] +> Before 2.0.0-beta5, `Option` had a constructor that took a name and a description. Because of that, the second argument might now be treated as an alias rather than a description. It's the only known breaking change in the API that doesn't cause a compiler error. +> Update any code that passed a description to the constructor to use the new constructor that takes a name and aliases, and then set the `Description` property separately. For example: ```csharp Option beta4 = new("--help", "An option with aliases."); beta4b.Aliases.Add("-h"); beta4b.Aliases.Add("/h"); -Option beta7 = new("--help", "-h", "/h") +Option beta5 = new("--help", "-h", "/h") { Description = "An option with aliases." }; @@ -100,16 +100,17 @@ Option beta7 = new("--help", "-h", "/h") ## Default values and custom parsing -In 2.0.0-beta4, you could set default values for options and arguments by using the `SetDefaultValue` methods. Those methods accepted an `object` value, which wasn't type-safe and could lead to run-time errors if the value was not compatible with the option or argument type: +In 2.0.0-beta4, you could set default values for options and arguments by using the `SetDefaultValue` methods. Those methods accepted an `object` value, which wasn't type safe and could lead to run-time errors if the value wasn't compatible with the option or argument type: ```csharp Option option = new("--number"); -option.SetDefaultValue("text"); // This is not type-safe, as the value is a string, not an int. +// This is not type safe, as the value is a string, not an int: +option.SetDefaultValue("text"); ``` -Moreover, some of the `Option` and `Argument` constructors accepted a parse delegate and a Boolean indicating whether the delegate was a custom parser or a default value provider. This was confusing. +Moreover, some of the `Option` and `Argument` constructors accepted a parse delegate and a Boolean indicating whether the delegate was a custom parser or a default value provider, which was confusing. -`Option` and `Argument` classes now have a property that can be used to set a delegate that can be called to get the default value for the option or argument. This delegate is invoked when the option or argument is not found in the parsed command line input. +`Option` and `Argument` classes now have a property that you can use to set a delegate that can be called to get the default value for the option or argument. This delegate is invoked when the option or argument isn't found in the parsed command-line input. ```csharp Option number = new("--number") @@ -118,7 +119,7 @@ Option number = new("--number") }; ``` -`Argument` and `Option` also come with a property that can be used to set a custom parser for the symbol: +`Argument` and `Option` also come with a property that you can use to set a custom parser for the symbol: ```csharp Argument uri = new("arg") @@ -140,25 +141,25 @@ Moreover, `CustomParser` accepts a delegate of type `Func`, rathe For more examples of how to use `DefaultValueFactory` and `CustomParser`, see [How to customize parsing and validation in System.CommandLine](how-to-customize-parsing-and-validation.md). -## The separation of parsing and invocation +## Separation of parsing and invocation -In 2.0.0-beta4, it was possible to separate the parsing and invoking of commands, but it wasn't clear how to do it. `Command` did not expose a `Parse` method, but `CommandExtensions` provided `Parse`, `Invoke`, and `InvokeAsync` extension methods for `Command`. This was confusing, as it was not clear which method to use and when. The following changes were made to simplify the API: +In 2.0.0-beta4, it was possible to separate the parsing and invoking of commands, but it wasn't clear how to do it. `Command` didn't expose a `Parse` method, but `CommandExtensions` provided `Parse`, `Invoke`, and `InvokeAsync` extension methods for `Command`. This was confusing, as it wasn't clear which method to use and when. The following changes were made to simplify the API: -- `Command` now exposes a `Parse` method that returns a `ParseResult` object. This method is used to parse the command line input and return the result of the parse operation. Moreover, it makes it clear that the command is not invoked, but only parsed and only in synchronous manner. -- `ParseResult` now exposes both `Invoke` and `InvokeAsync` methods that can be used to invoke the command. This makes it clear that the command is invoked after parsing, and allows for both synchronous and asynchronous invocation. +- now exposes a `Parse` method that returns a `ParseResult` object. This method is used to parse the command-line input and return the result of the parse operation. Moreover, it makes it clear that the command isn't invoked but parsed, and only in synchronous manner. +- `ParseResult` now exposes both `Invoke` and `InvokeAsync` methods that you can use to invoke the command. This pattern makes it clear that the command is invoked after parsing, and allows for both synchronous and asynchronous invocation. - The `CommandExtensions` class was removed, as it's no longer needed. ### Configuration Before 2.0.0-beta5, it was possible to customize the parsing, but only with some of the public `Parse` methods. There was a `Parser` class that exposed two public constructors: one accepting a `Command` and another accepting a `CommandLineConfiguration`. `CommandLineConfiguration` was immutable, and to create it, you had to use a builder pattern exposed by the `CommandLineBuilder` class. The following changes were made to simplify the API: -- `CommandLineConfiguration` was split into two *mutable* classes (in beta7): and . Creating an invocation configuration is now as simple as creating an instance of `InvocationConfiguration` and setting the properties you want to customize. -- Every `Parse` method now accepts an optional parameter that can be used to customize the parsing. When it's not provided, the default configuration is used. -- To avoid name conflicts, `Parser` was renamed to to disambiguate from other parser types. Since it's stateless, it's now a static class with only static methods. It exposes two `Parse` parse methods: one accepting an `IReadOnlyList args` and another accepting a `string args`. The latter uses `CommandLineParser.SplitCommandLine` (also public) to split the command line input into [tokens](syntax.md#tokens) before parsing it. +- `CommandLineConfiguration` was split into two *mutable* classes (in 2.0.0-beta7): and . Creating an invocation configuration is now as simple as creating an instance of `InvocationConfiguration` and setting the properties you want to customize. +- Every `Parse` method now accepts an optional parameter that you can use to customize the parsing. When it isn't provided, the default configuration is used. +- To avoid name conflicts, `Parser` was renamed to to disambiguate from other parser types. Since it's stateless, it's now a static class with only static methods. It exposes two `Parse` parse methods: one accepting an `IReadOnlyList args` and another accepting a `string args`. The latter uses (also public) to split the command line input into [tokens](syntax.md#tokens) before parsing it. `CommandLineBuilderExtensions` was also removed. Here is how you can map its methods to the new APIs: -- `CancelOnProcessTermination` is now a property of called [ProcessTerminationTimeout](how-to-parse-and-invoke.md#process-termination-timeout). It's enabled by default, with a 2 second timeout. To disable it, set it to `null`. +- `CancelOnProcessTermination` is now a property of called . It's enabled by default, with a 2 second timeout. To disable it, set it to `null`. For more information, see [Process termination timeout](how-to-parse-and-invoke.md#process-termination-timeout). - `EnableDirectives`, `UseEnvironmentVariableDirective`, `UseParseDirective`, and `UseSuggestDirective` were removed. A new [Directive](syntax.md#directives) type was introduced and [RootCommand](syntax.md#root-command) now exposes a property. You can add, remove, and iterate directives by using this collection. [Suggest directive](syntax.md#suggest-directive) is included by default; you can also use other directives like [DiagramDirective](syntax.md#the-diagram-directive) or . - `EnableLegacyDoubleDashBehavior` was removed. All unmatched tokens are now exposed by the property. For more information, see [Unmatched tokens](how-to-parse-and-invoke.md#unmatched-tokens). - `EnablePosixBundling` was removed. The bundling is now enabled by default, you can disable it by setting the property to `false`. For more information, see [EnablePosixBundling](how-to-configure-the-parser.md#enableposixbundling). @@ -181,7 +182,7 @@ Before 2.0.0-beta5, it was possible to customize the parsing, but only with some - `UseLocalizationResources` and `LocalizationResources` were removed. This feature was used mostly by the `dotnet` CLI to add missing translations to `System.CommandLine`. All those translations were moved to the System.CommandLine itself, so this feature is no longer needed. If support for your language is missing, please [report an issue](https://github.com/dotnet/command-line-api/issues/new/choose). - `UseTokenReplacer` was removed. [Response files](syntax.md#response-files) are enabled by default, but you can disable them by setting the property to `null`. You can also provide a custom implementation to customize how response files are processed. -Last but not least, the `IConsole` and all related interfaces (`IStandardOut`, `IStandardError`, `IStandardIn`) were removed. exposes two `TextWriter` properties: and . You can set these properties to any instance, such as a `StringWriter`, which can be used to capture output for testing. The motivation for this change was to expose fewer types and reuse existing abstractions. +Last but not least, the `IConsole` and all related interfaces (`IStandardOut`, `IStandardError`, `IStandardIn`) were removed. exposes two `TextWriter` properties: and . You can set these properties to any instance, such as a `StringWriter`, which you can use to capture output for testing. The motivation for this change was to expose fewer types and reuse existing abstractions. ### Invocation @@ -225,15 +226,15 @@ As a result of these and other aforementioned changes, the `InvocationContext` c To summarize these changes: - The `ICommandHandler` interface was removed. `SynchronousCommandLineAction` and `AsynchronousCommandLineAction` were introduced. -- The `Command.SetHandler` method was renamed to `SetAction`. -- The `Command.Handler` property was renamed to `Command.Action`. `Option` was extended with `Option.Action`. +- The `Command.SetHandler` method was renamed to . +- The `Command.Handler` property was renamed to . `Option` was extended with . - `InvocationContext` was removed. The `ParseResult` is now passed directly to the action. For more details about how to use actions, see [How to parse and invoke commands in System.CommandLine](how-to-parse-and-invoke.md). ## The benefits of the simplified API -The changes made in 2.0.0-beta7 make the API more consistent, future-proof, and easier to use for existing and new users. +The changes made in 2.0.0-beta5 make the API more consistent, future-proof, and easier to use for existing and new users. New users need to learn fewer concepts and types, as the number of public interfaces decreased from 11 to 0, and public classes (and structs) decreased from 56 to 38. The public method count dropped from 378 to 235, and public properties from 118 to 99. diff --git a/docs/standard/commandline/snippets/configuration/csharp/scl.csproj b/docs/standard/commandline/snippets/configuration/csharp/scl.csproj index 88b6015180700..16f8e10d4dda2 100644 --- a/docs/standard/commandline/snippets/configuration/csharp/scl.csproj +++ b/docs/standard/commandline/snippets/configuration/csharp/scl.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 enable enable From 47b4dca35dec7e93027287765766ef1c9436203d Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 13 Aug 2025 20:45:55 -0700 Subject: [PATCH 4/4] unindent --- docs/standard/commandline/migration-guide-2.0.0-beta5.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/standard/commandline/migration-guide-2.0.0-beta5.md b/docs/standard/commandline/migration-guide-2.0.0-beta5.md index 98e6633afaf91..db04358698e38 100644 --- a/docs/standard/commandline/migration-guide-2.0.0-beta5.md +++ b/docs/standard/commandline/migration-guide-2.0.0-beta5.md @@ -83,9 +83,9 @@ int number = parseResult.GetValue("--number"); In the past, exposed many constructors, some of which accepted the name. Since the name is now mandatory and aliases will frequently be provided for `Option`, there's only a single constructor. It accepts the name and a `params` array of aliases. -> [!NOTE] -> Before 2.0.0-beta5, `Option` had a constructor that took a name and a description. Because of that, the second argument might now be treated as an alias rather than a description. It's the only known breaking change in the API that doesn't cause a compiler error. -> Update any code that passed a description to the constructor to use the new constructor that takes a name and aliases, and then set the `Description` property separately. For example: +Before 2.0.0-beta5, `Option` had a constructor that took a name and a description. Because of that, the second argument might now be treated as an alias rather than a description. It's the only known breaking change in the API that doesn't cause a compiler error. + +Update any code that passed a description to the constructor to use the new constructor that takes a name and aliases, and then set the `Description` property separately. For example: ```csharp Option beta4 = new("--help", "An option with aliases.");