mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 17:15:28 +08:00 
			
		
		
		
	fixed line-endings
This commit is contained in:
		 Nils Andresen
					Nils Andresen
				
			
				
					committed by
					
						 Patrik Svensson
						Patrik Svensson
					
				
			
			
				
	
			
			
			 Patrik Svensson
						Patrik Svensson
					
				
			
						parent
						
							989c0b9904
						
					
				
				
					commit
					44300c871f
				
			
							
								
								
									
										154
									
								
								docs/Program.cs
									
									
									
									
									
								
							
							
						
						
									
										154
									
								
								docs/Program.cs
									
									
									
									
									
								
							| @@ -1,77 +1,77 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Threading.Tasks; | ||||
| using Docs.Extensions; | ||||
| using Docs.Shortcodes; | ||||
| using Docs.Utilities; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Statiq.App; | ||||
| using Statiq.Common; | ||||
| using Statiq.Core; | ||||
| using Statiq.Web; | ||||
|  | ||||
| namespace Docs | ||||
| { | ||||
|     public static class Program | ||||
|     { | ||||
|         public static async Task<int> Main(string[] args) => | ||||
|             await Bootstrapper.Factory | ||||
|                 .CreateWeb(args) | ||||
|                 .AddSetting(Keys.Host, "spectreconsole.net") | ||||
|                 .AddSetting(Keys.LinksUseHttps, true) | ||||
|                 .AddSetting(Constants.EditLink, ConfigureEditLink()) | ||||
|                 .AddSetting(Constants.SourceFiles, new List<string> | ||||
|                 { | ||||
|                     "../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", | ||||
|                     "../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", | ||||
|                     "../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", | ||||
|                     "../../src/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs" | ||||
|                 }) | ||||
|                 .AddSetting(Constants.ExampleSourceFiles, new List<string> | ||||
|                     { | ||||
|                         "../../examples/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", | ||||
|                     } | ||||
|                 ) | ||||
|                 .ConfigureServices(i => | ||||
|                 { | ||||
|                     i.AddSingleton(new TypeNameLinks()); | ||||
|                 }) | ||||
|                 .ConfigureSite("spectreconsole", "spectre.console", "main") | ||||
|                 .AddShortcode("Children", typeof(ChildrenShortcode)) | ||||
|                 .AddShortcode("ColorTable", typeof(ColorTableShortcode)) | ||||
|                 .AddShortcode("EmojiTable", typeof(EmojiTableShortcode)) | ||||
|                 .AddShortcode("Alert", typeof(AlertShortcode)) | ||||
|                 .AddShortcode("Info", typeof(InfoShortcode)) | ||||
|                 .AddShortcode("AsciiCast", typeof(AsciiCastShortcode)) | ||||
|                 .AddShortcode("Example", typeof(ExampleSnippet)) | ||||
|                 .AddPipelines() | ||||
|                 .BuildPipeline( | ||||
| 			        "Bootstrap", | ||||
| 			            builder => builder | ||||
| 				            .WithInputReadFiles("../node_modules/asciinema-player/dist/bundle/asciinema-player.js") | ||||
| 				            .WithProcessModules(new SetDestination(Config.FromDocument(doc => new NormalizedPath($"./assets/{doc.Source.FileName}")), true)) | ||||
| 				    .WithOutputWriteFiles() | ||||
|                 ) | ||||
|                 .AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("npm", "install --audit false --fund false --progress false") | ||||
|                 { | ||||
|                     LogErrors = false | ||||
|                 }) | ||||
|                 .AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("dotnet", "playwright install chromium")) | ||||
|                 .AddProcess(ProcessTiming.BeforeDeployment, _ => new ProcessLauncher("npm", "run build:tailwind") | ||||
|                 { | ||||
|                     LogErrors = false | ||||
|                 }) | ||||
|                 .RunAsync(); | ||||
|  | ||||
|         private static Config<string> ConfigureEditLink() | ||||
|         { | ||||
|             return Config.FromDocument((doc, ctx) => | ||||
|             { | ||||
|                 return string.Format("https://github.com/{0}/{1}/edit/{2}/docs/input/{3}", | ||||
|                     ctx.GetString(Constants.Site.Owner), | ||||
|                     ctx.GetString(Constants.Site.Repository), | ||||
|                     ctx.GetString(Constants.Site.Branch), | ||||
|                     doc.Source.GetRelativeInputPath()); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| using System.Collections.Generic; | ||||
| using System.Threading.Tasks; | ||||
| using Docs.Extensions; | ||||
| using Docs.Shortcodes; | ||||
| using Docs.Utilities; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Statiq.App; | ||||
| using Statiq.Common; | ||||
| using Statiq.Core; | ||||
| using Statiq.Web; | ||||
|  | ||||
| namespace Docs | ||||
| { | ||||
|     public static class Program | ||||
|     { | ||||
|         public static async Task<int> Main(string[] args) => | ||||
|             await Bootstrapper.Factory | ||||
|                 .CreateWeb(args) | ||||
|                 .AddSetting(Keys.Host, "spectreconsole.net") | ||||
|                 .AddSetting(Keys.LinksUseHttps, true) | ||||
|                 .AddSetting(Constants.EditLink, ConfigureEditLink()) | ||||
|                 .AddSetting(Constants.SourceFiles, new List<string> | ||||
|                 { | ||||
|                     "../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", | ||||
|                     "../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", | ||||
|                     "../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", | ||||
|                     "../../src/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs" | ||||
|                 }) | ||||
|                 .AddSetting(Constants.ExampleSourceFiles, new List<string> | ||||
|                     { | ||||
|                         "../../examples/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", | ||||
|                     } | ||||
|                 ) | ||||
|                 .ConfigureServices(i => | ||||
|                 { | ||||
|                     i.AddSingleton(new TypeNameLinks()); | ||||
|                 }) | ||||
|                 .ConfigureSite("spectreconsole", "spectre.console", "main") | ||||
|                 .AddShortcode("Children", typeof(ChildrenShortcode)) | ||||
|                 .AddShortcode("ColorTable", typeof(ColorTableShortcode)) | ||||
|                 .AddShortcode("EmojiTable", typeof(EmojiTableShortcode)) | ||||
|                 .AddShortcode("Alert", typeof(AlertShortcode)) | ||||
|                 .AddShortcode("Info", typeof(InfoShortcode)) | ||||
|                 .AddShortcode("AsciiCast", typeof(AsciiCastShortcode)) | ||||
|                 .AddShortcode("Example", typeof(ExampleSnippet)) | ||||
|                 .AddPipelines() | ||||
|                 .BuildPipeline( | ||||
| 			        "Bootstrap", | ||||
| 			            builder => builder | ||||
| 				            .WithInputReadFiles("../node_modules/asciinema-player/dist/bundle/asciinema-player.js") | ||||
| 				            .WithProcessModules(new SetDestination(Config.FromDocument(doc => new NormalizedPath($"./assets/{doc.Source.FileName}")), true)) | ||||
| 				    .WithOutputWriteFiles() | ||||
|                 ) | ||||
|                 .AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("npm", "install --audit false --fund false --progress false") | ||||
|                 { | ||||
|                     LogErrors = false | ||||
|                 }) | ||||
|                 .AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("dotnet", "playwright install chromium")) | ||||
|                 .AddProcess(ProcessTiming.BeforeDeployment, _ => new ProcessLauncher("npm", "run build:tailwind") | ||||
|                 { | ||||
|                     LogErrors = false | ||||
|                 }) | ||||
|                 .RunAsync(); | ||||
|  | ||||
|         private static Config<string> ConfigureEditLink() | ||||
|         { | ||||
|             return Config.FromDocument((doc, ctx) => | ||||
|             { | ||||
|                 return string.Format("https://github.com/{0}/{1}/edit/{2}/docs/input/{3}", | ||||
|                     ctx.GetString(Constants.Site.Owner), | ||||
|                     ctx.GetString(Constants.Site.Repository), | ||||
|                     ctx.GetString(Constants.Site.Branch), | ||||
|                     doc.Source.GetRelativeInputPath()); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ Description: "*Spectre.Console* makes it easy to write text with different style | ||||
| Highlights: | ||||
|     - Bold, Italic, Underline, strikethrough | ||||
|     - Dim, Invert | ||||
|     - Conceal, slowblink, rapidblink | ||||
|     - Conceal, slowblink, rapidblink | ||||
|     - Links | ||||
| --- | ||||
|  | ||||
| @@ -46,9 +46,9 @@ Note that what styles that can be used is defined by the system or your terminal | ||||
|     <tr> | ||||
|         <td><code>strikethrough</code></td> | ||||
|         <td>Shows text with a horizontal line through the center</td> | ||||
|     </tr> | ||||
|     <tr> | ||||
|         <td><code>link</link></td> | ||||
|         <td>Creates a clickable link within text</td> | ||||
|     </tr> | ||||
|     <tr> | ||||
|         <td><code>link</link></td> | ||||
|         <td>Creates a clickable link within text</td> | ||||
|     </tr> | ||||
| </table> | ||||
| @@ -1,47 +1,47 @@ | ||||
| Title: Command Help | ||||
| Order: 13 | ||||
| Description: "Console applications built with *Spectre.Console.Cli* include automatically generated help command line help." | ||||
| --- | ||||
|  | ||||
| Console applications built with `Spectre.Console.Cli` include automatically generated help which is displayed when `-h` or `--help` has been specified on the command line. | ||||
|  | ||||
| The automatically generated help is derived from the configured commands and their command settings. | ||||
|  | ||||
| The help is also context aware and tailored depending on what has been specified on the command line before it. For example, | ||||
|  | ||||
| 1. When `-h` or `--help` appears immediately after the application name (eg. `application.exe --help`), then the help displayed is a high-level summary of the application, including any command line examples and a listing of all possible commands the user can execute.  | ||||
|  | ||||
| 2. When `-h` or `--help` appears immediately after a command has been specified (eg. `application.exe command --help`), then the help displayed is specific to the command and includes information about command specific switches and any default values.  | ||||
|  | ||||
| `HelpProvider` is the `Spectre.Console` class responsible for determining context and preparing the help text to write to the console. It is an implementation of the public interface `IHelpProvider`. | ||||
|  | ||||
| ## Custom help providers | ||||
|  | ||||
| Whilst it shouldn't be common place to implement your own help provider, it is however possible.  | ||||
|  | ||||
| You are able to implement your own `IHelpProvider` and configure a `CommandApp` to use that instead of the Spectre.Console help provider.  | ||||
|  | ||||
| ```csharp | ||||
| using Spectre.Console.Cli; | ||||
|  | ||||
| namespace Help; | ||||
|  | ||||
| public static class Program | ||||
| { | ||||
|     public static int Main(string[] args) | ||||
|     { | ||||
|         var app = new CommandApp<DefaultCommand>(); | ||||
|  | ||||
|         app.Configure(config => | ||||
|         { | ||||
|             // Register the custom help provider | ||||
|             config.SetHelpProvider(new CustomHelpProvider(config.Settings)); | ||||
|         }); | ||||
|  | ||||
|         return app.Run(args); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| There is a working [example of a custom help provider](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Help) demonstrating this. | ||||
|  | ||||
| Title: Command Help | ||||
| Order: 13 | ||||
| Description: "Console applications built with *Spectre.Console.Cli* include automatically generated help command line help." | ||||
| --- | ||||
|  | ||||
| Console applications built with `Spectre.Console.Cli` include automatically generated help which is displayed when `-h` or `--help` has been specified on the command line. | ||||
|  | ||||
| The automatically generated help is derived from the configured commands and their command settings. | ||||
|  | ||||
| The help is also context aware and tailored depending on what has been specified on the command line before it. For example, | ||||
|  | ||||
| 1. When `-h` or `--help` appears immediately after the application name (eg. `application.exe --help`), then the help displayed is a high-level summary of the application, including any command line examples and a listing of all possible commands the user can execute.  | ||||
|  | ||||
| 2. When `-h` or `--help` appears immediately after a command has been specified (eg. `application.exe command --help`), then the help displayed is specific to the command and includes information about command specific switches and any default values.  | ||||
|  | ||||
| `HelpProvider` is the `Spectre.Console` class responsible for determining context and preparing the help text to write to the console. It is an implementation of the public interface `IHelpProvider`. | ||||
|  | ||||
| ## Custom help providers | ||||
|  | ||||
| Whilst it shouldn't be common place to implement your own help provider, it is however possible.  | ||||
|  | ||||
| You are able to implement your own `IHelpProvider` and configure a `CommandApp` to use that instead of the Spectre.Console help provider.  | ||||
|  | ||||
| ```csharp | ||||
| using Spectre.Console.Cli; | ||||
|  | ||||
| namespace Help; | ||||
|  | ||||
| public static class Program | ||||
| { | ||||
|     public static int Main(string[] args) | ||||
|     { | ||||
|         var app = new CommandApp<DefaultCommand>(); | ||||
|  | ||||
|         app.Configure(config => | ||||
|         { | ||||
|             // Register the custom help provider | ||||
|             config.SetHelpProvider(new CustomHelpProvider(config.Settings)); | ||||
|         }); | ||||
|  | ||||
|         return app.Run(args); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| There is a working [example of a custom help provider](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Help) demonstrating this. | ||||
|  | ||||
|   | ||||
| @@ -1,119 +1,119 @@ | ||||
| Title: Markup | ||||
| Order: 30 | ||||
| Description: The Markup class allows you to output rich text to the console. | ||||
| Highlights: | ||||
|  - Easily add *color*. | ||||
|  - Add hyperlinks to for supported terminals. | ||||
|  - Emoji 🚀 parsing. | ||||
| Reference: | ||||
|  - M:Spectre.Console.AnsiConsole.Markup(System.String) | ||||
|  - M:Spectre.Console.AnsiConsole.MarkupLine(System.String) | ||||
|  - T:Spectre.Console.Markup | ||||
| --- | ||||
|  | ||||
| The `Markup` class allows you to output rich text to the console. | ||||
|  | ||||
| ## Syntax | ||||
|  | ||||
| Console markup uses a syntax inspired by bbcode. If you write the style (see [Styles](xref:styles))  | ||||
| in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Write(new Markup("[bold yellow]Hello[/] [red]World![/]")); | ||||
| ``` | ||||
|  | ||||
| The `Markup` class implements `IRenderable` which means that you  | ||||
| can use this in tables, grids, and panels. Most classes that support | ||||
| rendering of `IRenderable` also have overloads for rendering rich text. | ||||
|  | ||||
| ```csharp | ||||
| var table = new Table(); | ||||
| table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]"))); | ||||
| table.AddColumn(new TableColumn("[blue]Bar[/]")); | ||||
| AnsiConsole.Write(table); | ||||
| ``` | ||||
|  | ||||
| ## Convenience methods | ||||
|  | ||||
| There are also convenience methods on `AnsiConsole` that can be used | ||||
| to write markup text to the console without instantiating a new `Markup` | ||||
| instance. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[underline green]Hello[/] "); | ||||
| AnsiConsole.MarkupLine("[bold]World[/]"); | ||||
| ``` | ||||
|  | ||||
| ## Escaping format characters | ||||
|  | ||||
| To output a `[` you use `[[`, and to output a `]` you use `]]`. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[[Hello]] "); // [Hello] | ||||
| AnsiConsole.Markup("[red][[World]][/]"); // [World] | ||||
| ``` | ||||
|  | ||||
| You can also use the `EscapeMarkup` extension method. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".EscapeMarkup()); | ||||
| ``` | ||||
| You can also use the `Markup.Escape` method. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[red]{0}[/]", Markup.Escape("Hello [World]")); | ||||
| ``` | ||||
|  | ||||
| ## Escaping Interpolated Strings | ||||
|  | ||||
| When working with interpolated strings, you can use the `MarkupInterpolated` and `MarkupLineInterpolated` methods to automatically escape the values in the interpolated string "holes". | ||||
|  | ||||
| ```csharp | ||||
| string hello = "Hello [World]"; | ||||
| AnsiConsole.MarkupInterpolated($"[red]{hello}[/]"); | ||||
| ``` | ||||
|  | ||||
| ## Setting background color | ||||
|  | ||||
| You can set the background color in markup by prefixing the color with `on`. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[bold yellow on blue]Hello[/]"); | ||||
| AnsiConsole.Markup("[default on blue]World[/]"); | ||||
| ``` | ||||
|  | ||||
| ## Rendering emojis | ||||
|  | ||||
| To output an emoji as part of markup, you can use emoji shortcodes. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("Hello :globe_showing_europe_africa:!"); | ||||
| ``` | ||||
|  | ||||
| For a list of emoji, see the [Emojis](xref:emojis) appendix section. | ||||
|  | ||||
| ## Colors | ||||
|  | ||||
| In the examples above, all colors were referenced by their name, | ||||
| but you can also use the hex or rgb representation for colors in markdown. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[red]Foo[/] "); | ||||
| AnsiConsole.Markup("[#ff0000]Bar[/] "); | ||||
| AnsiConsole.Markup("[rgb(255,0,0)]Baz[/] "); | ||||
| ``` | ||||
|  | ||||
| For a list of colors, see the [Colors](xref:colors) appendix section. | ||||
|  | ||||
| ## Links | ||||
|  | ||||
| To output a clickable link, you can use the `[link]` style. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[link]https://spectreconsole.net[/]"); | ||||
| AnsiConsole.Markup("[link=https://spectreconsole.net]Spectre Console Documentation[/]"); | ||||
| ``` | ||||
|  | ||||
| ## Styles | ||||
|  | ||||
| For a list of styles, see the [Styles](xref:styles) appendix section. | ||||
| Title: Markup | ||||
| Order: 30 | ||||
| Description: The Markup class allows you to output rich text to the console. | ||||
| Highlights: | ||||
|  - Easily add *color*. | ||||
|  - Add hyperlinks to for supported terminals. | ||||
|  - Emoji 🚀 parsing. | ||||
| Reference: | ||||
|  - M:Spectre.Console.AnsiConsole.Markup(System.String) | ||||
|  - M:Spectre.Console.AnsiConsole.MarkupLine(System.String) | ||||
|  - T:Spectre.Console.Markup | ||||
| --- | ||||
|  | ||||
| The `Markup` class allows you to output rich text to the console. | ||||
|  | ||||
| ## Syntax | ||||
|  | ||||
| Console markup uses a syntax inspired by bbcode. If you write the style (see [Styles](xref:styles))  | ||||
| in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Write(new Markup("[bold yellow]Hello[/] [red]World![/]")); | ||||
| ``` | ||||
|  | ||||
| The `Markup` class implements `IRenderable` which means that you  | ||||
| can use this in tables, grids, and panels. Most classes that support | ||||
| rendering of `IRenderable` also have overloads for rendering rich text. | ||||
|  | ||||
| ```csharp | ||||
| var table = new Table(); | ||||
| table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]"))); | ||||
| table.AddColumn(new TableColumn("[blue]Bar[/]")); | ||||
| AnsiConsole.Write(table); | ||||
| ``` | ||||
|  | ||||
| ## Convenience methods | ||||
|  | ||||
| There are also convenience methods on `AnsiConsole` that can be used | ||||
| to write markup text to the console without instantiating a new `Markup` | ||||
| instance. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[underline green]Hello[/] "); | ||||
| AnsiConsole.MarkupLine("[bold]World[/]"); | ||||
| ``` | ||||
|  | ||||
| ## Escaping format characters | ||||
|  | ||||
| To output a `[` you use `[[`, and to output a `]` you use `]]`. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[[Hello]] "); // [Hello] | ||||
| AnsiConsole.Markup("[red][[World]][/]"); // [World] | ||||
| ``` | ||||
|  | ||||
| You can also use the `EscapeMarkup` extension method. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".EscapeMarkup()); | ||||
| ``` | ||||
| You can also use the `Markup.Escape` method. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[red]{0}[/]", Markup.Escape("Hello [World]")); | ||||
| ``` | ||||
|  | ||||
| ## Escaping Interpolated Strings | ||||
|  | ||||
| When working with interpolated strings, you can use the `MarkupInterpolated` and `MarkupLineInterpolated` methods to automatically escape the values in the interpolated string "holes". | ||||
|  | ||||
| ```csharp | ||||
| string hello = "Hello [World]"; | ||||
| AnsiConsole.MarkupInterpolated($"[red]{hello}[/]"); | ||||
| ``` | ||||
|  | ||||
| ## Setting background color | ||||
|  | ||||
| You can set the background color in markup by prefixing the color with `on`. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[bold yellow on blue]Hello[/]"); | ||||
| AnsiConsole.Markup("[default on blue]World[/]"); | ||||
| ``` | ||||
|  | ||||
| ## Rendering emojis | ||||
|  | ||||
| To output an emoji as part of markup, you can use emoji shortcodes. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("Hello :globe_showing_europe_africa:!"); | ||||
| ``` | ||||
|  | ||||
| For a list of emoji, see the [Emojis](xref:emojis) appendix section. | ||||
|  | ||||
| ## Colors | ||||
|  | ||||
| In the examples above, all colors were referenced by their name, | ||||
| but you can also use the hex or rgb representation for colors in markdown. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[red]Foo[/] "); | ||||
| AnsiConsole.Markup("[#ff0000]Bar[/] "); | ||||
| AnsiConsole.Markup("[rgb(255,0,0)]Baz[/] "); | ||||
| ``` | ||||
|  | ||||
| For a list of colors, see the [Colors](xref:colors) appendix section. | ||||
|  | ||||
| ## Links | ||||
|  | ||||
| To output a clickable link, you can use the `[link]` style. | ||||
|  | ||||
| ```csharp | ||||
| AnsiConsole.Markup("[link]https://spectreconsole.net[/]"); | ||||
| AnsiConsole.Markup("[link=https://spectreconsole.net]Spectre Console Documentation[/]"); | ||||
| ``` | ||||
|  | ||||
| ## Styles | ||||
|  | ||||
| For a list of styles, see the [Styles](xref:styles) appendix section. | ||||
|   | ||||
| @@ -63,22 +63,22 @@ What's the secret number? _ | ||||
|  | ||||
| ```text | ||||
| Enter password: ************_ | ||||
| ``` | ||||
|  | ||||
| ## Masks | ||||
|  | ||||
| ``` | ||||
|  | ||||
| ## Masks | ||||
|  | ||||
| <?# Example symbol="M:Prompt.Program.AskPasswordWithCustomMask" project="Prompt" /?> | ||||
|  | ||||
|  | ||||
| ```text | ||||
| ```text | ||||
| Enter password: ------------_ | ||||
| ``` | ||||
|  | ||||
| You can utilize a null character to completely hide input. | ||||
|  | ||||
| ``` | ||||
|  | ||||
| You can utilize a null character to completely hide input. | ||||
|  | ||||
| <?# Example symbol="M:Prompt.Program.AskPasswordWithNullMask" project="Prompt" /?> | ||||
|  | ||||
| ```text | ||||
| ```text | ||||
| Enter password: _ | ||||
| ``` | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| Title: Calendar | ||||
| Title: Calendar | ||||
| Order: 40 | ||||
| RedirectFrom: calendar | ||||
| Description: "The **Calendar** is used to render a calendar to the terminal." | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Statiq.App; | ||||
| using Statiq.App; | ||||
| using Statiq.Common; | ||||
| using Statiq.Web; | ||||
|  | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using System; | ||||
| using System; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Immutable; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace Docs.Extensions | ||||
| namespace Docs.Extensions | ||||
| { | ||||
|     public static class StringExtensions | ||||
|     { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using System; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using Newtonsoft.Json; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace Docs.Models | ||||
| { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using System.Linq; | ||||
| using System.Linq; | ||||
| using System.Net; | ||||
| using Docs.Utilities; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Statiq.Common; | ||||
| using Statiq.Common; | ||||
| using Statiq.Web.GitHub; | ||||
| using Statiq.Web.Netlify; | ||||
|  | ||||
|   | ||||
| @@ -1,119 +1,119 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Builder; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.FileProviders; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using Microsoft.Playwright; | ||||
| using Statiq.Common; | ||||
| using Statiq.Core; | ||||
| using Statiq.Web; | ||||
| using Statiq.Web.Modules; | ||||
| using Statiq.Web.Pipelines; | ||||
|  | ||||
| namespace Docs.Pipelines | ||||
| { | ||||
|     public class SocialImages : Pipeline | ||||
|     { | ||||
|         public SocialImages() | ||||
|         { | ||||
|             Dependencies.AddRange(nameof(Inputs)); | ||||
|  | ||||
|             ProcessModules = new ModuleList | ||||
|             { | ||||
|                 new GetPipelineDocuments(ContentType.Content), | ||||
|  | ||||
|                 // Filter to non-archive content | ||||
|                 new FilterDocuments(Config.FromDocument(doc => !Archives.IsArchive(doc))), | ||||
|  | ||||
|                 // Process the content | ||||
|                 new CacheDocuments | ||||
|                 { | ||||
|                     new AddTitle(), | ||||
|                     new SetDestination(true), | ||||
|                     new ExecuteIf(Config.FromSetting(WebKeys.OptimizeContentFileNames, true)) | ||||
|                     { | ||||
|                         new OptimizeFileName() | ||||
|                     }, | ||||
|                     new GenerateSocialImage(), | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             OutputModules = new ModuleList { new WriteFiles() }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class GenerateSocialImage : ParallelModule | ||||
|     { | ||||
|         private IPlaywright _playwright; | ||||
|         private IBrowser _browser; | ||||
|         private WebApplication _app; | ||||
|         private IBrowserContext _context; | ||||
|  | ||||
|         protected override async Task BeforeExecutionAsync(IExecutionContext context) | ||||
|         { | ||||
|             var builder = WebApplication.CreateBuilder(); | ||||
|             builder.Logging.ClearProviders(); | ||||
|  | ||||
|             builder.Services | ||||
|                 .AddRazorPages() | ||||
|                 .WithRazorPagesRoot("/src/SocialCards/"); | ||||
|  | ||||
|             _app = builder.Build(); | ||||
|             _app.MapRazorPages(); | ||||
|             _app.UseStaticFiles(new StaticFileOptions | ||||
|             { | ||||
|                 FileProvider = new PhysicalFileProvider( | ||||
|                     Path.Combine(builder.Environment.ContentRootPath, "src/SocialCards")), | ||||
|                 RequestPath = "/static" | ||||
|             }); | ||||
|  | ||||
|             await _app.StartAsync().ConfigureAwait(false); | ||||
|  | ||||
|             _playwright = await Playwright.CreateAsync().ConfigureAwait(false); | ||||
|             _browser = await _playwright.Chromium.LaunchAsync().ConfigureAwait(false); | ||||
|             _context = await _browser.NewContextAsync(new BrowserNewContextOptions { | ||||
|                 ViewportSize = new ViewportSize { Width = 1200, Height = 618 }, | ||||
|             }).ConfigureAwait(false); | ||||
|         } | ||||
|  | ||||
|         protected override async Task FinallyAsync(IExecutionContext context) | ||||
|         { | ||||
|             await _context.DisposeAsync().ConfigureAwait(false); | ||||
|             await _browser.DisposeAsync().ConfigureAwait(false); | ||||
|             _playwright.Dispose(); | ||||
|             await _app.DisposeAsync().ConfigureAwait(false); | ||||
|             await base.FinallyAsync(context); | ||||
|         } | ||||
|  | ||||
|         protected override async Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context) | ||||
|         { | ||||
|             var url = _app.Urls.FirstOrDefault(u => u.StartsWith("http://")); | ||||
|             var page = await _context.NewPageAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var title = input.GetString("Title"); | ||||
|             var description = input.GetString("Description"); | ||||
|             var highlights = input.GetList<string>("Highlights") ?? Array.Empty<string>(); | ||||
|  | ||||
|             await page.GotoAsync($"{url}/?title={title}&desc={description}&highlights={string.Join("||", highlights)}"); | ||||
|  | ||||
|             // This will not just wait for the  page to load over the network, but it'll also give | ||||
|             // chrome a chance to complete rendering of the fonts while the wait timeout completes. | ||||
|             await page.WaitForLoadStateAsync(LoadState.NetworkIdle).ConfigureAwait(false); | ||||
|             var bytes = await page.ScreenshotAsync().ConfigureAwait(false); | ||||
|             await page.CloseAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var destination = input.Destination.InsertSuffix("-social").ChangeExtension("png"); | ||||
|             var doc = context.CreateDocument( | ||||
|                 input.Source, | ||||
|                 destination, | ||||
|                 new MetadataItems { { "DocId", input.Id }}, | ||||
|                 context.GetContentProvider(bytes)); | ||||
|  | ||||
|             return new[] { doc }; | ||||
|         } | ||||
|     } | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.AspNetCore.Builder; | ||||
| using Microsoft.Extensions.DependencyInjection; | ||||
| using Microsoft.Extensions.FileProviders; | ||||
| using Microsoft.Extensions.Logging; | ||||
| using Microsoft.Playwright; | ||||
| using Statiq.Common; | ||||
| using Statiq.Core; | ||||
| using Statiq.Web; | ||||
| using Statiq.Web.Modules; | ||||
| using Statiq.Web.Pipelines; | ||||
|  | ||||
| namespace Docs.Pipelines | ||||
| { | ||||
|     public class SocialImages : Pipeline | ||||
|     { | ||||
|         public SocialImages() | ||||
|         { | ||||
|             Dependencies.AddRange(nameof(Inputs)); | ||||
|  | ||||
|             ProcessModules = new ModuleList | ||||
|             { | ||||
|                 new GetPipelineDocuments(ContentType.Content), | ||||
|  | ||||
|                 // Filter to non-archive content | ||||
|                 new FilterDocuments(Config.FromDocument(doc => !Archives.IsArchive(doc))), | ||||
|  | ||||
|                 // Process the content | ||||
|                 new CacheDocuments | ||||
|                 { | ||||
|                     new AddTitle(), | ||||
|                     new SetDestination(true), | ||||
|                     new ExecuteIf(Config.FromSetting(WebKeys.OptimizeContentFileNames, true)) | ||||
|                     { | ||||
|                         new OptimizeFileName() | ||||
|                     }, | ||||
|                     new GenerateSocialImage(), | ||||
|                 } | ||||
|             }; | ||||
|  | ||||
|             OutputModules = new ModuleList { new WriteFiles() }; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class GenerateSocialImage : ParallelModule | ||||
|     { | ||||
|         private IPlaywright _playwright; | ||||
|         private IBrowser _browser; | ||||
|         private WebApplication _app; | ||||
|         private IBrowserContext _context; | ||||
|  | ||||
|         protected override async Task BeforeExecutionAsync(IExecutionContext context) | ||||
|         { | ||||
|             var builder = WebApplication.CreateBuilder(); | ||||
|             builder.Logging.ClearProviders(); | ||||
|  | ||||
|             builder.Services | ||||
|                 .AddRazorPages() | ||||
|                 .WithRazorPagesRoot("/src/SocialCards/"); | ||||
|  | ||||
|             _app = builder.Build(); | ||||
|             _app.MapRazorPages(); | ||||
|             _app.UseStaticFiles(new StaticFileOptions | ||||
|             { | ||||
|                 FileProvider = new PhysicalFileProvider( | ||||
|                     Path.Combine(builder.Environment.ContentRootPath, "src/SocialCards")), | ||||
|                 RequestPath = "/static" | ||||
|             }); | ||||
|  | ||||
|             await _app.StartAsync().ConfigureAwait(false); | ||||
|  | ||||
|             _playwright = await Playwright.CreateAsync().ConfigureAwait(false); | ||||
|             _browser = await _playwright.Chromium.LaunchAsync().ConfigureAwait(false); | ||||
|             _context = await _browser.NewContextAsync(new BrowserNewContextOptions { | ||||
|                 ViewportSize = new ViewportSize { Width = 1200, Height = 618 }, | ||||
|             }).ConfigureAwait(false); | ||||
|         } | ||||
|  | ||||
|         protected override async Task FinallyAsync(IExecutionContext context) | ||||
|         { | ||||
|             await _context.DisposeAsync().ConfigureAwait(false); | ||||
|             await _browser.DisposeAsync().ConfigureAwait(false); | ||||
|             _playwright.Dispose(); | ||||
|             await _app.DisposeAsync().ConfigureAwait(false); | ||||
|             await base.FinallyAsync(context); | ||||
|         } | ||||
|  | ||||
|         protected override async Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context) | ||||
|         { | ||||
|             var url = _app.Urls.FirstOrDefault(u => u.StartsWith("http://")); | ||||
|             var page = await _context.NewPageAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var title = input.GetString("Title"); | ||||
|             var description = input.GetString("Description"); | ||||
|             var highlights = input.GetList<string>("Highlights") ?? Array.Empty<string>(); | ||||
|  | ||||
|             await page.GotoAsync($"{url}/?title={title}&desc={description}&highlights={string.Join("||", highlights)}"); | ||||
|  | ||||
|             // This will not just wait for the  page to load over the network, but it'll also give | ||||
|             // chrome a chance to complete rendering of the fonts while the wait timeout completes. | ||||
|             await page.WaitForLoadStateAsync(LoadState.NetworkIdle).ConfigureAwait(false); | ||||
|             var bytes = await page.ScreenshotAsync().ConfigureAwait(false); | ||||
|             await page.CloseAsync().ConfigureAwait(false); | ||||
|  | ||||
|             var destination = input.Destination.InsertSuffix("-social").ChangeExtension("png"); | ||||
|             var doc = context.CreateDocument( | ||||
|                 input.Source, | ||||
|                 destination, | ||||
|                 new MetadataItems { { "DocId", input.Id }}, | ||||
|                 context.GetContentProvider(bytes)); | ||||
|  | ||||
|             return new[] { doc }; | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| using System; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Threading.Tasks; | ||||
| using Docs.Extensions; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using System; | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
|   | ||||
| @@ -15,22 +15,22 @@ public static class Program | ||||
|         { | ||||
|             config.SetApplicationName("fake-dotnet"); | ||||
|             config.ValidateExamples(); | ||||
|             config.AddExample("run", "--no-build"); | ||||
|  | ||||
|             // Run | ||||
|             config.AddCommand<RunCommand>("run"); | ||||
|  | ||||
|             // Add | ||||
|             config.AddBranch<AddSettings>("add", add => | ||||
|             { | ||||
|                 add.SetDescription("Add a package or reference to a .NET project"); | ||||
|                 add.AddCommand<AddPackageCommand>("package"); | ||||
|                 add.AddCommand<AddReferenceCommand>("reference"); | ||||
|             }); | ||||
|  | ||||
|             // Serve | ||||
|             config.AddCommand<ServeCommand>("serve") | ||||
|                 .WithExample("serve", "-o", "firefox") | ||||
|             config.AddExample("run", "--no-build"); | ||||
|  | ||||
|             // Run | ||||
|             config.AddCommand<RunCommand>("run"); | ||||
|  | ||||
|             // Add | ||||
|             config.AddBranch<AddSettings>("add", add => | ||||
|             { | ||||
|                 add.SetDescription("Add a package or reference to a .NET project"); | ||||
|                 add.AddCommand<AddPackageCommand>("package"); | ||||
|                 add.AddCommand<AddReferenceCommand>("reference"); | ||||
|             }); | ||||
|  | ||||
|             // Serve | ||||
|             config.AddCommand<ServeCommand>("serve") | ||||
|                 .WithExample("serve", "-o", "firefox") | ||||
|                 .WithExample("serve", "--port", "80", "-o", "firefox"); | ||||
|             }); | ||||
|  | ||||
|   | ||||
| @@ -1,30 +1,30 @@ | ||||
| using System.Linq; | ||||
| using Spectre.Console; | ||||
| using Spectre.Console.Cli; | ||||
| using Spectre.Console.Cli.Help; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Help; | ||||
|  | ||||
| /// <summary> | ||||
| /// Example showing how to extend the built-in Spectre.Console help provider | ||||
| /// by rendering a custom banner at the top of the help information | ||||
| /// </summary> | ||||
| internal class CustomHelpProvider : HelpProvider | ||||
| { | ||||
|     public CustomHelpProvider(ICommandAppSettings settings) | ||||
|         : base(settings) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         return new[] | ||||
|         { | ||||
|             new Text("--------------------------------------"), Text.NewLine, | ||||
|             new Text("---      CUSTOM HELP PROVIDER      ---"), Text.NewLine, | ||||
|             new Text("--------------------------------------"), Text.NewLine, | ||||
|             Text.NewLine, | ||||
|         }; | ||||
|     } | ||||
| using System.Linq; | ||||
| using Spectre.Console; | ||||
| using Spectre.Console.Cli; | ||||
| using Spectre.Console.Cli.Help; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Help; | ||||
|  | ||||
| /// <summary> | ||||
| /// Example showing how to extend the built-in Spectre.Console help provider | ||||
| /// by rendering a custom banner at the top of the help information | ||||
| /// </summary> | ||||
| internal class CustomHelpProvider : HelpProvider | ||||
| { | ||||
|     public CustomHelpProvider(ICommandAppSettings settings) | ||||
|         : base(settings) | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         return new[] | ||||
|         { | ||||
|             new Text("--------------------------------------"), Text.NewLine, | ||||
|             new Text("---      CUSTOM HELP PROVIDER      ---"), Text.NewLine, | ||||
|             new Text("--------------------------------------"), Text.NewLine, | ||||
|             Text.NewLine, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @@ -1,20 +1,20 @@ | ||||
| using Spectre.Console; | ||||
| using Spectre.Console.Cli; | ||||
|  | ||||
| namespace Help; | ||||
|  | ||||
| public sealed class DefaultCommand : Command | ||||
| { | ||||
|     private IAnsiConsole _console; | ||||
|  | ||||
|     public DefaultCommand(IAnsiConsole console) | ||||
|     { | ||||
|         _console = console; | ||||
|     } | ||||
|  | ||||
|     public override int Execute(CommandContext context) | ||||
|     { | ||||
|         _console.WriteLine("Hello world"); | ||||
|         return 0; | ||||
|     } | ||||
| using Spectre.Console; | ||||
| using Spectre.Console.Cli; | ||||
|  | ||||
| namespace Help; | ||||
|  | ||||
| public sealed class DefaultCommand : Command | ||||
| { | ||||
|     private IAnsiConsole _console; | ||||
|  | ||||
|     public DefaultCommand(IAnsiConsole console) | ||||
|     { | ||||
|         _console = console; | ||||
|     } | ||||
|  | ||||
|     public override int Execute(CommandContext context) | ||||
|     { | ||||
|         _console.WriteLine("Hello world"); | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
| @@ -1,19 +1,19 @@ | ||||
| using Spectre.Console.Cli; | ||||
|  | ||||
| namespace Help; | ||||
|  | ||||
| public static class Program | ||||
| { | ||||
|     public static int Main(string[] args) | ||||
|     { | ||||
|         var app = new CommandApp<DefaultCommand>(); | ||||
|  | ||||
|         app.Configure(config => | ||||
|         { | ||||
|             // Register the custom help provider | ||||
|             config.SetHelpProvider(new CustomHelpProvider(config.Settings)); | ||||
|         }); | ||||
|  | ||||
|         return app.Run(args); | ||||
|     } | ||||
| } | ||||
| using Spectre.Console.Cli; | ||||
|  | ||||
| namespace Help; | ||||
|  | ||||
| public static class Program | ||||
| { | ||||
|     public static int Main(string[] args) | ||||
|     { | ||||
|         var app = new CommandApp<DefaultCommand>(); | ||||
|  | ||||
|         app.Configure(config => | ||||
|         { | ||||
|             // Register the custom help provider | ||||
|             config.SetHelpProvider(new CustomHelpProvider(config.Settings)); | ||||
|         }); | ||||
|  | ||||
|         return app.Run(args); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -33,11 +33,11 @@ namespace Prompt | ||||
|             var age = AskAge(); | ||||
|  | ||||
|             WriteDivider("Secrets"); | ||||
|             var password = AskPassword(); | ||||
|  | ||||
|             var password = AskPassword(); | ||||
|  | ||||
|             WriteDivider("Mask"); | ||||
|             var mask = AskPasswordWithCustomMask(); | ||||
|  | ||||
|             var mask = AskPasswordWithCustomMask(); | ||||
|  | ||||
|             WriteDivider("Null Mask"); | ||||
|             var nullMask = AskPasswordWithNullMask(); | ||||
|  | ||||
| @@ -54,8 +54,8 @@ namespace Prompt | ||||
|                 .AddRow("[grey]Favorite fruit[/]", fruit) | ||||
|                 .AddRow("[grey]Favorite sport[/]", sport) | ||||
|                 .AddRow("[grey]Age[/]", age.ToString()) | ||||
|                 .AddRow("[grey]Password[/]", password) | ||||
|                 .AddRow("[grey]Mask[/]", mask) | ||||
|                 .AddRow("[grey]Password[/]", password) | ||||
|                 .AddRow("[grey]Mask[/]", mask) | ||||
|                 .AddRow("[grey]Null Mask[/]", nullMask) | ||||
|                 .AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color)); | ||||
|         } | ||||
| @@ -153,22 +153,22 @@ namespace Prompt | ||||
|                 new TextPrompt<string>("Enter [green]password[/]?") | ||||
|                     .PromptStyle("red") | ||||
|                     .Secret()); | ||||
|         } | ||||
|  | ||||
|         public static string AskPasswordWithCustomMask() | ||||
|         { | ||||
|         } | ||||
|  | ||||
|         public static string AskPasswordWithCustomMask() | ||||
|         { | ||||
|             return AnsiConsole.Prompt( | ||||
|                 new TextPrompt<string>("Enter [green]password[/]?") | ||||
|                     .PromptStyle("red") | ||||
|                     .Secret('-')); | ||||
|         } | ||||
|  | ||||
|         public static string AskPasswordWithNullMask() | ||||
|         { | ||||
|                     .Secret('-')); | ||||
|         } | ||||
|  | ||||
|         public static string AskPasswordWithNullMask() | ||||
|         { | ||||
|             return AnsiConsole.Prompt( | ||||
|                 new TextPrompt<string>("Enter [green]password[/]?") | ||||
|                     .PromptStyle("red") | ||||
|                     .Secret(null)); | ||||
|                     .Secret(null)); | ||||
|         } | ||||
|  | ||||
|         public static string AskColor() | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Spectre.Console; | ||||
| using Spectre.Console; | ||||
|  | ||||
| namespace Generator.Commands | ||||
| { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| using Microsoft.CodeAnalysis.Editing; | ||||
| using Microsoft.CodeAnalysis.Editing; | ||||
| using Microsoft.CodeAnalysis.Simplification; | ||||
|  | ||||
| namespace Spectre.Console.Analyzer.CodeActions; | ||||
| @@ -32,171 +32,171 @@ public class SwitchToAnsiConsoleAction : CodeAction | ||||
|  | ||||
|     /// <inheritdoc /> | ||||
|     protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken) | ||||
|     { | ||||
|         var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false); | ||||
|         var compilation = editor.SemanticModel.Compilation; | ||||
|  | ||||
|         var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation; | ||||
|         if (operation == null) | ||||
|         { | ||||
|             return _document; | ||||
|         } | ||||
|  | ||||
|         // If there is an IAnsiConsole passed into the method then we'll use it. | ||||
|         // otherwise we'll check for a field level instance. | ||||
|         // if neither of those exist we'll fall back to the static param. | ||||
|         var spectreConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole"); | ||||
|         var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole"); | ||||
|  | ||||
|         ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol; | ||||
|         if (iansiConsoleSymbol != null) | ||||
|         { | ||||
|             var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition); | ||||
|  | ||||
|             foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start)) | ||||
|             { | ||||
|                 // LookupSymbols check the accessibility of the symbol, but it can | ||||
|                 // suggest instance members when the current context is static. | ||||
|                 var symbolType = symbol switch | ||||
|                 { | ||||
|                     IParameterSymbol parameter => parameter.Type, | ||||
|                     IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type, | ||||
|                     IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type, | ||||
|                     ILocalSymbol local => local.Type, | ||||
|                     _ => null, | ||||
|                 }; | ||||
|  | ||||
|                 // Locals can be returned even if there are not valid in the current context. For instance, | ||||
|                 // it can return locals declared after the current location. Or it can return locals that | ||||
|                 // should not be accessible in a static local function. | ||||
|                 // | ||||
|                 // void Sample() | ||||
|                 // { | ||||
|                 //    int local = 0; | ||||
|                 //    static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it | ||||
|                 // } | ||||
|                 // | ||||
|                 // Parameters from the ancestor methods or local functions are also returned even if the operation is in a static local function. | ||||
|                 if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter) | ||||
|                 { | ||||
|                     var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start; | ||||
|  | ||||
|                     // The local is not part of the source tree | ||||
|                     if (localPosition == null) | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     // The local is declared after the current expression | ||||
|                     if (localPosition > _originalInvocation.Span.Start) | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     // The local is declared outside the static local function | ||||
|                     if (isInStaticContext && localPosition < parentStaticMemberStartPosition) | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol)) | ||||
|                 { | ||||
|                     accessibleConsoleSymbol = symbol; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (accessibleConsoleSymbol == null) | ||||
|         { | ||||
|             return _document; | ||||
|         } | ||||
|  | ||||
|         // Replace the original invocation | ||||
|         var generator = editor.Generator; | ||||
|         var consoleExpression = accessibleConsoleSymbol switch | ||||
|         { | ||||
|             ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation), | ||||
|             _ => generator.IdentifierName(accessibleConsoleSymbol.Name), | ||||
|         }; | ||||
|  | ||||
|         var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments) | ||||
|                 .WithLeadingTrivia(_originalInvocation.GetLeadingTrivia()) | ||||
|                 .WithTrailingTrivia(_originalInvocation.GetTrailingTrivia()); | ||||
|  | ||||
|         editor.ReplaceNode(_originalInvocation, newExpression); | ||||
|  | ||||
|         return editor.GetChangedDocument(); | ||||
|     } | ||||
|  | ||||
|     private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol) | ||||
|     { | ||||
|         if (symbol == null) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol)) | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         foreach (var iface in symbol.AllInterfaces) | ||||
|         { | ||||
|             if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol)) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition) | ||||
|     { | ||||
|         // Local functions can be nested, and an instance local function can be declared | ||||
|         // in a static local function. So, you need to continue to check ancestors when a | ||||
|         // local function is not static. | ||||
|         foreach (var member in operation.Syntax.Ancestors()) | ||||
|         { | ||||
|             if (member is LocalFunctionStatementSyntax localFunction) | ||||
|             { | ||||
|                 var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken); | ||||
|                 if (symbol != null && symbol.IsStatic) | ||||
|                 { | ||||
|                     parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start; | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             else if (member is LambdaExpressionSyntax lambdaExpression) | ||||
|             { | ||||
|                 var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol; | ||||
|                 if (symbol != null && symbol.IsStatic) | ||||
|                 { | ||||
|                     parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start; | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             else if (member is AnonymousMethodExpressionSyntax anonymousMethod) | ||||
|             { | ||||
|                 var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol; | ||||
|                 if (symbol != null && symbol.IsStatic) | ||||
|                 { | ||||
|                     parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start; | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             else if (member is MethodDeclarationSyntax methodDeclaration) | ||||
|             { | ||||
|                 parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start; | ||||
|  | ||||
|                 var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken); | ||||
|                 return symbol != null && symbol.IsStatic; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         parentStaticMemberStartPosition = -1; | ||||
|         return false; | ||||
|     { | ||||
|         var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false); | ||||
|         var compilation = editor.SemanticModel.Compilation; | ||||
|  | ||||
|         var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation; | ||||
|         if (operation == null) | ||||
|         { | ||||
|             return _document; | ||||
|         } | ||||
|  | ||||
|         // If there is an IAnsiConsole passed into the method then we'll use it. | ||||
|         // otherwise we'll check for a field level instance. | ||||
|         // if neither of those exist we'll fall back to the static param. | ||||
|         var spectreConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole"); | ||||
|         var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole"); | ||||
|  | ||||
|         ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol; | ||||
|         if (iansiConsoleSymbol != null) | ||||
|         { | ||||
|             var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition); | ||||
|  | ||||
|             foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start)) | ||||
|             { | ||||
|                 // LookupSymbols check the accessibility of the symbol, but it can | ||||
|                 // suggest instance members when the current context is static. | ||||
|                 var symbolType = symbol switch | ||||
|                 { | ||||
|                     IParameterSymbol parameter => parameter.Type, | ||||
|                     IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type, | ||||
|                     IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type, | ||||
|                     ILocalSymbol local => local.Type, | ||||
|                     _ => null, | ||||
|                 }; | ||||
|  | ||||
|                 // Locals can be returned even if there are not valid in the current context. For instance, | ||||
|                 // it can return locals declared after the current location. Or it can return locals that | ||||
|                 // should not be accessible in a static local function. | ||||
|                 // | ||||
|                 // void Sample() | ||||
|                 // { | ||||
|                 //    int local = 0; | ||||
|                 //    static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it | ||||
|                 // } | ||||
|                 // | ||||
|                 // Parameters from the ancestor methods or local functions are also returned even if the operation is in a static local function. | ||||
|                 if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter) | ||||
|                 { | ||||
|                     var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start; | ||||
|  | ||||
|                     // The local is not part of the source tree | ||||
|                     if (localPosition == null) | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     // The local is declared after the current expression | ||||
|                     if (localPosition > _originalInvocation.Span.Start) | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     // The local is declared outside the static local function | ||||
|                     if (isInStaticContext && localPosition < parentStaticMemberStartPosition) | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol)) | ||||
|                 { | ||||
|                     accessibleConsoleSymbol = symbol; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (accessibleConsoleSymbol == null) | ||||
|         { | ||||
|             return _document; | ||||
|         } | ||||
|  | ||||
|         // Replace the original invocation | ||||
|         var generator = editor.Generator; | ||||
|         var consoleExpression = accessibleConsoleSymbol switch | ||||
|         { | ||||
|             ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation), | ||||
|             _ => generator.IdentifierName(accessibleConsoleSymbol.Name), | ||||
|         }; | ||||
|  | ||||
|         var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments) | ||||
|                 .WithLeadingTrivia(_originalInvocation.GetLeadingTrivia()) | ||||
|                 .WithTrailingTrivia(_originalInvocation.GetTrailingTrivia()); | ||||
|  | ||||
|         editor.ReplaceNode(_originalInvocation, newExpression); | ||||
|  | ||||
|         return editor.GetChangedDocument(); | ||||
|     } | ||||
|  | ||||
|     private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol) | ||||
|     { | ||||
|         if (symbol == null) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol)) | ||||
|         { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         foreach (var iface in symbol.AllInterfaces) | ||||
|         { | ||||
|             if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol)) | ||||
|             { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition) | ||||
|     { | ||||
|         // Local functions can be nested, and an instance local function can be declared | ||||
|         // in a static local function. So, you need to continue to check ancestors when a | ||||
|         // local function is not static. | ||||
|         foreach (var member in operation.Syntax.Ancestors()) | ||||
|         { | ||||
|             if (member is LocalFunctionStatementSyntax localFunction) | ||||
|             { | ||||
|                 var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken); | ||||
|                 if (symbol != null && symbol.IsStatic) | ||||
|                 { | ||||
|                     parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start; | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             else if (member is LambdaExpressionSyntax lambdaExpression) | ||||
|             { | ||||
|                 var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol; | ||||
|                 if (symbol != null && symbol.IsStatic) | ||||
|                 { | ||||
|                     parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start; | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             else if (member is AnonymousMethodExpressionSyntax anonymousMethod) | ||||
|             { | ||||
|                 var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol; | ||||
|                 if (symbol != null && symbol.IsStatic) | ||||
|                 { | ||||
|                     parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start; | ||||
|                     return true; | ||||
|                 } | ||||
|             } | ||||
|             else if (member is MethodDeclarationSyntax methodDeclaration) | ||||
|             { | ||||
|                 parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start; | ||||
|  | ||||
|                 var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken); | ||||
|                 return symbol != null && symbol.IsStatic; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         parentStaticMemberStartPosition = -1; | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Spectre.Console.Cli.Internal.Configuration; | ||||
|  | ||||
| using Spectre.Console.Cli.Internal.Configuration; | ||||
|  | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| /// <summary> | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Spectre.Console.Cli.Internal.Configuration; | ||||
|  | ||||
| using Spectre.Console.Cli.Internal.Configuration; | ||||
|  | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| /// <summary> | ||||
| @@ -49,8 +49,8 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp | ||||
|     public Task<int> RunAsync(IEnumerable<string> args) | ||||
|     { | ||||
|         return _app.RunAsync(args); | ||||
|     } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     internal Configurator GetConfigurator() | ||||
|     { | ||||
|         return _app.GetConfigurator(); | ||||
|   | ||||
| @@ -5,40 +5,40 @@ namespace Spectre.Console.Cli; | ||||
| /// and <see cref="IConfigurator{TSettings}"/>. | ||||
| /// </summary> | ||||
| public static class ConfiguratorExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Sets the help provider for the application. | ||||
|     /// </summary> | ||||
|     /// <param name="configurator">The configurator.</param> | ||||
|     /// <param name="helpProvider">The help provider to use.</param> | ||||
|     /// <returns>A configurator that can be used to configure the application further.</returns> | ||||
|     public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHelpProvider helpProvider) | ||||
|     { | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Sets the help provider for the application. | ||||
|     /// </summary> | ||||
|     /// <param name="configurator">The configurator.</param> | ||||
|     /// <param name="helpProvider">The help provider to use.</param> | ||||
|     /// <returns>A configurator that can be used to configure the application further.</returns> | ||||
|     public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHelpProvider helpProvider) | ||||
|     { | ||||
|         if (configurator == null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(configurator)); | ||||
|         } | ||||
|  | ||||
|         configurator.SetHelpProvider(helpProvider); | ||||
|         return configurator; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Sets the help provider for the application. | ||||
|     /// </summary> | ||||
|     /// <param name="configurator">The configurator.</param> | ||||
|     /// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam> | ||||
|     /// <returns>A configurator that can be used to configure the application further.</returns> | ||||
|     public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator) | ||||
|         where T : IHelpProvider | ||||
|     { | ||||
|         } | ||||
|  | ||||
|         configurator.SetHelpProvider(helpProvider); | ||||
|         return configurator; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Sets the help provider for the application. | ||||
|     /// </summary> | ||||
|     /// <param name="configurator">The configurator.</param> | ||||
|     /// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam> | ||||
|     /// <returns>A configurator that can be used to configure the application further.</returns> | ||||
|     public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator) | ||||
|         where T : IHelpProvider | ||||
|     { | ||||
|         if (configurator == null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(configurator)); | ||||
|         } | ||||
|  | ||||
|         configurator.SetHelpProvider<T>(); | ||||
|         return configurator; | ||||
|         } | ||||
|  | ||||
|         configurator.SetHelpProvider<T>(); | ||||
|         return configurator; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -11,22 +11,22 @@ namespace Spectre.Console.Cli.Help; | ||||
| public class HelpProvider : IHelpProvider | ||||
| { | ||||
|     private HelpProviderResources resources; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating how many examples from direct children to show in the help text. | ||||
|     /// </summary> | ||||
|     protected virtual int MaximumIndirectExamples { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether any default values for command options are shown in the help text. | ||||
|     /// </summary> | ||||
|     protected virtual bool ShowOptionDefaultValues { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether a trailing period of a command description is trimmed in the help text. | ||||
|     /// </summary> | ||||
|     protected virtual bool TrimTrailingPeriod { get; } | ||||
|  | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating how many examples from direct children to show in the help text. | ||||
|     /// </summary> | ||||
|     protected virtual int MaximumIndirectExamples { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether any default values for command options are shown in the help text. | ||||
|     /// </summary> | ||||
|     protected virtual bool ShowOptionDefaultValues { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether a trailing period of a command description is trimmed in the help text. | ||||
|     /// </summary> | ||||
|     protected virtual bool TrimTrailingPeriod { get; } | ||||
|  | ||||
|     private sealed class HelpArgument | ||||
|     { | ||||
|         public string Name { get; } | ||||
| @@ -74,14 +74,14 @@ public class HelpProvider : IHelpProvider | ||||
|         public static IReadOnlyList<HelpOption> Get(ICommandInfo? command, HelpProviderResources resources) | ||||
|         { | ||||
|             var parameters = new List<HelpOption>(); | ||||
|             parameters.Add(new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null)); | ||||
|  | ||||
|             // Version information applies to the entire application | ||||
|             // Include the "-v" option in the help when at the root of the command line application | ||||
|             // Don't allow the "-v" option if users have specified one or more sub-commands | ||||
|             if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false)) | ||||
|             { | ||||
|                 parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null)); | ||||
|             parameters.Add(new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null)); | ||||
|  | ||||
|             // Version information applies to the entire application | ||||
|             // Include the "-v" option in the help when at the root of the command line application | ||||
|             // Don't allow the "-v" option if users have specified one or more sub-commands | ||||
|             if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false)) | ||||
|             { | ||||
|                 parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null)); | ||||
|             } | ||||
|  | ||||
|             parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o => | ||||
| @@ -92,55 +92,55 @@ public class HelpProvider : IHelpProvider | ||||
|                 ?? Array.Empty<HelpOption>()); | ||||
|             return parameters; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Initializes a new instance of the <see cref="HelpProvider"/> class. | ||||
|     /// </summary> | ||||
|     /// <param name="settings">The command line application settings used for configuration.</param> | ||||
|     public HelpProvider(ICommandAppSettings settings) | ||||
|     { | ||||
|         this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues; | ||||
|         this.MaximumIndirectExamples = settings.MaximumIndirectExamples; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Initializes a new instance of the <see cref="HelpProvider"/> class. | ||||
|     /// </summary> | ||||
|     /// <param name="settings">The command line application settings used for configuration.</param> | ||||
|     public HelpProvider(ICommandAppSettings settings) | ||||
|     { | ||||
|         this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues; | ||||
|         this.MaximumIndirectExamples = settings.MaximumIndirectExamples; | ||||
|         this.TrimTrailingPeriod = settings.TrimTrailingPeriod; | ||||
|  | ||||
|         resources = new HelpProviderResources(settings.Culture); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|         resources = new HelpProviderResources(settings.Culture); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         var result = new List<IRenderable>(); | ||||
|  | ||||
|         result.AddRange(GetHeader(model, command)); | ||||
|         var result = new List<IRenderable>(); | ||||
|  | ||||
|         result.AddRange(GetHeader(model, command)); | ||||
|         result.AddRange(GetDescription(model, command)); | ||||
|         result.AddRange(GetUsage(model, command)); | ||||
|         result.AddRange(GetExamples(model, command)); | ||||
|         result.AddRange(GetArguments(model, command)); | ||||
|         result.AddRange(GetOptions(model, command)); | ||||
|         result.AddRange(GetCommands(model, command)); | ||||
|         result.AddRange(GetCommands(model, command)); | ||||
|         result.AddRange(GetFooter(model, command)); | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the header for the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the header for the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     public virtual IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the description section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the description section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     public virtual IEnumerable<IRenderable> GetDescription(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         if (command?.Description == null) | ||||
| @@ -152,14 +152,14 @@ public class HelpProvider : IHelpProvider | ||||
|         composer.Style("yellow", $"{resources.Description}:").LineBreak(); | ||||
|         composer.Text(command.Description).LineBreak(); | ||||
|         yield return composer.LineBreak(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the usage section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the usage section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         var composer = new Composer(); | ||||
| @@ -219,26 +219,26 @@ public class HelpProvider : IHelpProvider | ||||
|             } | ||||
|  | ||||
|             if (command.IsBranch && command.DefaultCommand == null) | ||||
|             { | ||||
|             { | ||||
|                 // The user must specify the command | ||||
|                 parameters.Add($"[aqua]<{resources.Command}>[/]"); | ||||
|             } | ||||
|             else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0) | ||||
|             { | ||||
|                 // We are on a branch with a default command | ||||
|             } | ||||
|             else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0) | ||||
|             { | ||||
|                 // We are on a branch with a default command | ||||
|                 // The user can optionally specify the command | ||||
|                 parameters.Add($"[aqua][[{resources.Command}]][/]"); | ||||
|             } | ||||
|             else if (command.IsDefaultCommand) | ||||
|             { | ||||
|                 var commands = model.Commands.Where(x => !x.IsHidden && !x.IsDefaultCommand).ToList(); | ||||
|  | ||||
|                 if (commands.Count > 0) | ||||
|                 { | ||||
|                     // Commands other than the default are present | ||||
|                     // So make these optional in the usage statement | ||||
|                     parameters.Add($"[aqua][[{resources.Command}]][/]"); | ||||
|                 } | ||||
|                 parameters.Add($"[aqua][[{resources.Command}]][/]"); | ||||
|             } | ||||
|             else if (command.IsDefaultCommand) | ||||
|             { | ||||
|                 var commands = model.Commands.Where(x => !x.IsHidden && !x.IsDefaultCommand).ToList(); | ||||
|  | ||||
|                 if (commands.Count > 0) | ||||
|                 { | ||||
|                     // Commands other than the default are present | ||||
|                     // So make these optional in the usage statement | ||||
|                     parameters.Add($"[aqua][[{resources.Command}]][/]"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -249,19 +249,19 @@ public class HelpProvider : IHelpProvider | ||||
|         { | ||||
|             composer, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the examples section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     /// <remarks> | ||||
|     /// Examples from the command's direct children are used | ||||
|     /// if no examples have been set on the specified command or model. | ||||
|     /// </remarks> | ||||
|     public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, ICommandInfo? command) | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the examples section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     /// <remarks> | ||||
|     /// Examples from the command's direct children are used | ||||
|     /// if no examples have been set on the specified command or model. | ||||
|     /// </remarks> | ||||
|     public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         var maxExamples = int.MaxValue; | ||||
|  | ||||
| @@ -272,12 +272,12 @@ public class HelpProvider : IHelpProvider | ||||
|             // make sure that we limit the number of examples. | ||||
|             maxExamples = MaximumIndirectExamples; | ||||
|  | ||||
|             // Start at the current command (if exists) | ||||
|             // or alternatively commence at the model. | ||||
|             // Start at the current command (if exists) | ||||
|             // or alternatively commence at the model. | ||||
|             var commandContainer = command ?? (ICommandContainer)model; | ||||
|             var queue = new Queue<ICommandContainer>(new[] { commandContainer }); | ||||
|  | ||||
|             // Traverse the command tree and look for examples. | ||||
|             // Traverse the command tree and look for examples. | ||||
|             // As soon as a node contains commands, bail. | ||||
|             while (queue.Count > 0) | ||||
|             { | ||||
| @@ -317,14 +317,14 @@ public class HelpProvider : IHelpProvider | ||||
|         } | ||||
|  | ||||
|         return Array.Empty<IRenderable>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the arguments section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the arguments section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         var arguments = HelpArgument.Get(command); | ||||
| @@ -361,13 +361,13 @@ public class HelpProvider : IHelpProvider | ||||
|         result.Add(grid); | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the options section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the options section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
| @@ -469,18 +469,18 @@ public class HelpProvider : IHelpProvider | ||||
|         result.Add(grid); | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the commands section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the commands section of the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     public virtual IEnumerable<IRenderable> GetCommands(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         var commandContainer = command ?? (ICommandContainer)model; | ||||
|         bool isDefaultCommand = command?.IsDefaultCommand ?? false; | ||||
|     { | ||||
|         var commandContainer = command ?? (ICommandContainer)model; | ||||
|         bool isDefaultCommand = command?.IsDefaultCommand ?? false; | ||||
|  | ||||
|         var commands = isDefaultCommand ? model.Commands : commandContainer.Commands; | ||||
|         commands = commands.Where(x => !x.IsHidden).ToList(); | ||||
| @@ -530,16 +530,16 @@ public class HelpProvider : IHelpProvider | ||||
|         result.Add(grid); | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the footer for the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the footer for the help information. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns> | ||||
|     public virtual IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo? command) | ||||
|     { | ||||
|         yield break; | ||||
|     { | ||||
|         yield break; | ||||
|     } | ||||
| } | ||||
| @@ -4,7 +4,7 @@ namespace Spectre.Console.Cli.Help; | ||||
| /// Represents a command container. | ||||
| /// </summary> | ||||
| public interface ICommandContainer | ||||
| { | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Gets all the examples for the container. | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -1,42 +1,42 @@ | ||||
| namespace Spectre.Console.Cli.Help; | ||||
|  | ||||
| /// <summary> | ||||
| /// Represents an executable command. | ||||
| /// </summary> | ||||
| public interface ICommandInfo : ICommandContainer | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Gets the name of the command. | ||||
|     /// </summary> | ||||
|     string Name { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the description of the command. | ||||
|     /// </summary> | ||||
|     string? Description { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the command is a branch. | ||||
|     /// </summary> | ||||
|     bool IsBranch { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the command is the default command within its container. | ||||
|     /// </summary> | ||||
|     bool IsDefaultCommand { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the command is hidden. | ||||
|     /// </summary> | ||||
|     bool IsHidden { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the parameters associated with the command. | ||||
|     /// </summary> | ||||
|     IReadOnlyList<ICommandParameter> Parameters { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the parent command, if any. | ||||
|     /// </summary> | ||||
|     ICommandInfo? Parent { get; } | ||||
| /// <summary> | ||||
| /// Represents an executable command. | ||||
| /// </summary> | ||||
| public interface ICommandInfo : ICommandContainer | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Gets the name of the command. | ||||
|     /// </summary> | ||||
|     string Name { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the description of the command. | ||||
|     /// </summary> | ||||
|     string? Description { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the command is a branch. | ||||
|     /// </summary> | ||||
|     bool IsBranch { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the command is the default command within its container. | ||||
|     /// </summary> | ||||
|     bool IsDefaultCommand { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the command is hidden. | ||||
|     /// </summary> | ||||
|     bool IsHidden { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the parameters associated with the command. | ||||
|     /// </summary> | ||||
|     IReadOnlyList<ICommandParameter> Parameters { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the parent command, if any. | ||||
|     /// </summary> | ||||
|     ICommandInfo? Parent { get; } | ||||
| } | ||||
| @@ -1,23 +1,23 @@ | ||||
| namespace Spectre.Console.Cli.Help; | ||||
|  | ||||
| internal static class ICommandInfoExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Walks up the command.Parent tree, adding each command into a list as it goes. | ||||
|     /// </summary> | ||||
|     /// <remarks>The first command added to the list is the current (ie. this one).</remarks> | ||||
|     /// <returns>The list of commands from current to root, as traversed by <see cref="CommandInfo.Parent"/>.</returns> | ||||
|     public static List<ICommandInfo> Flatten(this ICommandInfo commandInfo) | ||||
|     { | ||||
|         var result = new Stack<Help.ICommandInfo>(); | ||||
|  | ||||
|         var current = commandInfo; | ||||
|         while (current != null) | ||||
|         { | ||||
|             result.Push(current); | ||||
|             current = current.Parent; | ||||
|         } | ||||
|  | ||||
|         return result.ToList(); | ||||
|     } | ||||
| } | ||||
| namespace Spectre.Console.Cli.Help; | ||||
|  | ||||
| internal static class ICommandInfoExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Walks up the command.Parent tree, adding each command into a list as it goes. | ||||
|     /// </summary> | ||||
|     /// <remarks>The first command added to the list is the current (ie. this one).</remarks> | ||||
|     /// <returns>The list of commands from current to root, as traversed by <see cref="CommandInfo.Parent"/>.</returns> | ||||
|     public static List<ICommandInfo> Flatten(this ICommandInfo commandInfo) | ||||
|     { | ||||
|         var result = new Stack<Help.ICommandInfo>(); | ||||
|  | ||||
|         var current = commandInfo; | ||||
|         while (current != null) | ||||
|         { | ||||
|             result.Push(current); | ||||
|             current = current.Parent; | ||||
|         } | ||||
|  | ||||
|         return result.ToList(); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,27 +1,27 @@ | ||||
| namespace Spectre.Console.Cli.Help; | ||||
|  | ||||
| /// <summary> | ||||
| /// Represents a command option. | ||||
| /// </summary> | ||||
| public interface ICommandOption : ICommandParameter | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Gets the long names of the option. | ||||
|     /// </summary> | ||||
|     IReadOnlyList<string> LongNames { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the short names of the option. | ||||
|     /// </summary> | ||||
|     IReadOnlyList<string> ShortNames { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the value name of the option, if applicable. | ||||
|     /// </summary> | ||||
|     string? ValueName { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the option value is optional. | ||||
|     /// </summary> | ||||
|     bool ValueIsOptional { get; } | ||||
| /// <summary> | ||||
| /// Represents a command option. | ||||
| /// </summary> | ||||
| public interface ICommandOption : ICommandParameter | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Gets the long names of the option. | ||||
|     /// </summary> | ||||
|     IReadOnlyList<string> LongNames { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the short names of the option. | ||||
|     /// </summary> | ||||
|     IReadOnlyList<string> ShortNames { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the value name of the option, if applicable. | ||||
|     /// </summary> | ||||
|     string? ValueName { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the option value is optional. | ||||
|     /// </summary> | ||||
|     bool ValueIsOptional { get; } | ||||
| } | ||||
| @@ -1,32 +1,32 @@ | ||||
| namespace Spectre.Console.Cli.Help; | ||||
|  | ||||
| /// <summary> | ||||
| /// Represents a command parameter. | ||||
| /// </summary> | ||||
| public interface ICommandParameter | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the parameter is a flag. | ||||
|     /// </summary> | ||||
|     bool IsFlag { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the parameter is required. | ||||
|     /// </summary> | ||||
|     bool Required { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the description of the parameter. | ||||
|     /// </summary> | ||||
|     string? Description { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the default value of the parameter, if specified. | ||||
|     /// </summary> | ||||
|     DefaultValueAttribute? DefaultValue { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the parameter is hidden. | ||||
|     /// </summary> | ||||
|     bool IsHidden { get; } | ||||
| /// <summary> | ||||
| /// Represents a command parameter. | ||||
| /// </summary> | ||||
| public interface ICommandParameter | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the parameter is a flag. | ||||
|     /// </summary> | ||||
|     bool IsFlag { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the parameter is required. | ||||
|     /// </summary> | ||||
|     bool Required { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the description of the parameter. | ||||
|     /// </summary> | ||||
|     string? Description { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the default value of the parameter, if specified. | ||||
|     /// </summary> | ||||
|     DefaultValueAttribute? DefaultValue { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the parameter is hidden. | ||||
|     /// </summary> | ||||
|     bool IsHidden { get; } | ||||
| } | ||||
| @@ -1,20 +1,20 @@ | ||||
| namespace Spectre.Console.Cli.Help; | ||||
|  | ||||
|  | ||||
| /// <summary> | ||||
| /// The help provider interface for Spectre.Console. | ||||
| /// </summary> | ||||
| /// <remarks> | ||||
| /// Implementations of this interface are responsbile | ||||
| /// for writing command help to the terminal when the | ||||
| /// `-h` or `--help` has been specified on the command line. | ||||
| /// The help provider interface for Spectre.Console. | ||||
| /// </summary> | ||||
| /// <remarks> | ||||
| /// Implementations of this interface are responsbile | ||||
| /// for writing command help to the terminal when the | ||||
| /// `-h` or `--help` has been specified on the command line. | ||||
| /// </remarks> | ||||
| public interface IHelpProvider | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Writes help information for the application. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects representing the help information.</returns> | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Writes help information for the application. | ||||
|     /// </summary> | ||||
|     /// <param name="model">The command model to write help for.</param> | ||||
|     /// <param name="command">The command for which to write help information (optional).</param> | ||||
|     /// <returns>An enumerable collection of <see cref="IRenderable"/> objects representing the help information.</returns> | ||||
|     IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command); | ||||
| } | ||||
|   | ||||
| @@ -24,22 +24,22 @@ public interface ICommandAppSettings | ||||
|     /// <summary> | ||||
|     /// Gets or sets the application version (use it to override auto-detected value). | ||||
|     /// </summary> | ||||
|     string? ApplicationVersion { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating how many examples from direct children to show in the help text. | ||||
|     /// </summary> | ||||
|     int MaximumIndirectExamples { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether any default values for command options are shown in the help text. | ||||
|     /// </summary> | ||||
|     bool ShowOptionDefaultValues { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text. | ||||
|     /// </summary> | ||||
|     bool TrimTrailingPeriod { get; set; } | ||||
|     string? ApplicationVersion { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating how many examples from direct children to show in the help text. | ||||
|     /// </summary> | ||||
|     int MaximumIndirectExamples { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether any default values for command options are shown in the help text. | ||||
|     /// </summary> | ||||
|     bool ShowOptionDefaultValues { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text. | ||||
|     /// </summary> | ||||
|     bool TrimTrailingPeriod { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets the <see cref="IAnsiConsole"/>. | ||||
| @@ -65,14 +65,14 @@ public interface ICommandAppSettings | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether or not parsing is strict. | ||||
|     /// </summary> | ||||
|     bool StrictParsing { get; set; } | ||||
|  | ||||
|     bool StrictParsing { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether or not flags found on the commnd line | ||||
|     /// that would normally result in a <see cref="CommandParseException"/> being thrown | ||||
|     /// during parsing with the message "Flags cannot be assigned a value." | ||||
|     /// Gets or sets a value indicating whether or not flags found on the commnd line | ||||
|     /// that would normally result in a <see cref="CommandParseException"/> being thrown | ||||
|     /// during parsing with the message "Flags cannot be assigned a value." | ||||
|     /// should instead be added to the remaining arguments collection. | ||||
|     /// </summary> | ||||
|     /// </summary> | ||||
|     bool ConvertFlagsToRemainingArguments { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -4,19 +4,19 @@ namespace Spectre.Console.Cli; | ||||
| /// Represents a configurator. | ||||
| /// </summary> | ||||
| public interface IConfigurator | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Sets the help provider for the application. | ||||
|     /// </summary> | ||||
|     /// <param name="helpProvider">The help provider to use.</param> | ||||
|     public void SetHelpProvider(IHelpProvider helpProvider); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Sets the help provider for the application. | ||||
|     /// </summary> | ||||
|     /// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam> | ||||
|     public void SetHelpProvider<T>() | ||||
|         where T : IHelpProvider; | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Sets the help provider for the application. | ||||
|     /// </summary> | ||||
|     /// <param name="helpProvider">The help provider to use.</param> | ||||
|     public void SetHelpProvider(IHelpProvider helpProvider); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Sets the help provider for the application. | ||||
|     /// </summary> | ||||
|     /// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam> | ||||
|     public void SetHelpProvider<T>() | ||||
|         where T : IHelpProvider; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the command app settings. | ||||
| @@ -66,5 +66,5 @@ public interface IConfigurator | ||||
|     /// <param name="action">The command branch configurator.</param> | ||||
|     /// <returns>A branch configurator that can be used to configure the branch further.</returns> | ||||
|     IBranchConfigurator AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action) | ||||
|         where TSettings : CommandSettings; | ||||
|         where TSettings : CommandSettings; | ||||
| } | ||||
| @@ -1,146 +1,146 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal sealed class CommandExecutor | ||||
| { | ||||
|     private readonly ITypeRegistrar _registrar; | ||||
|  | ||||
|     public CommandExecutor(ITypeRegistrar registrar) | ||||
|     { | ||||
|         _registrar = registrar ?? throw new ArgumentNullException(nameof(registrar)); | ||||
|         _registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor)); | ||||
|     } | ||||
|  | ||||
|     public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args) | ||||
|     { | ||||
|         if (configuration == null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(configuration)); | ||||
|         } | ||||
|  | ||||
|         args ??= new List<string>(); | ||||
|  | ||||
|         _registrar.RegisterInstance(typeof(IConfiguration), configuration); | ||||
|         _registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole()); | ||||
|  | ||||
|         // Create the command model. | ||||
|         var model = CommandModelBuilder.Build(configuration); | ||||
|         _registrar.RegisterInstance(typeof(CommandModel), model); | ||||
|         _registrar.RegisterDependencies(model); | ||||
|  | ||||
|         // Asking for version? Kind of a hack, but it's alright. | ||||
|         // We should probably make this a bit better in the future. | ||||
|         if (args.Contains("-v") || args.Contains("--version")) | ||||
|         { | ||||
|             var console = configuration.Settings.Console.GetConsole(); | ||||
|             console.WriteLine(ResolveApplicationVersion(configuration)); | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         // Parse and map the model against the arguments. | ||||
|         var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args); | ||||
|  | ||||
|         // Register the arguments with the container. | ||||
|         _registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult); | ||||
|         _registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining); | ||||
|  | ||||
|         // Create the resolver. | ||||
|         using (var resolver = new TypeResolverAdapter(_registrar.Build())) | ||||
|         { | ||||
|             // Get the registered help provider, falling back to the default provider | ||||
|             // if no custom implementations have been registered. | ||||
|             var helpProviders = resolver.Resolve(typeof(IEnumerable<IHelpProvider>)) as IEnumerable<IHelpProvider>; | ||||
|             var helpProvider = helpProviders?.LastOrDefault() ?? new HelpProvider(configuration.Settings); | ||||
|  | ||||
|             // Currently the root? | ||||
|             if (parsedResult?.Tree == null) | ||||
|             { | ||||
|                 // Display help. | ||||
|                 configuration.Settings.Console.SafeRender(helpProvider.Write(model, null)); | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             // Get the command to execute. | ||||
|             var leaf = parsedResult.Tree.GetLeafCommand(); | ||||
|             if (leaf.Command.IsBranch || leaf.ShowHelp) | ||||
|             { | ||||
|                 // Branches can't be executed. Show help. | ||||
|                 configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command)); | ||||
|                 return leaf.ShowHelp ? 0 : 1; | ||||
|             } | ||||
|  | ||||
|             // Is this the default and is it called without arguments when there are required arguments? | ||||
|             if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required)) | ||||
|             { | ||||
|                 // Display help for default command. | ||||
|                 configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command)); | ||||
|                 return 1; | ||||
|             } | ||||
|  | ||||
|             // Create the content. | ||||
|             var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data); | ||||
|  | ||||
|             // Execute the command tree. | ||||
|             return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| #pragma warning disable CS8603 // Possible null reference return. | ||||
|     private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args) | ||||
|     { | ||||
|         var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments); | ||||
|  | ||||
|         var parserContext = new CommandTreeParserContext(args, settings.ParsingMode); | ||||
|         var tokenizerResult = CommandTreeTokenizer.Tokenize(args); | ||||
|         var parsedResult = parser.Parse(parserContext, tokenizerResult); | ||||
|  | ||||
|         var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand(); | ||||
|         var lastParsedCommand = lastParsedLeaf?.Command; | ||||
|         if (lastParsedLeaf != null && lastParsedCommand != null && | ||||
|             lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp && | ||||
|             lastParsedCommand.DefaultCommand != null) | ||||
|         { | ||||
|             // Insert this branch's default command into the command line | ||||
|             // arguments and try again to see if it will parse. | ||||
|             var argsWithDefaultCommand = new List<string>(args); | ||||
|  | ||||
|             argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name); | ||||
|  | ||||
|             parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode); | ||||
|             tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand); | ||||
|             parsedResult = parser.Parse(parserContext, tokenizerResult); | ||||
|         } | ||||
|  | ||||
|         return parsedResult; | ||||
|     } | ||||
| #pragma warning restore CS8603 // Possible null reference return. | ||||
|  | ||||
|     private static string ResolveApplicationVersion(IConfiguration configuration) | ||||
|     { | ||||
|         return | ||||
|             configuration.Settings.ApplicationVersion ?? // potential override | ||||
|             VersionHelper.GetVersion(Assembly.GetEntryAssembly()); | ||||
|     } | ||||
|  | ||||
|     private static Task<int> Execute( | ||||
|         CommandTree leaf, | ||||
|         CommandTree tree, | ||||
|         CommandContext context, | ||||
|         ITypeResolver resolver, | ||||
|         IConfiguration configuration) | ||||
|     { | ||||
|         // Bind the command tree against the settings. | ||||
|         var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver); | ||||
|         configuration.Settings.Interceptor?.Intercept(context, settings); | ||||
|  | ||||
|         // Create and validate the command. | ||||
|         var command = leaf.CreateCommand(resolver); | ||||
|         var validationResult = command.Validate(context, settings); | ||||
|         if (!validationResult.Successful) | ||||
|         { | ||||
|             throw CommandRuntimeException.ValidationFailed(validationResult); | ||||
|         } | ||||
|  | ||||
|         // Execute the command. | ||||
|         return command.Execute(context, settings); | ||||
|     } | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal sealed class CommandExecutor | ||||
| { | ||||
|     private readonly ITypeRegistrar _registrar; | ||||
|  | ||||
|     public CommandExecutor(ITypeRegistrar registrar) | ||||
|     { | ||||
|         _registrar = registrar ?? throw new ArgumentNullException(nameof(registrar)); | ||||
|         _registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor)); | ||||
|     } | ||||
|  | ||||
|     public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args) | ||||
|     { | ||||
|         if (configuration == null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(configuration)); | ||||
|         } | ||||
|  | ||||
|         args ??= new List<string>(); | ||||
|  | ||||
|         _registrar.RegisterInstance(typeof(IConfiguration), configuration); | ||||
|         _registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole()); | ||||
|  | ||||
|         // Create the command model. | ||||
|         var model = CommandModelBuilder.Build(configuration); | ||||
|         _registrar.RegisterInstance(typeof(CommandModel), model); | ||||
|         _registrar.RegisterDependencies(model); | ||||
|  | ||||
|         // Asking for version? Kind of a hack, but it's alright. | ||||
|         // We should probably make this a bit better in the future. | ||||
|         if (args.Contains("-v") || args.Contains("--version")) | ||||
|         { | ||||
|             var console = configuration.Settings.Console.GetConsole(); | ||||
|             console.WriteLine(ResolveApplicationVersion(configuration)); | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         // Parse and map the model against the arguments. | ||||
|         var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args); | ||||
|  | ||||
|         // Register the arguments with the container. | ||||
|         _registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult); | ||||
|         _registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining); | ||||
|  | ||||
|         // Create the resolver. | ||||
|         using (var resolver = new TypeResolverAdapter(_registrar.Build())) | ||||
|         { | ||||
|             // Get the registered help provider, falling back to the default provider | ||||
|             // if no custom implementations have been registered. | ||||
|             var helpProviders = resolver.Resolve(typeof(IEnumerable<IHelpProvider>)) as IEnumerable<IHelpProvider>; | ||||
|             var helpProvider = helpProviders?.LastOrDefault() ?? new HelpProvider(configuration.Settings); | ||||
|  | ||||
|             // Currently the root? | ||||
|             if (parsedResult?.Tree == null) | ||||
|             { | ||||
|                 // Display help. | ||||
|                 configuration.Settings.Console.SafeRender(helpProvider.Write(model, null)); | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             // Get the command to execute. | ||||
|             var leaf = parsedResult.Tree.GetLeafCommand(); | ||||
|             if (leaf.Command.IsBranch || leaf.ShowHelp) | ||||
|             { | ||||
|                 // Branches can't be executed. Show help. | ||||
|                 configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command)); | ||||
|                 return leaf.ShowHelp ? 0 : 1; | ||||
|             } | ||||
|  | ||||
|             // Is this the default and is it called without arguments when there are required arguments? | ||||
|             if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required)) | ||||
|             { | ||||
|                 // Display help for default command. | ||||
|                 configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command)); | ||||
|                 return 1; | ||||
|             } | ||||
|  | ||||
|             // Create the content. | ||||
|             var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data); | ||||
|  | ||||
|             // Execute the command tree. | ||||
|             return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| #pragma warning disable CS8603 // Possible null reference return. | ||||
|     private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args) | ||||
|     { | ||||
|         var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments); | ||||
|  | ||||
|         var parserContext = new CommandTreeParserContext(args, settings.ParsingMode); | ||||
|         var tokenizerResult = CommandTreeTokenizer.Tokenize(args); | ||||
|         var parsedResult = parser.Parse(parserContext, tokenizerResult); | ||||
|  | ||||
|         var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand(); | ||||
|         var lastParsedCommand = lastParsedLeaf?.Command; | ||||
|         if (lastParsedLeaf != null && lastParsedCommand != null && | ||||
|             lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp && | ||||
|             lastParsedCommand.DefaultCommand != null) | ||||
|         { | ||||
|             // Insert this branch's default command into the command line | ||||
|             // arguments and try again to see if it will parse. | ||||
|             var argsWithDefaultCommand = new List<string>(args); | ||||
|  | ||||
|             argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name); | ||||
|  | ||||
|             parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode); | ||||
|             tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand); | ||||
|             parsedResult = parser.Parse(parserContext, tokenizerResult); | ||||
|         } | ||||
|  | ||||
|         return parsedResult; | ||||
|     } | ||||
| #pragma warning restore CS8603 // Possible null reference return. | ||||
|  | ||||
|     private static string ResolveApplicationVersion(IConfiguration configuration) | ||||
|     { | ||||
|         return | ||||
|             configuration.Settings.ApplicationVersion ?? // potential override | ||||
|             VersionHelper.GetVersion(Assembly.GetEntryAssembly()); | ||||
|     } | ||||
|  | ||||
|     private static Task<int> Execute( | ||||
|         CommandTree leaf, | ||||
|         CommandTree tree, | ||||
|         CommandContext context, | ||||
|         ITypeResolver resolver, | ||||
|         IConfiguration configuration) | ||||
|     { | ||||
|         // Bind the command tree against the settings. | ||||
|         var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver); | ||||
|         configuration.Settings.Interceptor?.Intercept(context, settings); | ||||
|  | ||||
|         // Create and validate the command. | ||||
|         var command = leaf.CreateCommand(resolver); | ||||
|         var validationResult = command.Validate(context, settings); | ||||
|         if (!validationResult.Successful) | ||||
|         { | ||||
|             throw CommandRuntimeException.ValidationFailed(validationResult); | ||||
|         } | ||||
|  | ||||
|         // Execute the command. | ||||
|         return command.Execute(context, settings); | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal sealed class BranchConfigurator : IBranchConfigurator | ||||
| { | ||||
|   | ||||
| @@ -5,16 +5,16 @@ internal sealed class CommandAppSettings : ICommandAppSettings | ||||
|     public CultureInfo? Culture { get; set; } | ||||
|     public string? ApplicationName { get; set; } | ||||
|     public string? ApplicationVersion { get; set; } | ||||
|     public int MaximumIndirectExamples { get; set; } | ||||
|     public bool ShowOptionDefaultValues { get; set; } | ||||
|     public int MaximumIndirectExamples { get; set; } | ||||
|     public bool ShowOptionDefaultValues { get; set; } | ||||
|     public IAnsiConsole? Console { get; set; } | ||||
|     public ICommandInterceptor? Interceptor { get; set; } | ||||
|     public ITypeRegistrarFrontend Registrar { get; set; } | ||||
|     public CaseSensitivity CaseSensitivity { get; set; } | ||||
|     public bool PropagateExceptions { get; set; } | ||||
|     public bool ValidateExamples { get; set; } | ||||
|     public bool TrimTrailingPeriod { get; set; } = true; | ||||
|     public bool StrictParsing { get; set; } | ||||
|     public bool TrimTrailingPeriod { get; set; } = true; | ||||
|     public bool StrictParsing { get; set; } | ||||
|     public bool ConvertFlagsToRemainingArguments { get; set; } = false; | ||||
|  | ||||
|     public ParsingMode ParsingMode => | ||||
| @@ -26,7 +26,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings | ||||
|     { | ||||
|         Registrar = new TypeRegistrar(registrar); | ||||
|         CaseSensitivity = CaseSensitivity.All; | ||||
|         ShowOptionDefaultValues = true; | ||||
|         ShowOptionDefaultValues = true; | ||||
|         MaximumIndirectExamples = 5; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -19,19 +19,19 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig | ||||
|         Settings = new CommandAppSettings(registrar); | ||||
|         Examples = new List<string[]>(); | ||||
|     } | ||||
|  | ||||
|     public void SetHelpProvider(IHelpProvider helpProvider) | ||||
|     { | ||||
|         // Register the help provider | ||||
|         _registrar.RegisterInstance(typeof(IHelpProvider), helpProvider); | ||||
|     } | ||||
|  | ||||
|     public void SetHelpProvider<T>() | ||||
|         where T : IHelpProvider | ||||
|     { | ||||
|         // Register the help provider | ||||
|         _registrar.Register(typeof(IHelpProvider), typeof(T)); | ||||
|     } | ||||
|  | ||||
|     public void SetHelpProvider(IHelpProvider helpProvider) | ||||
|     { | ||||
|         // Register the help provider | ||||
|         _registrar.RegisterInstance(typeof(IHelpProvider), helpProvider); | ||||
|     } | ||||
|  | ||||
|     public void SetHelpProvider<T>() | ||||
|         where T : IHelpProvider | ||||
|     { | ||||
|         // Register the help provider | ||||
|         _registrar.Register(typeof(IHelpProvider), typeof(T)); | ||||
|     } | ||||
|  | ||||
|     public void AddExample(params string[] args) | ||||
|     { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
|  | ||||
| internal sealed class CommandInfo : ICommandContainer, ICommandInfo | ||||
| { | ||||
|     public string Name { get; } | ||||
| @@ -20,14 +20,14 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo | ||||
|  | ||||
|     // only branches can have a default command | ||||
|     public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null; | ||||
|     public bool IsHidden { get; } | ||||
|  | ||||
|     IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList(); | ||||
|     ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand; | ||||
|     IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList(); | ||||
|     ICommandInfo? ICommandInfo.Parent => Parent; | ||||
|     IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples; | ||||
|  | ||||
|     public bool IsHidden { get; } | ||||
|  | ||||
|     IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList(); | ||||
|     ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand; | ||||
|     IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList(); | ||||
|     ICommandInfo? ICommandInfo.Parent => Parent; | ||||
|     IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples; | ||||
|  | ||||
|     public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype) | ||||
|     { | ||||
|         Parent = parent; | ||||
| @@ -54,5 +54,5 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo | ||||
|                 Description = description.Description; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     } | ||||
| } | ||||
| @@ -1,17 +1,17 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
|  | ||||
| internal sealed class CommandModel : ICommandContainer, ICommandModel | ||||
| { | ||||
|     public string? ApplicationName { get; } | ||||
|     public ParsingMode ParsingMode { get; } | ||||
|     public IList<CommandInfo> Commands { get; } | ||||
|     public IList<string[]> Examples { get; } | ||||
|  | ||||
|     public IList<string[]> Examples { get; } | ||||
|  | ||||
|     public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand); | ||||
|  | ||||
|     string ICommandModel.ApplicationName => GetApplicationName(ApplicationName); | ||||
|     IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Commands.Cast<ICommandInfo>().ToList(); | ||||
|     ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand; | ||||
|     ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand; | ||||
|     IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples; | ||||
|  | ||||
|     public CommandModel( | ||||
| @@ -22,18 +22,18 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel | ||||
|         ApplicationName = settings.ApplicationName; | ||||
|         ParsingMode = settings.ParsingMode; | ||||
|         Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>()); | ||||
|         Examples = new List<string[]>(examples ?? Array.Empty<string[]>()); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the name of the application. | ||||
|     /// If the provided <paramref name="applicationName"/> is not null or empty, | ||||
|     /// it is returned. Otherwise the name of the current application | ||||
|     /// is determined based on the executable file's name. | ||||
|     /// </summary> | ||||
|     /// <param name="applicationName">The optional name of the application.</param> | ||||
|     /// <returns> | ||||
|     /// The name of the application, or a default value of "?" if no valid application name can be determined. | ||||
|         Examples = new List<string[]>(examples ?? Array.Empty<string[]>()); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the name of the application. | ||||
|     /// If the provided <paramref name="applicationName"/> is not null or empty, | ||||
|     /// it is returned. Otherwise the name of the current application | ||||
|     /// is determined based on the executable file's name. | ||||
|     /// </summary> | ||||
|     /// <param name="applicationName">The optional name of the application.</param> | ||||
|     /// <returns> | ||||
|     /// The name of the application, or a default value of "?" if no valid application name can be determined. | ||||
|     /// </returns> | ||||
|     private static string GetApplicationName(string? applicationName) | ||||
|     { | ||||
| @@ -45,7 +45,7 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel | ||||
|  | ||||
|     private static string? GetApplicationFile() | ||||
|     { | ||||
|         var location = Assembly.GetEntryAssembly()?.Location; | ||||
|         var location = Assembly.GetEntryAssembly()?.Location; | ||||
|  | ||||
|         if (string.IsNullOrWhiteSpace(location)) | ||||
|         { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
|  | ||||
| internal static class CommandModelBuilder | ||||
| { | ||||
|     // Consider removing this in favor for value tuples at some point. | ||||
| @@ -31,8 +31,8 @@ internal static class CommandModelBuilder | ||||
|             configuration.DefaultCommand.Examples.AddRange(configuration.Examples); | ||||
|  | ||||
|             // Build the default command. | ||||
|             var defaultCommand = Build(null, configuration.DefaultCommand); | ||||
|  | ||||
|             var defaultCommand = Build(null, configuration.DefaultCommand); | ||||
|  | ||||
|             result.Add(defaultCommand); | ||||
|         } | ||||
|  | ||||
| @@ -55,7 +55,7 @@ internal static class CommandModelBuilder | ||||
|         foreach (var childCommand in command.Children) | ||||
|         { | ||||
|             var child = Build(info, childCommand); | ||||
|             info.Children.Add(child); | ||||
|             info.Children.Add(child); | ||||
|         } | ||||
|  | ||||
|         // Normalize argument positions. | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
|  | ||||
| internal abstract class CommandParameter : ICommandParameterInfo, ICommandParameter | ||||
| { | ||||
|     public Guid Id { get; } | ||||
| @@ -17,10 +17,10 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame | ||||
|     public string PropertyName => Property.Name; | ||||
|  | ||||
|     public virtual bool WantRawValue => ParameterType.IsPairDeconstructable() | ||||
|         && (PairDeconstructor != null || Converter == null); | ||||
|  | ||||
|     public bool IsFlag => ParameterKind == ParameterKind.Flag; | ||||
|  | ||||
|         && (PairDeconstructor != null || Converter == null); | ||||
|  | ||||
|     public bool IsFlag => ParameterKind == ParameterKind.Flag; | ||||
|  | ||||
|     protected CommandParameter( | ||||
|         Type parameterType, ParameterKind parameterKind, PropertyInfo property, | ||||
|         string? description, TypeConverterAttribute? converter, | ||||
|   | ||||
| @@ -8,13 +8,13 @@ internal interface ICommandContainer | ||||
|     /// <summary> | ||||
|     /// Gets all commands in the container. | ||||
|     /// </summary> | ||||
|     IList<CommandInfo> Commands { get; } | ||||
|  | ||||
|     IList<CommandInfo> Commands { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the default command for the container. | ||||
|     /// </summary> | ||||
|     /// <remarks> | ||||
|     /// Returns null if a default command has not been set. | ||||
|     /// </remarks> | ||||
|     /// </summary> | ||||
|     /// <remarks> | ||||
|     /// Returns null if a default command has not been set. | ||||
|     /// </remarks> | ||||
|     CommandInfo? DefaultCommand { get; } | ||||
| } | ||||
| @@ -1,12 +1,12 @@ | ||||
| using static Spectre.Console.Cli.CommandTreeTokenizer; | ||||
|  | ||||
| using static Spectre.Console.Cli.CommandTreeTokenizer; | ||||
|  | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal class CommandTreeParser | ||||
| { | ||||
|     private readonly CommandModel _configuration; | ||||
|     private readonly ParsingMode _parsingMode; | ||||
|     private readonly CommandOptionAttribute _help; | ||||
|     private readonly CommandOptionAttribute _help; | ||||
|     private readonly bool _convertFlagsToRemainingArguments; | ||||
|  | ||||
|     public CaseSensitivity CaseSensitivity { get; } | ||||
| @@ -21,20 +21,20 @@ internal class CommandTreeParser | ||||
|     { | ||||
|         _configuration = configuration ?? throw new ArgumentNullException(nameof(configuration)); | ||||
|         _parsingMode = parsingMode ?? _configuration.ParsingMode; | ||||
|         _help = new CommandOptionAttribute("-h|--help"); | ||||
|         _help = new CommandOptionAttribute("-h|--help"); | ||||
|         _convertFlagsToRemainingArguments = convertFlagsToRemainingArguments ?? false; | ||||
|  | ||||
|         CaseSensitivity = caseSensitivity; | ||||
|     } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public CommandTreeParserResult Parse(IEnumerable<string> args) | ||||
|     { | ||||
|         var parserContext = new CommandTreeParserContext(args, _parsingMode); | ||||
|         var tokenizerResult = CommandTreeTokenizer.Tokenize(args); | ||||
|  | ||||
|         return Parse(parserContext, tokenizerResult); | ||||
|     } | ||||
|  | ||||
|     { | ||||
|         var parserContext = new CommandTreeParserContext(args, _parsingMode); | ||||
|         var tokenizerResult = CommandTreeTokenizer.Tokenize(args); | ||||
|  | ||||
|         return Parse(parserContext, tokenizerResult); | ||||
|     } | ||||
|  | ||||
|     public CommandTreeParserResult Parse(CommandTreeParserContext context, CommandTreeTokenizerResult tokenizerResult) | ||||
|     { | ||||
|         var tokens = tokenizerResult.Tokens; | ||||
| @@ -258,8 +258,8 @@ internal class CommandTreeParser | ||||
|             // Find the option. | ||||
|             var option = node.FindOption(token.Value, isLongOption, CaseSensitivity); | ||||
|             if (option != null) | ||||
|             { | ||||
|                 ParseOptionValue(context, stream, token, node, option); | ||||
|             { | ||||
|                 ParseOptionValue(context, stream, token, node, option); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
| @@ -291,10 +291,10 @@ internal class CommandTreeParser | ||||
|         CommandTreeParserContext context, | ||||
|         CommandTreeTokenStream stream, | ||||
|         CommandTreeToken token, | ||||
|         CommandTree current, | ||||
|         CommandTree current, | ||||
|         CommandParameter? parameter = null) | ||||
|     { | ||||
|         bool addToMappedCommandParameters = parameter != null; | ||||
|     { | ||||
|         bool addToMappedCommandParameters = parameter != null; | ||||
|  | ||||
|         var value = default(string); | ||||
|  | ||||
| @@ -312,49 +312,49 @@ internal class CommandTreeParser | ||||
|             { | ||||
|                 // Is this a command? | ||||
|                 if (current.Command.FindCommand(valueToken.Value, CaseSensitivity) == null) | ||||
|                 { | ||||
|                     if (parameter != null) | ||||
|                     { | ||||
|                         if (parameter.ParameterKind == ParameterKind.Flag) | ||||
|                         { | ||||
|                             if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase)) | ||||
|                             { | ||||
|                                 if (!valueToken.HadSeparator) | ||||
|                                 { | ||||
|                                     // Do nothing | ||||
|                                     // - assume valueToken is unrelated to the flag parameter (ie. we've parsed it unnecessarily) | ||||
|                                     // - rely on the "No value?" code below to set the flag to its default value | ||||
|                                     // - valueToken will be handled on the next pass of the parser | ||||
|                                 } | ||||
|                                 else | ||||
|                                 { | ||||
|                                     // Flags cannot be assigned a value. | ||||
|                                     if (_convertFlagsToRemainingArguments) | ||||
|                                     { | ||||
|                                         value = stream.Consume(CommandTreeToken.Kind.String)?.Value; | ||||
|  | ||||
|                                         context.AddRemainingArgument(token.Value, value); | ||||
|  | ||||
|                                         // Prevent the option and it's non-boolean value from being added to | ||||
|                                         // mapped parameters (otherwise an exception will be thrown later | ||||
|                                         // when binding the value to the flag in the comand settings) | ||||
|                                         addToMappedCommandParameters = false; | ||||
|                                     } | ||||
|                                     else | ||||
|                                     { | ||||
|                                         throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token); | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 value = stream.Consume(CommandTreeToken.Kind.String)?.Value; | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             value = stream.Consume(CommandTreeToken.Kind.String)?.Value; | ||||
|                         } | ||||
|                 { | ||||
|                     if (parameter != null) | ||||
|                     { | ||||
|                         if (parameter.ParameterKind == ParameterKind.Flag) | ||||
|                         { | ||||
|                             if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase)) | ||||
|                             { | ||||
|                                 if (!valueToken.HadSeparator) | ||||
|                                 { | ||||
|                                     // Do nothing | ||||
|                                     // - assume valueToken is unrelated to the flag parameter (ie. we've parsed it unnecessarily) | ||||
|                                     // - rely on the "No value?" code below to set the flag to its default value | ||||
|                                     // - valueToken will be handled on the next pass of the parser | ||||
|                                 } | ||||
|                                 else | ||||
|                                 { | ||||
|                                     // Flags cannot be assigned a value. | ||||
|                                     if (_convertFlagsToRemainingArguments) | ||||
|                                     { | ||||
|                                         value = stream.Consume(CommandTreeToken.Kind.String)?.Value; | ||||
|  | ||||
|                                         context.AddRemainingArgument(token.Value, value); | ||||
|  | ||||
|                                         // Prevent the option and it's non-boolean value from being added to | ||||
|                                         // mapped parameters (otherwise an exception will be thrown later | ||||
|                                         // when binding the value to the flag in the comand settings) | ||||
|                                         addToMappedCommandParameters = false; | ||||
|                                     } | ||||
|                                     else | ||||
|                                     { | ||||
|                                         throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token); | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                             else | ||||
|                             { | ||||
|                                 value = stream.Consume(CommandTreeToken.Kind.String)?.Value; | ||||
|                             } | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             value = stream.Consume(CommandTreeToken.Kind.String)?.Value; | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
| @@ -370,13 +370,13 @@ internal class CommandTreeParser | ||||
|                 } | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|             { | ||||
|                 context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             if (parameter == null && // Only add tokens which have not been matched to a command parameter | ||||
|             if (parameter == null && // Only add tokens which have not been matched to a command parameter | ||||
|                 (context.State == State.Remaining || context.ParsingMode == ParsingMode.Relaxed)) | ||||
|             { | ||||
|                 context.AddRemainingArgument(token.Value, null); | ||||
| @@ -399,10 +399,10 @@ internal class CommandTreeParser | ||||
|                         if (parameter.IsFlagValue()) | ||||
|                         { | ||||
|                             value = null; | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             throw CommandParseException.OptionHasNoValue(context.Arguments, token, option); | ||||
|                         } | ||||
|                         else | ||||
|                         { | ||||
|                             throw CommandParseException.OptionHasNoValue(context.Arguments, token, option); | ||||
|                         } | ||||
|                     } | ||||
|                     else | ||||
| @@ -415,9 +415,9 @@ internal class CommandTreeParser | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (parameter != null && addToMappedCommandParameters) | ||||
|         { | ||||
|             current.Mapped.Add(new MappedCommandParameter(parameter, value)); | ||||
|         if (parameter != null && addToMappedCommandParameters) | ||||
|         { | ||||
|             current.Mapped.Add(new MappedCommandParameter(parameter, value)); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -26,15 +26,15 @@ internal class CommandTreeParserContext | ||||
|     public void IncreaseArgumentPosition() | ||||
|     { | ||||
|         CurrentArgumentPosition++; | ||||
|     } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public void AddRemainingArgument(string key, string? value) | ||||
|     { | ||||
|         if (!_remaining.ContainsKey(key)) | ||||
|         { | ||||
|             _remaining.Add(key, new List<string?>()); | ||||
|         } | ||||
|  | ||||
|     { | ||||
|         if (!_remaining.ContainsKey(key)) | ||||
|         { | ||||
|             _remaining.Add(key, new List<string?>()); | ||||
|         } | ||||
|  | ||||
|         _remaining[key].Add(value); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -6,11 +6,11 @@ internal sealed class CommandTreeToken | ||||
|     public int Position { get; } | ||||
|     public string Value { get; } | ||||
|     public string Representation { get; } | ||||
|     public bool IsGrouped { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>. | ||||
|     /// </summary> | ||||
|     public bool IsGrouped { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>. | ||||
|     /// </summary> | ||||
|     public bool HadSeparator { get; set; } | ||||
|  | ||||
|     public enum Kind | ||||
|   | ||||
| @@ -5,7 +5,7 @@ internal sealed class CommandTreeTokenStream : IReadOnlyList<CommandTreeToken> | ||||
|     private readonly List<CommandTreeToken> _tokens; | ||||
|     private int _position; | ||||
|  | ||||
|     public int Count => _tokens.Count; | ||||
|     public int Count => _tokens.Count; | ||||
|     public int Position => _position; | ||||
|  | ||||
|     public CommandTreeToken this[int index] => _tokens[index]; | ||||
|   | ||||
| @@ -29,13 +29,13 @@ internal static class CommandTreeTokenizer | ||||
|         var context = new CommandTreeTokenizerContext(); | ||||
|  | ||||
|         foreach (var arg in args) | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(arg)) | ||||
|             { | ||||
|                 // Null strings in the args array are still represented as tokens | ||||
|                 tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty)); | ||||
|                 continue; | ||||
|             } | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(arg)) | ||||
|             { | ||||
|                 // Null strings in the args array are still represented as tokens | ||||
|                 tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty)); | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             var start = position; | ||||
|             var reader = new TextBuffer(previousReader, arg); | ||||
| @@ -55,30 +55,30 @@ internal static class CommandTreeTokenizer | ||||
|     } | ||||
|  | ||||
|     private static int ParseToken(CommandTreeTokenizerContext context, TextBuffer reader, int position, int start, List<CommandTreeToken> tokens) | ||||
|     { | ||||
|         if (!reader.ReachedEnd && reader.Peek() == '-') | ||||
|         { | ||||
|             // Option | ||||
|             tokens.AddRange(ScanOptions(context, reader)); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Command or argument | ||||
|             while (reader.Peek() != -1) | ||||
|             { | ||||
|                 if (reader.ReachedEnd) | ||||
|                 { | ||||
|                     position += reader.Position - start; | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 tokens.Add(ScanString(context, reader)); | ||||
|  | ||||
|                 // Flush remaining tokens | ||||
|                 context.FlushRemaining(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|     { | ||||
|         if (!reader.ReachedEnd && reader.Peek() == '-') | ||||
|         { | ||||
|             // Option | ||||
|             tokens.AddRange(ScanOptions(context, reader)); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             // Command or argument | ||||
|             while (reader.Peek() != -1) | ||||
|             { | ||||
|                 if (reader.ReachedEnd) | ||||
|                 { | ||||
|                     position += reader.Position - start; | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 tokens.Add(ScanString(context, reader)); | ||||
|  | ||||
|                 // Flush remaining tokens | ||||
|                 context.FlushRemaining(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return position; | ||||
|     } | ||||
|  | ||||
| @@ -102,7 +102,7 @@ internal static class CommandTreeTokenizer | ||||
|             builder.Append(current); | ||||
|         } | ||||
|  | ||||
|         var value = builder.ToString(); | ||||
|         var value = builder.ToString(); | ||||
|         return new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value); | ||||
|     } | ||||
|  | ||||
| @@ -149,8 +149,8 @@ internal static class CommandTreeTokenizer | ||||
|                     var token = new CommandTreeToken(CommandTreeToken.Kind.String, reader.Position, "=", "="); | ||||
|                     throw CommandParseException.OptionValueWasExpected(reader.Original, token); | ||||
|                 } | ||||
|  | ||||
|                 var tokenValue = ScanString(context, reader); | ||||
|  | ||||
|                 var tokenValue = ScanString(context, reader); | ||||
|                 tokenValue.HadSeparator = true; | ||||
|                 result.Add(tokenValue); | ||||
|             } | ||||
| @@ -193,7 +193,7 @@ internal static class CommandTreeTokenizer | ||||
|                 // be tokenized as strings. This block handles parsing those cases, but we only allow this | ||||
|                 // when the digit is the first character in the token (i.e. "-a1" is always an error), hence the | ||||
|                 // result.Count == 0 check above. | ||||
|                 string value = string.Empty; | ||||
|                 string value = string.Empty; | ||||
|  | ||||
|                 while (!reader.ReachedEnd) | ||||
|                 { | ||||
| @@ -212,12 +212,12 @@ internal static class CommandTreeTokenizer | ||||
|                 result.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value)); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|             { | ||||
|                 // Create a token representing the short option. | ||||
|                 var representation = current.ToString(CultureInfo.InvariantCulture); | ||||
|                 var tokenPosition = position + 1 + result.Count; | ||||
|                 var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation); | ||||
|  | ||||
|                 var representation = current.ToString(CultureInfo.InvariantCulture); | ||||
|                 var tokenPosition = position + 1 + result.Count; | ||||
|                 var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation); | ||||
|  | ||||
|                 throw CommandParseException.InvalidShortOptionName(reader.Original, token); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -10,7 +10,7 @@ global using System.Linq; | ||||
| global using System.Reflection; | ||||
| global using System.Text; | ||||
| global using System.Threading.Tasks; | ||||
| global using System.Xml; | ||||
| global using System.Xml; | ||||
| global using Spectre.Console.Cli.Help; | ||||
| global using Spectre.Console.Cli.Unsafe; | ||||
| global using Spectre.Console.Rendering; | ||||
| @@ -1,153 +1,153 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| // <auto-generated> | ||||
| //     This code was generated by a tool. | ||||
| //     Runtime Version:4.0.30319.42000 | ||||
| // | ||||
| //     Changes to this file may cause incorrect behavior and will be lost if | ||||
| //     the code is regenerated. | ||||
| // </auto-generated> | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace Spectre.Console.Cli.Resources { | ||||
|     using System; | ||||
|      | ||||
|      | ||||
|     /// <summary> | ||||
|     ///   A strongly-typed resource class, for looking up localized strings, etc. | ||||
|     /// </summary> | ||||
|     // This class was auto-generated by the StronglyTypedResourceBuilder | ||||
|     // class via a tool like ResGen or Visual Studio. | ||||
|     // To add or remove a member, edit your .ResX file then rerun ResGen | ||||
|     // with the /str option, or rebuild your VS project. | ||||
|     [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] | ||||
|     [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] | ||||
|     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] | ||||
|     internal class HelpProvider { | ||||
|          | ||||
|         private static global::System.Resources.ResourceManager resourceMan; | ||||
|          | ||||
|         private static global::System.Globalization.CultureInfo resourceCulture; | ||||
|          | ||||
|         [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||||
|         internal HelpProvider() { | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Returns the cached ResourceManager instance used by this class. | ||||
|         /// </summary> | ||||
|         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | ||||
|         internal static global::System.Resources.ResourceManager ResourceManager { | ||||
|             get { | ||||
|                 if (object.ReferenceEquals(resourceMan, null)) { | ||||
|                     global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly); | ||||
|                     resourceMan = temp; | ||||
|                 } | ||||
|                 return resourceMan; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Overrides the current thread's CurrentUICulture property for all | ||||
|         ///   resource lookups using this strongly typed resource class. | ||||
|         /// </summary> | ||||
|         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | ||||
|         internal static global::System.Globalization.CultureInfo Culture { | ||||
|             get { | ||||
|                 return resourceCulture; | ||||
|             } | ||||
|             set { | ||||
|                 resourceCulture = value; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to ARGUMENTS. | ||||
|         /// </summary> | ||||
|         internal static string Arguments { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Arguments", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to COMMAND. | ||||
|         /// </summary> | ||||
|         internal static string Command { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Command", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to COMMANDS. | ||||
|         /// </summary> | ||||
|         internal static string Commands { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Commands", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to DEFAULT. | ||||
|         /// </summary> | ||||
|         internal static string Default { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Default", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to DESCRIPTION. | ||||
|         /// </summary> | ||||
|         internal static string Description { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Description", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to EXAMPLES. | ||||
|         /// </summary> | ||||
|         internal static string Examples { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Examples", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to OPTIONS. | ||||
|         /// </summary> | ||||
|         internal static string Options { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Options", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to Prints help information. | ||||
|         /// </summary> | ||||
|         internal static string PrintHelpDescription { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("PrintHelpDescription", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to Prints version information. | ||||
|         /// </summary> | ||||
|         internal static string PrintVersionDescription { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("PrintVersionDescription", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to USAGE. | ||||
|         /// </summary> | ||||
|         internal static string Usage { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Usage", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| //------------------------------------------------------------------------------ | ||||
| // <auto-generated> | ||||
| //     This code was generated by a tool. | ||||
| //     Runtime Version:4.0.30319.42000 | ||||
| // | ||||
| //     Changes to this file may cause incorrect behavior and will be lost if | ||||
| //     the code is regenerated. | ||||
| // </auto-generated> | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace Spectre.Console.Cli.Resources { | ||||
|     using System; | ||||
|      | ||||
|      | ||||
|     /// <summary> | ||||
|     ///   A strongly-typed resource class, for looking up localized strings, etc. | ||||
|     /// </summary> | ||||
|     // This class was auto-generated by the StronglyTypedResourceBuilder | ||||
|     // class via a tool like ResGen or Visual Studio. | ||||
|     // To add or remove a member, edit your .ResX file then rerun ResGen | ||||
|     // with the /str option, or rebuild your VS project. | ||||
|     [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] | ||||
|     [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] | ||||
|     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] | ||||
|     internal class HelpProvider { | ||||
|          | ||||
|         private static global::System.Resources.ResourceManager resourceMan; | ||||
|          | ||||
|         private static global::System.Globalization.CultureInfo resourceCulture; | ||||
|          | ||||
|         [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||||
|         internal HelpProvider() { | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Returns the cached ResourceManager instance used by this class. | ||||
|         /// </summary> | ||||
|         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | ||||
|         internal static global::System.Resources.ResourceManager ResourceManager { | ||||
|             get { | ||||
|                 if (object.ReferenceEquals(resourceMan, null)) { | ||||
|                     global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly); | ||||
|                     resourceMan = temp; | ||||
|                 } | ||||
|                 return resourceMan; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Overrides the current thread's CurrentUICulture property for all | ||||
|         ///   resource lookups using this strongly typed resource class. | ||||
|         /// </summary> | ||||
|         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | ||||
|         internal static global::System.Globalization.CultureInfo Culture { | ||||
|             get { | ||||
|                 return resourceCulture; | ||||
|             } | ||||
|             set { | ||||
|                 resourceCulture = value; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to ARGUMENTS. | ||||
|         /// </summary> | ||||
|         internal static string Arguments { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Arguments", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to COMMAND. | ||||
|         /// </summary> | ||||
|         internal static string Command { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Command", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to COMMANDS. | ||||
|         /// </summary> | ||||
|         internal static string Commands { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Commands", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to DEFAULT. | ||||
|         /// </summary> | ||||
|         internal static string Default { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Default", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to DESCRIPTION. | ||||
|         /// </summary> | ||||
|         internal static string Description { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Description", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to EXAMPLES. | ||||
|         /// </summary> | ||||
|         internal static string Examples { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Examples", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to OPTIONS. | ||||
|         /// </summary> | ||||
|         internal static string Options { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Options", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to Prints help information. | ||||
|         /// </summary> | ||||
|         internal static string PrintHelpDescription { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("PrintHelpDescription", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to Prints version information. | ||||
|         /// </summary> | ||||
|         internal static string PrintVersionDescription { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("PrintVersionDescription", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized string similar to USAGE. | ||||
|         /// </summary> | ||||
|         internal static string Usage { | ||||
|             get { | ||||
|                 return ResourceManager.GetString("Usage", resourceCulture); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -141,8 +141,8 @@ public sealed class CommandAppTester | ||||
|             .Trim(); | ||||
|  | ||||
|         return new CommandAppResult(result, output, context, settings); | ||||
|     } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Runs the command application asynchronously. | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -60,7 +60,7 @@ public static partial class AnsiConsoleExtensions | ||||
|  | ||||
|             if (!char.IsControl(key.KeyChar)) | ||||
|             { | ||||
|                 text += key.KeyChar.ToString(); | ||||
|                 text += key.KeyChar.ToString(); | ||||
|                 var output = key.KeyChar.ToString(); | ||||
|                 console.Write(secret ? output.Mask(mask) : output, style); | ||||
|             } | ||||
|   | ||||
| @@ -185,28 +185,28 @@ public static class StringExtensions | ||||
| #else | ||||
|         return text.Contains(value, StringComparison.Ordinal); | ||||
| #endif | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// "Masks" every character in a string. | ||||
|     /// </summary> | ||||
|     /// <param name="value">String value to mask.</param> | ||||
|     /// <param name="mask">Character to use for masking.</param> | ||||
|     /// <returns>Masked string.</returns> | ||||
|     public static string Mask(this string value, char? mask) | ||||
|     { | ||||
|         var output = string.Empty; | ||||
|  | ||||
|         if (mask is null) | ||||
|         { | ||||
|             return output; | ||||
|         } | ||||
|  | ||||
|         foreach (var c in value) | ||||
|         { | ||||
|             output += mask; | ||||
|         } | ||||
|  | ||||
|         return output; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// "Masks" every character in a string. | ||||
|     /// </summary> | ||||
|     /// <param name="value">String value to mask.</param> | ||||
|     /// <param name="mask">Character to use for masking.</param> | ||||
|     /// <returns>Masked string.</returns> | ||||
|     public static string Mask(this string value, char? mask) | ||||
|     { | ||||
|         var output = string.Empty; | ||||
|  | ||||
|         if (mask is null) | ||||
|         { | ||||
|             return output; | ||||
|         } | ||||
|  | ||||
|         foreach (var c in value) | ||||
|         { | ||||
|             output += mask; | ||||
|         } | ||||
|  | ||||
|         return output; | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace Spectre.Console; | ||||
| namespace Spectre.Console; | ||||
|  | ||||
| /// <summary> | ||||
| /// Represents horizontal alignment. | ||||
|   | ||||
| @@ -286,25 +286,25 @@ public static class TextPromptExtensions | ||||
|  | ||||
|         obj.IsSecret = true; | ||||
|         return obj; | ||||
|     } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Replaces prompt user input with mask in the console. | ||||
|     /// </summary> | ||||
|     /// <typeparam name="T">The prompt type.</typeparam> | ||||
|     /// <param name="obj">The prompt.</param> | ||||
|     /// <param name="obj">The prompt.</param> | ||||
|     /// <param name="mask">The masking character to use for the secret.</param> | ||||
|     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|     public static TextPrompt<T> Secret<T>(this TextPrompt<T> obj, char? mask) | ||||
|     { | ||||
|         if (obj is null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(obj)); | ||||
|         } | ||||
|  | ||||
|         obj.IsSecret = true; | ||||
|         obj.Mask = mask; | ||||
|         return obj; | ||||
|     /// <returns>The same instance so that multiple calls can be chained.</returns> | ||||
|     public static TextPrompt<T> Secret<T>(this TextPrompt<T> obj, char? mask) | ||||
|     { | ||||
|         if (obj is null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(obj)); | ||||
|         } | ||||
|  | ||||
|         obj.IsSecret = true; | ||||
|         obj.Mask = mask; | ||||
|         return obj; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace Spectre.Console; | ||||
| namespace Spectre.Console; | ||||
|  | ||||
| /// <summary> | ||||
| /// Represents vertical alignment. | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace Spectre.Console; | ||||
| namespace Spectre.Console; | ||||
|  | ||||
| internal static class TypeNameHelper | ||||
| { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace Spectre.Console; | ||||
| namespace Spectre.Console; | ||||
|  | ||||
| [DebuggerDisplay("{Region,nq}")] | ||||
| internal sealed class LayoutRender | ||||
|   | ||||
| @@ -5,7 +5,7 @@ public static class SpectreAnalyzerVerifier<TAnalyzer> | ||||
| { | ||||
|     public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource) | ||||
|         => VerifyCodeFixAsync(source, OutputKind.DynamicallyLinkedLibrary, new[] { expected }, fixedSource); | ||||
|  | ||||
|  | ||||
|     public static Task VerifyCodeFixAsync(string source, OutputKind outputKind, DiagnosticResult expected, string fixedSource) | ||||
|         => VerifyCodeFixAsync(source, outputKind, new[] { expected }, fixedSource); | ||||
|  | ||||
| @@ -13,10 +13,10 @@ public static class SpectreAnalyzerVerifier<TAnalyzer> | ||||
|     { | ||||
|         var test = new Test | ||||
|         { | ||||
|             TestCode = source, | ||||
|             TestState = | ||||
|             { | ||||
|                 OutputKind = outputKind, | ||||
|             TestCode = source, | ||||
|             TestState = | ||||
|             { | ||||
|                 OutputKind = outputKind, | ||||
|             }, | ||||
|             FixedCode = fixedSource, | ||||
|         }; | ||||
|   | ||||
| @@ -1,28 +1,28 @@ | ||||
| namespace Spectre.Console.Tests.Data; | ||||
|  | ||||
| public sealed class AsynchronousCommand : AsyncCommand<AsynchronousCommandSettings> | ||||
| { | ||||
|     private readonly IAnsiConsole _console; | ||||
|  | ||||
|     public AsynchronousCommand(IAnsiConsole console) | ||||
|     { | ||||
|         _console = console; | ||||
|     } | ||||
|  | ||||
|     public async override Task<int> ExecuteAsync(CommandContext context, AsynchronousCommandSettings settings) | ||||
|     { | ||||
|         // Simulate a long running asynchronous task | ||||
|         await Task.Delay(200); | ||||
|  | ||||
|         if (settings.ThrowException) | ||||
|         { | ||||
|             throw new Exception($"Throwing exception asynchronously"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             _console.WriteLine($"Finished executing asynchronously"); | ||||
|         } | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
| namespace Spectre.Console.Tests.Data; | ||||
|  | ||||
| public sealed class AsynchronousCommand : AsyncCommand<AsynchronousCommandSettings> | ||||
| { | ||||
|     private readonly IAnsiConsole _console; | ||||
|  | ||||
|     public AsynchronousCommand(IAnsiConsole console) | ||||
|     { | ||||
|         _console = console; | ||||
|     } | ||||
|  | ||||
|     public async override Task<int> ExecuteAsync(CommandContext context, AsynchronousCommandSettings settings) | ||||
|     { | ||||
|         // Simulate a long running asynchronous task | ||||
|         await Task.Delay(200); | ||||
|  | ||||
|         if (settings.ThrowException) | ||||
|         { | ||||
|             throw new Exception($"Throwing exception asynchronously"); | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             _console.WriteLine($"Finished executing asynchronously"); | ||||
|         } | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| using Spectre.Console; | ||||
|  | ||||
| public class GreeterCommand : Command<OptionalArgumentWithDefaultValueSettings> | ||||
| { | ||||
|     private readonly IAnsiConsole _console; | ||||
|  | ||||
|     public GreeterCommand(IAnsiConsole console) | ||||
|     { | ||||
|         _console = console; | ||||
|     } | ||||
|  | ||||
|     public override int Execute(CommandContext context, OptionalArgumentWithDefaultValueSettings settings) | ||||
|     { | ||||
|         _console.WriteLine(settings.Greeting); | ||||
|         return 0; | ||||
|     } | ||||
| using Spectre.Console; | ||||
|  | ||||
| public class GreeterCommand : Command<OptionalArgumentWithDefaultValueSettings> | ||||
| { | ||||
|     private readonly IAnsiConsole _console; | ||||
|  | ||||
|     public GreeterCommand(IAnsiConsole console) | ||||
|     { | ||||
|         _console = console; | ||||
|     } | ||||
|  | ||||
|     public override int Execute(CommandContext context, OptionalArgumentWithDefaultValueSettings settings) | ||||
|     { | ||||
|         _console.WriteLine(settings.Greeting); | ||||
|         return 0; | ||||
|     } | ||||
| } | ||||
| @@ -1,34 +1,34 @@ | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console.Cli.Tests.Data.Help; | ||||
|  | ||||
| internal class CustomHelpProvider : HelpProvider | ||||
| { | ||||
|     private readonly string version; | ||||
|  | ||||
|     public CustomHelpProvider(ICommandAppSettings settings, string version) | ||||
|         : base(settings) | ||||
|     { | ||||
|         this.version = version; | ||||
|     } | ||||
|  | ||||
|     public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo command) | ||||
|     { | ||||
|         return new IRenderable[] | ||||
|         { | ||||
|             new Text("--------------------------------------"), Text.NewLine, | ||||
|             new Text("---      CUSTOM HELP PROVIDER      ---"), Text.NewLine, | ||||
|             new Text("--------------------------------------"), Text.NewLine, | ||||
|             Text.NewLine, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public override IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo command) | ||||
|     { | ||||
|         return new IRenderable[] | ||||
|         { | ||||
|             Text.NewLine, | ||||
|             new Text($"Version {version}"), | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console.Cli.Tests.Data.Help; | ||||
|  | ||||
| internal class CustomHelpProvider : HelpProvider | ||||
| { | ||||
|     private readonly string version; | ||||
|  | ||||
|     public CustomHelpProvider(ICommandAppSettings settings, string version) | ||||
|         : base(settings) | ||||
|     { | ||||
|         this.version = version; | ||||
|     } | ||||
|  | ||||
|     public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo command) | ||||
|     { | ||||
|         return new IRenderable[] | ||||
|         { | ||||
|             new Text("--------------------------------------"), Text.NewLine, | ||||
|             new Text("---      CUSTOM HELP PROVIDER      ---"), Text.NewLine, | ||||
|             new Text("--------------------------------------"), Text.NewLine, | ||||
|             Text.NewLine, | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public override IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo command) | ||||
|     { | ||||
|         return new IRenderable[] | ||||
|         { | ||||
|             Text.NewLine, | ||||
|             new Text($"Version {version}"), | ||||
|         }; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,21 +1,21 @@ | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console.Cli.Tests.Data.Help; | ||||
|  | ||||
| internal class RedirectHelpProvider : IHelpProvider | ||||
| { | ||||
|     public virtual IEnumerable<IRenderable> Write(ICommandModel model) | ||||
|     { | ||||
|         return Write(model, null); | ||||
|     } | ||||
| #nullable enable | ||||
|     public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command) | ||||
| #nullable disable | ||||
|     { | ||||
|         return new[] | ||||
|         { | ||||
|             new Text("Help has moved online. Please see: http://www.example.com"), | ||||
|             Text.NewLine, | ||||
|         }; | ||||
|     } | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console.Cli.Tests.Data.Help; | ||||
|  | ||||
| internal class RedirectHelpProvider : IHelpProvider | ||||
| { | ||||
|     public virtual IEnumerable<IRenderable> Write(ICommandModel model) | ||||
|     { | ||||
|         return Write(model, null); | ||||
|     } | ||||
| #nullable enable | ||||
|     public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command) | ||||
| #nullable disable | ||||
|     { | ||||
|         return new[] | ||||
|         { | ||||
|             new Text("Help has moved online. Please see: http://www.example.com"), | ||||
|             Text.NewLine, | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| @@ -1,8 +1,8 @@ | ||||
| namespace Spectre.Console.Tests.Data; | ||||
|  | ||||
| public sealed class AsynchronousCommandSettings : CommandSettings | ||||
| { | ||||
|     [CommandOption("--ThrowException")] | ||||
|     [DefaultValue(false)] | ||||
|     public bool ThrowException { get; set; } | ||||
| namespace Spectre.Console.Tests.Data; | ||||
|  | ||||
| public sealed class AsynchronousCommandSettings : CommandSettings | ||||
| { | ||||
|     [CommandOption("--ThrowException")] | ||||
|     [DefaultValue(false)] | ||||
|     public bool ThrowException { get; set; } | ||||
| } | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace Spectre.Console.Tests.Data; | ||||
| namespace Spectre.Console.Tests.Data; | ||||
|  | ||||
| public class ReptileSettings : AnimalSettings | ||||
| { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| namespace Spectre.Console.Tests.Data; | ||||
|  | ||||
| public sealed class ThrowingCommandSettings : CommandSettings | ||||
| { | ||||
| } | ||||
| namespace Spectre.Console.Tests.Data; | ||||
|  | ||||
| public sealed class ThrowingCommandSettings : CommandSettings | ||||
| { | ||||
| } | ||||
|   | ||||
| @@ -7,7 +7,7 @@ global using System.Linq; | ||||
| global using System.Runtime.CompilerServices; | ||||
| global using System.Threading.Tasks; | ||||
| global using Shouldly; | ||||
| global using Spectre.Console.Cli; | ||||
| global using Spectre.Console.Cli; | ||||
| global using Spectre.Console.Cli.Help; | ||||
| global using Spectre.Console.Cli.Unsafe; | ||||
| global using Spectre.Console.Testing; | ||||
|   | ||||
| @@ -1,70 +1,70 @@ | ||||
| namespace Spectre.Console.Tests.Unit.Cli; | ||||
|  | ||||
| public sealed partial class CommandAppTests | ||||
| { | ||||
|     public sealed class Async | ||||
|     { | ||||
|         [Fact] | ||||
|         public async void Should_Execute_Command_Asynchronously() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.SetDefaultCommand<AsynchronousCommand>(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = await app.RunAsync(); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Output.ShouldBe("Finished executing asynchronously"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public async void Should_Handle_Exception_Asynchronously() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.SetDefaultCommand<AsynchronousCommand>(); | ||||
|  | ||||
|             // When | ||||
|             var result = await app.RunAsync(new[] | ||||
|                         { | ||||
|                         "--ThrowException", | ||||
|                         "true", | ||||
|                         }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(-1); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public async void Should_Throw_Exception_Asynchronously() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.SetDefaultCommand<AsynchronousCommand>(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = await Record.ExceptionAsync(async () => | ||||
|                     await app.RunAsync(new[] | ||||
|                         { | ||||
|                         "--ThrowException", | ||||
|                         "true", | ||||
|                         })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<Exception>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("Throwing exception asynchronously"); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| namespace Spectre.Console.Tests.Unit.Cli; | ||||
|  | ||||
| public sealed partial class CommandAppTests | ||||
| { | ||||
|     public sealed class Async | ||||
|     { | ||||
|         [Fact] | ||||
|         public async void Should_Execute_Command_Asynchronously() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.SetDefaultCommand<AsynchronousCommand>(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = await app.RunAsync(); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Output.ShouldBe("Finished executing asynchronously"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public async void Should_Handle_Exception_Asynchronously() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.SetDefaultCommand<AsynchronousCommand>(); | ||||
|  | ||||
|             // When | ||||
|             var result = await app.RunAsync(new[] | ||||
|                         { | ||||
|                         "--ThrowException", | ||||
|                         "true", | ||||
|                         }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(-1); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public async void Should_Throw_Exception_Asynchronously() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.SetDefaultCommand<AsynchronousCommand>(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = await Record.ExceptionAsync(async () => | ||||
|                     await app.RunAsync(new[] | ||||
|                         { | ||||
|                         "--ThrowException", | ||||
|                         "true", | ||||
|                         })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<Exception>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("Throwing exception asynchronously"); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -3,133 +3,133 @@ namespace Spectre.Console.Tests.Unit.Cli; | ||||
| public sealed partial class CommandAppTests | ||||
| { | ||||
|     public sealed class Branches | ||||
|     { | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Run_The_Default_Command_On_Branch() | ||||
|         { | ||||
|             // Given | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.SetDefaultCommand<CatCommand>(); | ||||
|                     animal.SetDefaultCommand<CatCommand>(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "animal", "4", | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>(); | ||||
|         } | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Throw_When_No_Default_Command_On_Branch() | ||||
|         { | ||||
|             // Given | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddBranch<AnimalSettings>("animal", animal => { }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => | ||||
|             { | ||||
|                 app.Run(new[] | ||||
|                 { | ||||
|                     "animal", "4", | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandConfigurationException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("The branch 'animal' does not define any commands."); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:SingleLineCommentMustBePrecededByBlankLine", Justification = "Helps to illustrate the expected behaviour of this unit test.")] | ||||
|         [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1005:SingleLineCommentsMustBeginWithSingleSpace", Justification = "Helps to illustrate the expected behaviour of this unit test.")] | ||||
|         [Fact] | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => | ||||
|             { | ||||
|                 app.Run(new[] | ||||
|                 { | ||||
|                     "animal", "4", | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandConfigurationException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("The branch 'animal' does not define any commands."); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:SingleLineCommentMustBePrecededByBlankLine", Justification = "Helps to illustrate the expected behaviour of this unit test.")] | ||||
|         [SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1005:SingleLineCommentsMustBeginWithSingleSpace", Justification = "Helps to illustrate the expected behaviour of this unit test.")] | ||||
|         [Fact] | ||||
|         public void Should_Be_Unable_To_Parse_Default_Command_Arguments_Relaxed_Parsing() | ||||
|         { | ||||
|             // Given | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.SetDefaultCommand<CatCommand>(); | ||||
|                     animal.SetDefaultCommand<CatCommand>(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 // The CommandTreeParser should be unable to determine which command line | ||||
|                 // arguments belong to the branch and which belong to the branch's | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 // The CommandTreeParser should be unable to determine which command line | ||||
|                 // arguments belong to the branch and which belong to the branch's | ||||
|                 // default command (once inserted). | ||||
|                 "animal", "4", "--name", "Kitty", | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>().And(cat => | ||||
|             { | ||||
|                 cat.Legs.ShouldBe(4); | ||||
|                 //cat.Name.ShouldBe("Kitty"); //<-- Should normally be correct, but instead name will be added to the remaining arguments (see below). | ||||
|             }); | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>().And(cat => | ||||
|             { | ||||
|                 cat.Legs.ShouldBe(4); | ||||
|                 //cat.Name.ShouldBe("Kitty"); //<-- Should normally be correct, but instead name will be added to the remaining arguments (see below). | ||||
|             }); | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(1); | ||||
|             result.Context.ShouldHaveRemainingArgument("name", values: new[] { "Kitty", }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|             result.Context.ShouldHaveRemainingArgument("name", values: new[] { "Kitty", }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Be_Unable_To_Parse_Default_Command_Arguments_Strict_Parsing() | ||||
|         { | ||||
|             // Given | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|                 config.UseStrictParsing(); | ||||
|             { | ||||
|                 config.UseStrictParsing(); | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.SetDefaultCommand<CatCommand>(); | ||||
|                     animal.SetDefaultCommand<CatCommand>(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => | ||||
|             { | ||||
|                 app.Run(new[] | ||||
|                 { | ||||
|                     // The CommandTreeParser should be unable to determine which command line | ||||
|                     // arguments belong to the branch and which belong to the branch's | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => | ||||
|             { | ||||
|                 app.Run(new[] | ||||
|                 { | ||||
|                     // The CommandTreeParser should be unable to determine which command line | ||||
|                     // arguments belong to the branch and which belong to the branch's | ||||
|                     // default command (once inserted). | ||||
|                     "animal", "4", "--name", "Kitty", | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("Unknown option 'name'."); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|                     "animal", "4", "--name", "Kitty", | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("Unknown option 'name'."); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Run_The_Default_Command_On_Branch_On_Branch() | ||||
|         { | ||||
|             // Given | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
| @@ -141,23 +141,23 @@ public sealed partial class CommandAppTests | ||||
|                         mammal.SetDefaultCommand<CatCommand>(); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "animal", "4", "mammal", | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>(); | ||||
|         } | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Run_The_Default_Command_On_Branch_On_Branch_With_Arguments() | ||||
|         { | ||||
|             // Given | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
| @@ -169,83 +169,83 @@ public sealed partial class CommandAppTests | ||||
|                         mammal.SetDefaultCommand<CatCommand>(); | ||||
|                     }); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "animal", "4", "mammal", "--name", "Kitty", | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>().And(cat => | ||||
|             { | ||||
|                 cat.Legs.ShouldBe(4); | ||||
|                 cat.Name.ShouldBe("Kitty"); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>().And(cat => | ||||
|             { | ||||
|                 cat.Legs.ShouldBe(4); | ||||
|                 cat.Name.ShouldBe("Kitty"); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Run_The_Default_Command_Not_The_Named_Command_On_Branch() | ||||
|         { | ||||
|             // Given | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.AddCommand<DogCommand>("dog"); | ||||
|                     animal.AddCommand<DogCommand>("dog"); | ||||
|  | ||||
|                     animal.SetDefaultCommand<CatCommand>(); | ||||
|                     animal.SetDefaultCommand<CatCommand>(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "animal", "4", | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>(); | ||||
|         } | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<CatSettings>(); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Run_The_Named_Command_Not_The_Default_Command_On_Branch() | ||||
|         { | ||||
|             // Given | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.AddCommand<DogCommand>("dog"); | ||||
|                     animal.AddCommand<DogCommand>("dog"); | ||||
|  | ||||
|                     animal.SetDefaultCommand<LionCommand>(); | ||||
|                     animal.SetDefaultCommand<LionCommand>(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "animal", "4", "dog", "12", "--good-boy", "--name", "Rufus", | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<DogSettings>().And(dog => | ||||
|             { | ||||
|                 dog.Legs.ShouldBe(4); | ||||
|                 dog.Age.ShouldBe(12); | ||||
|                 dog.GoodBoy.ShouldBe(true); | ||||
|                 dog.Name.ShouldBe("Rufus"); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<DogSettings>().And(dog => | ||||
|             { | ||||
|                 dog.Legs.ShouldBe(4); | ||||
|                 dog.Age.ShouldBe(12); | ||||
|                 dog.GoodBoy.ShouldBe(true); | ||||
|                 dog.Name.ShouldBe("Rufus"); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Allow_Multiple_Branches_Multiple_Commands() | ||||
|         { | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| using Spectre.Console.Cli.Tests.Data.Help; | ||||
|  | ||||
| using Spectre.Console.Cli.Tests.Data.Help; | ||||
|  | ||||
| namespace Spectre.Console.Tests.Unit.Cli; | ||||
|  | ||||
| public sealed partial class CommandAppTests | ||||
| @@ -93,12 +93,12 @@ public sealed partial class CommandAppTests | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("cat", "--help"); | ||||
|             var result = fixture.Run("cat", "--help"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Branch_Called_Without_Help")] | ||||
|         public Task Should_Output_Branch_When_Called_Without_Help_Option() | ||||
| @@ -110,27 +110,27 @@ public sealed partial class CommandAppTests | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddBranch<CatSettings>("cat", animal => | ||||
|                 { | ||||
|                     animal.SetDescription("Contains settings for a cat."); | ||||
|                     animal.SetDescription("Contains settings for a cat."); | ||||
|                     animal.AddCommand<LionCommand>("lion"); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("cat"); | ||||
|             var result = fixture.Run("cat"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Branch_Default_Greeter")] | ||||
|         [Expectation("Branch_Default_Greeter")] | ||||
|         public Task Should_Output_Branch_With_Default_Correctly() | ||||
|         { | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(); | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddBranch<OptionalArgumentWithDefaultValueSettings>("branch", animal => | ||||
|                 { | ||||
|                     animal.SetDefaultCommand<GreeterCommand>(); | ||||
| @@ -138,8 +138,8 @@ public sealed partial class CommandAppTests | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("branch", "--help"); | ||||
|             // When | ||||
|             var result = fixture.Run("branch", "--help"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
| @@ -186,7 +186,7 @@ public sealed partial class CommandAppTests | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("cat", "lion", "--help"); | ||||
|             var result = fixture.Run("cat", "lion", "--help"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
| @@ -233,13 +233,13 @@ public sealed partial class CommandAppTests | ||||
|         [Theory] | ||||
|         [InlineData(null, "EN")] | ||||
|         [InlineData("", "EN")] | ||||
|         [InlineData("en", "EN")] | ||||
|         [InlineData("en", "EN")] | ||||
|         [InlineData("en-EN", "EN")] | ||||
|         [InlineData("fr", "FR")] | ||||
|         [InlineData("fr", "FR")] | ||||
|         [InlineData("fr-FR", "FR")] | ||||
|         [InlineData("sv", "SV")] | ||||
|         [InlineData("sv", "SV")] | ||||
|         [InlineData("sv-SE", "SV")] | ||||
|         [InlineData("de", "DE")] | ||||
|         [InlineData("de", "DE")] | ||||
|         [InlineData("de-DE", "DE")] | ||||
|         [Expectation("Default_Without_Args_Additional")] | ||||
|         public Task Should_Output_Default_Command_And_Additional_Commands_When_Default_Command_Has_Required_Parameters_And_Is_Called_Without_Args_Localised(string culture, string expectationPrefix) | ||||
| @@ -281,106 +281,106 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Custom_Help_Registered_By_Instance")] | ||||
|         public Task Should_Output_Custom_Help_When_Registered_By_Instance() | ||||
|         { | ||||
|             var registrar = new DefaultTypeRegistrar(); | ||||
|  | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(registrar); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 // Create the custom help provider | ||||
|                 var helpProvider = new CustomHelpProvider(configurator.Settings, "1.0"); | ||||
|  | ||||
|                 // Register the custom help provider instance | ||||
|                 registrar.RegisterInstance(typeof(IHelpProvider), helpProvider); | ||||
|  | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run(); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             var registrar = new DefaultTypeRegistrar(); | ||||
|  | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(registrar); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 // Create the custom help provider | ||||
|                 var helpProvider = new CustomHelpProvider(configurator.Settings, "1.0"); | ||||
|  | ||||
|                 // Register the custom help provider instance | ||||
|                 registrar.RegisterInstance(typeof(IHelpProvider), helpProvider); | ||||
|  | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run(); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Custom_Help_Registered_By_Type")] | ||||
|         public Task Should_Output_Custom_Help_When_Registered_By_Type() | ||||
|         { | ||||
|             var registrar = new DefaultTypeRegistrar(); | ||||
|  | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(registrar); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 // Register the custom help provider type | ||||
|                 registrar.Register(typeof(IHelpProvider), typeof(RedirectHelpProvider)); | ||||
|  | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run(); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             var registrar = new DefaultTypeRegistrar(); | ||||
|  | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(registrar); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 // Register the custom help provider type | ||||
|                 registrar.Register(typeof(IHelpProvider), typeof(RedirectHelpProvider)); | ||||
|  | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run(); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Custom_Help_Configured_By_Instance")] | ||||
|         public Task Should_Output_Custom_Help_When_Configured_By_Instance() | ||||
|         { | ||||
|             var registrar = new DefaultTypeRegistrar(); | ||||
|  | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(registrar); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 // Configure the custom help provider instance | ||||
|                 configurator.SetHelpProvider(new CustomHelpProvider(configurator.Settings, "1.0")); | ||||
|  | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run(); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         { | ||||
|             var registrar = new DefaultTypeRegistrar(); | ||||
|  | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(registrar); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 // Configure the custom help provider instance | ||||
|                 configurator.SetHelpProvider(new CustomHelpProvider(configurator.Settings, "1.0")); | ||||
|  | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run(); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Custom_Help_Configured_By_Type")] | ||||
|         public Task Should_Output_Custom_Help_When_Configured_By_Type() | ||||
|         { | ||||
|             var registrar = new DefaultTypeRegistrar(); | ||||
|  | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(registrar); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 // Configure the custom help provider type | ||||
|                 configurator.SetHelpProvider<RedirectHelpProvider>(); | ||||
|  | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run(); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|         { | ||||
|             var registrar = new DefaultTypeRegistrar(); | ||||
|  | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(registrar); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 // Configure the custom help provider type | ||||
|                 configurator.SetHelpProvider<RedirectHelpProvider>(); | ||||
|  | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run(); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples")] | ||||
| @@ -391,20 +391,20 @@ public sealed partial class CommandAppTests | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // All root examples should be shown | ||||
|                 configurator.AddExample("dog", "--name", "Rufus", "--age", "12", "--good-boy"); | ||||
|                 configurator.AddExample("dog", "--name", "Luna"); | ||||
|                 configurator.AddExample("dog", "--name", "Charlie"); | ||||
|                 configurator.AddExample("dog", "--name", "Bella"); | ||||
|                 configurator.AddExample("dog", "--name", "Daisy"); | ||||
|  | ||||
|                 // All root examples should be shown | ||||
|                 configurator.AddExample("dog", "--name", "Rufus", "--age", "12", "--good-boy"); | ||||
|                 configurator.AddExample("dog", "--name", "Luna"); | ||||
|                 configurator.AddExample("dog", "--name", "Charlie"); | ||||
|                 configurator.AddExample("dog", "--name", "Bella"); | ||||
|                 configurator.AddExample("dog", "--name", "Daisy"); | ||||
|                 configurator.AddExample("dog", "--name", "Milo"); | ||||
|                 configurator.AddExample("horse", "--name", "Brutus"); | ||||
|                 configurator.AddExample("horse", "--name", "Sugar", "--IsAlive", "false"); | ||||
|                 configurator.AddExample("horse", "--name", "Cash"); | ||||
|                 configurator.AddExample("horse", "--name", "Dakota"); | ||||
|                 configurator.AddExample("horse", "--name", "Cisco"); | ||||
|                 configurator.AddExample("horse", "--name", "Spirit"); | ||||
|                 configurator.AddExample("horse", "--name", "Brutus"); | ||||
|                 configurator.AddExample("horse", "--name", "Sugar", "--IsAlive", "false"); | ||||
|                 configurator.AddExample("horse", "--name", "Cash"); | ||||
|                 configurator.AddExample("horse", "--name", "Dakota"); | ||||
|                 configurator.AddExample("horse", "--name", "Cisco"); | ||||
|                 configurator.AddExample("horse", "--name", "Spirit"); | ||||
|  | ||||
|                 configurator.AddCommand<DogCommand>("dog"); | ||||
|                 configurator.AddCommand<HorseCommand>("horse"); | ||||
| @@ -415,10 +415,10 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Children")] | ||||
|         [Expectation("Root_Examples_Children")] | ||||
|         [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:SingleLineCommentsMustNotBeFollowedByBlankLine", Justification = "Single line comment is relevant to several code blocks that follow.")] | ||||
|         public Task Should_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples() | ||||
|         { | ||||
| @@ -426,135 +426,24 @@ public sealed partial class CommandAppTests | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // It should be capped to the first 5 examples by default | ||||
|  | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // It should be capped to the first 5 examples by default | ||||
|  | ||||
|                 configurator.AddCommand<DogCommand>("dog") | ||||
|                     .WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                     .WithExample("dog", "--name", "Luna") | ||||
|                     .WithExample("dog", "--name", "Charlie") | ||||
|                     .WithExample("dog", "--name", "Bella") | ||||
|                     .WithExample("dog", "--name", "Daisy") | ||||
|                     .WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                     .WithExample("dog", "--name", "Luna") | ||||
|                     .WithExample("dog", "--name", "Charlie") | ||||
|                     .WithExample("dog", "--name", "Bella") | ||||
|                     .WithExample("dog", "--name", "Daisy") | ||||
|                     .WithExample("dog", "--name", "Milo"); | ||||
|  | ||||
|  | ||||
|                 configurator.AddCommand<HorseCommand>("horse") | ||||
|                     .WithExample("horse", "--name", "Brutus") | ||||
|                     .WithExample("horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                     .WithExample("horse", "--name", "Cash") | ||||
|                     .WithExample("horse", "--name", "Dakota") | ||||
|                     .WithExample("horse", "--name", "Cisco") | ||||
|                     .WithExample("horse", "--name", "Spirit"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("--help"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Children_Eight")] | ||||
|         public Task Should_Output_Eight_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples() | ||||
|         { | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // Show the first 8 examples defined on the direct children | ||||
|                 configurator.Settings.MaximumIndirectExamples = 8; | ||||
|  | ||||
|                 configurator.AddCommand<DogCommand>("dog") | ||||
|                     .WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                     .WithExample("dog", "--name", "Luna") | ||||
|                     .WithExample("dog", "--name", "Charlie") | ||||
|                     .WithExample("dog", "--name", "Bella") | ||||
|                     .WithExample("dog", "--name", "Daisy") | ||||
|                     .WithExample("dog", "--name", "Milo"); | ||||
|  | ||||
|                 configurator.AddCommand<HorseCommand>("horse") | ||||
|                     .WithExample("horse", "--name", "Brutus") | ||||
|                     .WithExample("horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                     .WithExample("horse", "--name", "Cash") | ||||
|                     .WithExample("horse", "--name", "Dakota") | ||||
|                     .WithExample("horse", "--name", "Cisco") | ||||
|                     .WithExample("horse", "--name", "Spirit"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("--help"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Children_Twelve")] | ||||
|         public Task Should_Output_All_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples() | ||||
|         { | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // Show all examples defined on the direct children | ||||
|                 configurator.Settings.MaximumIndirectExamples = int.MaxValue; | ||||
|  | ||||
|                 configurator.AddCommand<DogCommand>("dog") | ||||
|                     .WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                     .WithExample("dog", "--name", "Luna") | ||||
|                     .WithExample("dog", "--name", "Charlie") | ||||
|                     .WithExample("dog", "--name", "Bella") | ||||
|                     .WithExample("dog", "--name", "Daisy") | ||||
|                     .WithExample("dog", "--name", "Milo"); | ||||
|  | ||||
|                 configurator.AddCommand<HorseCommand>("horse") | ||||
|                     .WithExample("horse", "--name", "Brutus") | ||||
|                     .WithExample("horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                     .WithExample("horse", "--name", "Cash") | ||||
|                     .WithExample("horse", "--name", "Dakota") | ||||
|                     .WithExample("horse", "--name", "Cisco") | ||||
|                     .WithExample("horse", "--name", "Spirit"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("--help"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Children_None")] | ||||
|         public Task Should_Not_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples() | ||||
|         { | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // Do not show examples defined on the direct children | ||||
|                 configurator.Settings.MaximumIndirectExamples = 0; | ||||
|  | ||||
|                 configurator.AddCommand<DogCommand>("dog") | ||||
|                     .WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                     .WithExample("dog", "--name", "Luna") | ||||
|                     .WithExample("dog", "--name", "Charlie") | ||||
|                     .WithExample("dog", "--name", "Bella") | ||||
|                     .WithExample("dog", "--name", "Daisy") | ||||
|                     .WithExample("dog", "--name", "Milo"); | ||||
|  | ||||
|                 configurator.AddCommand<HorseCommand>("horse") | ||||
|                     .WithExample("horse", "--name", "Brutus") | ||||
|                     .WithExample("horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                     .WithExample("horse", "--name", "Cash") | ||||
|                     .WithExample("horse", "--name", "Dakota") | ||||
|                     .WithExample("horse", "--name", "Cisco") | ||||
|                     .WithExample("horse", "--name", "Brutus") | ||||
|                     .WithExample("horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                     .WithExample("horse", "--name", "Cash") | ||||
|                     .WithExample("horse", "--name", "Dakota") | ||||
|                     .WithExample("horse", "--name", "Cisco") | ||||
|                     .WithExample("horse", "--name", "Spirit"); | ||||
|             }); | ||||
|  | ||||
| @@ -566,7 +455,118 @@ public sealed partial class CommandAppTests | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Leafs")] | ||||
|         [Expectation("Root_Examples_Children_Eight")] | ||||
|         public Task Should_Output_Eight_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples() | ||||
|         { | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // Show the first 8 examples defined on the direct children | ||||
|                 configurator.Settings.MaximumIndirectExamples = 8; | ||||
|  | ||||
|                 configurator.AddCommand<DogCommand>("dog") | ||||
|                     .WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                     .WithExample("dog", "--name", "Luna") | ||||
|                     .WithExample("dog", "--name", "Charlie") | ||||
|                     .WithExample("dog", "--name", "Bella") | ||||
|                     .WithExample("dog", "--name", "Daisy") | ||||
|                     .WithExample("dog", "--name", "Milo"); | ||||
|  | ||||
|                 configurator.AddCommand<HorseCommand>("horse") | ||||
|                     .WithExample("horse", "--name", "Brutus") | ||||
|                     .WithExample("horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                     .WithExample("horse", "--name", "Cash") | ||||
|                     .WithExample("horse", "--name", "Dakota") | ||||
|                     .WithExample("horse", "--name", "Cisco") | ||||
|                     .WithExample("horse", "--name", "Spirit"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("--help"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Children_Twelve")] | ||||
|         public Task Should_Output_All_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples() | ||||
|         { | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // Show all examples defined on the direct children | ||||
|                 configurator.Settings.MaximumIndirectExamples = int.MaxValue; | ||||
|  | ||||
|                 configurator.AddCommand<DogCommand>("dog") | ||||
|                     .WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                     .WithExample("dog", "--name", "Luna") | ||||
|                     .WithExample("dog", "--name", "Charlie") | ||||
|                     .WithExample("dog", "--name", "Bella") | ||||
|                     .WithExample("dog", "--name", "Daisy") | ||||
|                     .WithExample("dog", "--name", "Milo"); | ||||
|  | ||||
|                 configurator.AddCommand<HorseCommand>("horse") | ||||
|                     .WithExample("horse", "--name", "Brutus") | ||||
|                     .WithExample("horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                     .WithExample("horse", "--name", "Cash") | ||||
|                     .WithExample("horse", "--name", "Dakota") | ||||
|                     .WithExample("horse", "--name", "Cisco") | ||||
|                     .WithExample("horse", "--name", "Spirit"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("--help"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Children_None")] | ||||
|         public Task Should_Not_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples() | ||||
|         { | ||||
|             // Given | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // Do not show examples defined on the direct children | ||||
|                 configurator.Settings.MaximumIndirectExamples = 0; | ||||
|  | ||||
|                 configurator.AddCommand<DogCommand>("dog") | ||||
|                     .WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                     .WithExample("dog", "--name", "Luna") | ||||
|                     .WithExample("dog", "--name", "Charlie") | ||||
|                     .WithExample("dog", "--name", "Bella") | ||||
|                     .WithExample("dog", "--name", "Daisy") | ||||
|                     .WithExample("dog", "--name", "Milo"); | ||||
|  | ||||
|                 configurator.AddCommand<HorseCommand>("horse") | ||||
|                     .WithExample("horse", "--name", "Brutus") | ||||
|                     .WithExample("horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                     .WithExample("horse", "--name", "Cash") | ||||
|                     .WithExample("horse", "--name", "Dakota") | ||||
|                     .WithExample("horse", "--name", "Cisco") | ||||
|                     .WithExample("horse", "--name", "Spirit"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|             var result = fixture.Run("--help"); | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Leafs")] | ||||
|         [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:SingleLineCommentsMustNotBeFollowedByBlankLine", Justification = "Single line comment is relevant to several code blocks that follow.")] | ||||
|         public Task Should_Output_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found() | ||||
|         { | ||||
| @@ -577,24 +577,24 @@ public sealed partial class CommandAppTests | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // It should be capped to the first 5 examples by default | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // It should be capped to the first 5 examples by default | ||||
|  | ||||
|                     animal.AddCommand<DogCommand>("dog") | ||||
|                         .WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                         .WithExample("animal", "dog", "--name", "Luna") | ||||
|                         .WithExample("animal", "dog", "--name", "Charlie") | ||||
|                         .WithExample("animal", "dog", "--name", "Bella") | ||||
|                         .WithExample("animal", "dog", "--name", "Daisy") | ||||
|                         .WithExample("animal", "dog", "--name", "Milo"); | ||||
|                         .WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                         .WithExample("animal", "dog", "--name", "Luna") | ||||
|                         .WithExample("animal", "dog", "--name", "Charlie") | ||||
|                         .WithExample("animal", "dog", "--name", "Bella") | ||||
|                         .WithExample("animal", "dog", "--name", "Daisy") | ||||
|                         .WithExample("animal", "dog", "--name", "Milo"); | ||||
|  | ||||
|                     animal.AddCommand<HorseCommand>("horse") | ||||
|                         .WithExample("animal", "horse", "--name", "Brutus") | ||||
|                         .WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                         .WithExample("animal", "horse", "--name", "Cash") | ||||
|                         .WithExample("animal", "horse", "--name", "Dakota") | ||||
|                         .WithExample("animal", "horse", "--name", "Cisco") | ||||
|                         .WithExample("animal", "horse", "--name", "Brutus") | ||||
|                         .WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                         .WithExample("animal", "horse", "--name", "Cash") | ||||
|                         .WithExample("animal", "horse", "--name", "Dakota") | ||||
|                         .WithExample("animal", "horse", "--name", "Cisco") | ||||
|                         .WithExample("animal", "horse", "--name", "Spirit"); | ||||
|                 }); | ||||
|             }); | ||||
| @@ -604,10 +604,10 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Leafs_Eight")] | ||||
|         [Expectation("Root_Examples_Leafs_Eight")] | ||||
|         public Task Should_Output_Eight_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found() | ||||
|         { | ||||
|             // Given | ||||
| @@ -617,25 +617,25 @@ public sealed partial class CommandAppTests | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // Show the first 8 examples defined on the direct children | ||||
|                     configurator.Settings.MaximumIndirectExamples = 8; | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // Show the first 8 examples defined on the direct children | ||||
|                     configurator.Settings.MaximumIndirectExamples = 8; | ||||
|  | ||||
|                     animal.AddCommand<DogCommand>("dog") | ||||
|                         .WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                         .WithExample("animal", "dog", "--name", "Luna") | ||||
|                         .WithExample("animal", "dog", "--name", "Charlie") | ||||
|                         .WithExample("animal", "dog", "--name", "Bella") | ||||
|                         .WithExample("animal", "dog", "--name", "Daisy") | ||||
|                         .WithExample("animal", "dog", "--name", "Milo"); | ||||
|                         .WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                         .WithExample("animal", "dog", "--name", "Luna") | ||||
|                         .WithExample("animal", "dog", "--name", "Charlie") | ||||
|                         .WithExample("animal", "dog", "--name", "Bella") | ||||
|                         .WithExample("animal", "dog", "--name", "Daisy") | ||||
|                         .WithExample("animal", "dog", "--name", "Milo"); | ||||
|  | ||||
|                     animal.AddCommand<HorseCommand>("horse") | ||||
|                         .WithExample("animal", "horse", "--name", "Brutus") | ||||
|                         .WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                         .WithExample("animal", "horse", "--name", "Cash") | ||||
|                         .WithExample("animal", "horse", "--name", "Dakota") | ||||
|                         .WithExample("animal", "horse", "--name", "Cisco") | ||||
|                         .WithExample("animal", "horse", "--name", "Brutus") | ||||
|                         .WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                         .WithExample("animal", "horse", "--name", "Cash") | ||||
|                         .WithExample("animal", "horse", "--name", "Dakota") | ||||
|                         .WithExample("animal", "horse", "--name", "Cisco") | ||||
|                         .WithExample("animal", "horse", "--name", "Spirit"); | ||||
|                 }); | ||||
|             }); | ||||
| @@ -645,10 +645,10 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Leafs_Twelve")] | ||||
|         [Expectation("Root_Examples_Leafs_Twelve")] | ||||
|         public Task Should_Output_All_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found() | ||||
|         { | ||||
|             // Given | ||||
| @@ -658,25 +658,25 @@ public sealed partial class CommandAppTests | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // Show all examples defined on the direct children | ||||
|                     configurator.Settings.MaximumIndirectExamples = int.MaxValue; | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // Show all examples defined on the direct children | ||||
|                     configurator.Settings.MaximumIndirectExamples = int.MaxValue; | ||||
|  | ||||
|                     animal.AddCommand<DogCommand>("dog") | ||||
|                         .WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                         .WithExample("animal", "dog", "--name", "Luna") | ||||
|                         .WithExample("animal", "dog", "--name", "Charlie") | ||||
|                         .WithExample("animal", "dog", "--name", "Bella") | ||||
|                         .WithExample("animal", "dog", "--name", "Daisy") | ||||
|                         .WithExample("animal", "dog", "--name", "Milo"); | ||||
|                         .WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                         .WithExample("animal", "dog", "--name", "Luna") | ||||
|                         .WithExample("animal", "dog", "--name", "Charlie") | ||||
|                         .WithExample("animal", "dog", "--name", "Bella") | ||||
|                         .WithExample("animal", "dog", "--name", "Daisy") | ||||
|                         .WithExample("animal", "dog", "--name", "Milo"); | ||||
|  | ||||
|                     animal.AddCommand<HorseCommand>("horse") | ||||
|                         .WithExample("animal", "horse", "--name", "Brutus") | ||||
|                         .WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                         .WithExample("animal", "horse", "--name", "Cash") | ||||
|                         .WithExample("animal", "horse", "--name", "Dakota") | ||||
|                         .WithExample("animal", "horse", "--name", "Cisco") | ||||
|                         .WithExample("animal", "horse", "--name", "Brutus") | ||||
|                         .WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                         .WithExample("animal", "horse", "--name", "Cash") | ||||
|                         .WithExample("animal", "horse", "--name", "Dakota") | ||||
|                         .WithExample("animal", "horse", "--name", "Cisco") | ||||
|                         .WithExample("animal", "horse", "--name", "Spirit"); | ||||
|                 }); | ||||
|             }); | ||||
| @@ -686,10 +686,10 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Root_Examples_Leafs_None")] | ||||
|         [Expectation("Root_Examples_Leafs_None")] | ||||
|         public Task Should_Not_Output_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found() | ||||
|         { | ||||
|             // Given | ||||
| @@ -699,25 +699,25 @@ public sealed partial class CommandAppTests | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // Do not show examples defined on the direct children | ||||
|                     configurator.Settings.MaximumIndirectExamples = 0; | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // Do not show examples defined on the direct children | ||||
|                     configurator.Settings.MaximumIndirectExamples = 0; | ||||
|  | ||||
|                     animal.AddCommand<DogCommand>("dog") | ||||
|                         .WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                         .WithExample("animal", "dog", "--name", "Luna") | ||||
|                         .WithExample("animal", "dog", "--name", "Charlie") | ||||
|                         .WithExample("animal", "dog", "--name", "Bella") | ||||
|                         .WithExample("animal", "dog", "--name", "Daisy") | ||||
|                         .WithExample("animal", "dog", "--name", "Milo"); | ||||
|                         .WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy") | ||||
|                         .WithExample("animal", "dog", "--name", "Luna") | ||||
|                         .WithExample("animal", "dog", "--name", "Charlie") | ||||
|                         .WithExample("animal", "dog", "--name", "Bella") | ||||
|                         .WithExample("animal", "dog", "--name", "Daisy") | ||||
|                         .WithExample("animal", "dog", "--name", "Milo"); | ||||
|  | ||||
|                     animal.AddCommand<HorseCommand>("horse") | ||||
|                         .WithExample("animal", "horse", "--name", "Brutus") | ||||
|                         .WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                         .WithExample("animal", "horse", "--name", "Cash") | ||||
|                         .WithExample("animal", "horse", "--name", "Dakota") | ||||
|                         .WithExample("animal", "horse", "--name", "Cisco") | ||||
|                         .WithExample("animal", "horse", "--name", "Brutus") | ||||
|                         .WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false") | ||||
|                         .WithExample("animal", "horse", "--name", "Cash") | ||||
|                         .WithExample("animal", "horse", "--name", "Dakota") | ||||
|                         .WithExample("animal", "horse", "--name", "Cisco") | ||||
|                         .WithExample("animal", "horse", "--name", "Spirit"); | ||||
|                 }); | ||||
|             }); | ||||
| @@ -737,23 +737,23 @@ public sealed partial class CommandAppTests | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|                 configurator.AddBranch<AnimalSettings>("animal", animal => | ||||
|                 { | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // All branch examples should be shown | ||||
|                     animal.AddExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Luna"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Charlie"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Bella"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Daisy"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Milo"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Brutus"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Cash"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Dakota"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Cisco"); | ||||
|                     animal.SetDescription("The animal command."); | ||||
|  | ||||
|                     // All branch examples should be shown | ||||
|                     animal.AddExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Luna"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Charlie"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Bella"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Daisy"); | ||||
|                     animal.AddExample("animal", "dog", "--name", "Milo"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Brutus"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Cash"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Dakota"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Cisco"); | ||||
|                     animal.AddExample("animal", "horse", "--name", "Spirit"); | ||||
|  | ||||
|                     animal.AddCommand<DogCommand>("dog") | ||||
| @@ -768,7 +768,7 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             return Verifier.Verify(result.Output); | ||||
|         } | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         [Expectation("Default_Examples")] | ||||
| @@ -780,13 +780,13 @@ public sealed partial class CommandAppTests | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationName("myapp"); | ||||
|  | ||||
|                 // All root examples should be shown | ||||
|                 configurator.AddExample("--name", "Rufus", "--age", "12", "--good-boy"); | ||||
|                 configurator.AddExample("--name", "Luna"); | ||||
|                 configurator.AddExample("--name", "Charlie"); | ||||
|                 configurator.AddExample("--name", "Bella"); | ||||
|                 configurator.AddExample("--name", "Daisy"); | ||||
|  | ||||
|                 // All root examples should be shown | ||||
|                 configurator.AddExample("--name", "Rufus", "--age", "12", "--good-boy"); | ||||
|                 configurator.AddExample("--name", "Luna"); | ||||
|                 configurator.AddExample("--name", "Charlie"); | ||||
|                 configurator.AddExample("--name", "Bella"); | ||||
|                 configurator.AddExample("--name", "Daisy"); | ||||
|                 configurator.AddExample("--name", "Milo"); | ||||
|             }); | ||||
|  | ||||
|   | ||||
| @@ -352,8 +352,8 @@ public sealed partial class CommandAppTests | ||||
|                 var result = app.Run("dog", "-u"); | ||||
|  | ||||
|                 // Then | ||||
|                 return Verifier.Verify(result.Output); | ||||
|             } | ||||
|                 return Verifier.Verify(result.Output); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [UsesVerify] | ||||
| @@ -584,7 +584,7 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|                 // Then | ||||
|                 result.Output.ShouldBe("Error: Command 'dog' is missing required argument 'AGE'."); | ||||
|             } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,16 +3,16 @@ namespace Spectre.Console.Tests.Unit.Cli; | ||||
| public sealed partial class CommandAppTests | ||||
| { | ||||
|     public sealed class Remaining | ||||
|     { | ||||
|         [Theory] | ||||
|         [InlineData("-a")] | ||||
|     { | ||||
|         [Theory] | ||||
|         [InlineData("-a")] | ||||
|         [InlineData("--alive")] | ||||
|         public void Should_Not_Add_Known_Flags_To_Remaining_Arguments_RelaxedParsing(string knownFlag) | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
| @@ -20,29 +20,29 @@ public sealed partial class CommandAppTests | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "dog", "12", "4", | ||||
|                 knownFlag, | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.Settings.ShouldBeOfType<DogSettings>().And(dog => | ||||
|             { | ||||
|                 dog.IsAlive.ShouldBe(true); | ||||
|             }); | ||||
|                 "dog", "12", "4", | ||||
|                 knownFlag, | ||||
|             }); | ||||
|  | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(0); | ||||
|             // Then | ||||
|             result.Settings.ShouldBeOfType<DogSettings>().And(dog => | ||||
|             { | ||||
|                 dog.IsAlive.ShouldBe(true); | ||||
|             }); | ||||
|  | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(0); | ||||
|             result.Context.Remaining.Raw.Count.ShouldBe(0); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-r")] | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-r")] | ||||
|         [InlineData("--romeo")] | ||||
|         public void Should_Add_Unknown_Flags_To_Remaining_Arguments_RelaxedParsing(string unknownFlag) | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
| @@ -50,23 +50,23 @@ public sealed partial class CommandAppTests | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "dog", "12", "4", | ||||
|                 unknownFlag, | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(1); | ||||
|             result.Context.ShouldHaveRemainingArgument(unknownFlag.TrimStart('-'), values: new[] { (string)null }); | ||||
|                 "dog", "12", "4", | ||||
|                 unknownFlag, | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(1); | ||||
|             result.Context.ShouldHaveRemainingArgument(unknownFlag.TrimStart('-'), values: new[] { (string)null }); | ||||
|             result.Context.Remaining.Raw.Count.ShouldBe(0); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Add_Unknown_Flags_When_Grouped_To_Remaining_Arguments_RelaxedParsing() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
| @@ -74,25 +74,25 @@ public sealed partial class CommandAppTests | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "dog", "12", "4", | ||||
|                 "-agr", | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(1); | ||||
|             result.Context.ShouldHaveRemainingArgument("r", values: new[] { (string)null }); | ||||
|                 "dog", "12", "4", | ||||
|                 "-agr", | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(1); | ||||
|             result.Context.ShouldHaveRemainingArgument("r", values: new[] { (string)null }); | ||||
|             result.Context.Remaining.Raw.Count.ShouldBe(0); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-a")] | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-a")] | ||||
|         [InlineData("--alive")] | ||||
|         public void Should_Not_Add_Known_Flags_To_Remaining_Arguments_StrictParsing(string knownFlag) | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.UseStrictParsing(); | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddCommand<DogCommand>("dog"); | ||||
| @@ -101,68 +101,68 @@ public sealed partial class CommandAppTests | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "dog", "12", "4", | ||||
|                 knownFlag, | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(0); | ||||
|                 "dog", "12", "4", | ||||
|                 knownFlag, | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(0); | ||||
|             result.Context.Remaining.Raw.Count.ShouldBe(0); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-r")] | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-r")] | ||||
|         [InlineData("--romeo")] | ||||
|         public void Should_Not_Add_Unknown_Flags_To_Remaining_Arguments_StrictParsing(string unknownFlag) | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.UseStrictParsing(); | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => app.Run(new[] | ||||
|             { | ||||
|                 "dog", "12", "4", | ||||
|                 unknownFlag, | ||||
|             })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe($"Unknown option '{unknownFlag.TrimStart('-')}'."); | ||||
|                 "dog", "12", "4", | ||||
|                 unknownFlag, | ||||
|             })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe($"Unknown option '{unknownFlag.TrimStart('-')}'."); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Not_Add_Unknown_Flags_When_Grouped_To_Remaining_Arguments_StrictParsing() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.UseStrictParsing(); | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => app.Run(new[] | ||||
|             { | ||||
|                 "dog", "12", "4", | ||||
|                 "-agr", | ||||
|             })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe($"Unknown option 'r'."); | ||||
|                 "dog", "12", "4", | ||||
|                 "-agr", | ||||
|             })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe($"Unknown option 'r'."); | ||||
|             }); | ||||
|         } | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Register_Remaining_Parsed_Arguments_With_Context() | ||||
| @@ -254,19 +254,19 @@ public sealed partial class CommandAppTests | ||||
|             result.Context.Remaining.Raw[0].ShouldBe("/c"); | ||||
|             result.Context.Remaining.Raw[1].ShouldBe("\"set && pause\""); | ||||
|             result.Context.Remaining.Raw[2].ShouldBe("Name=\" -Rufus --' "); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData(true)] | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData(true)] | ||||
|         [InlineData(false)] | ||||
|         public void Should_Convert_Flags_To_Remaining_Arguments_If_Cannot_Be_Assigned(bool useStrictParsing) | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|                 config.Settings.ConvertFlagsToRemainingArguments = true; | ||||
|                 config.Settings.StrictParsing = useStrictParsing; | ||||
|             { | ||||
|                 config.Settings.ConvertFlagsToRemainingArguments = true; | ||||
|                 config.Settings.StrictParsing = useStrictParsing; | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddCommand<DogCommand>("dog"); | ||||
|             }); | ||||
| @@ -280,8 +280,8 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             result.Context.Remaining.Parsed.Count.ShouldBe(1); | ||||
|             result.Context.ShouldHaveRemainingArgument("good-boy", values: new[] { "Please be good Rufus!" }); | ||||
|  | ||||
|             result.Context.ShouldHaveRemainingArgument("good-boy", values: new[] { "Please be good Rufus!" }); | ||||
|  | ||||
|             result.Context.Remaining.Raw.Count.ShouldBe(0); // nb. there are no "raw" remaining arguments on the command line | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -15,8 +15,8 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             result.Output.ShouldStartWith("Spectre.Cli version "); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Output_Application_Version_To_The_Console_With_No_Command() | ||||
|         { | ||||
| @@ -24,7 +24,7 @@ public sealed partial class CommandAppTests | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationVersion("1.0"); | ||||
|                 configurator.SetApplicationVersion("1.0"); | ||||
|             }); | ||||
|  | ||||
|             // When | ||||
| @@ -32,8 +32,8 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             result.Output.ShouldBe("1.0"); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Output_Application_Version_To_The_Console_With_Command() | ||||
|         { | ||||
| @@ -41,8 +41,8 @@ public sealed partial class CommandAppTests | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationVersion("1.0"); | ||||
|  | ||||
|                 configurator.SetApplicationVersion("1.0"); | ||||
|  | ||||
|                 configurator.AddCommand<EmptyCommand>("empty"); | ||||
|             }); | ||||
|  | ||||
| @@ -51,8 +51,8 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             result.Output.ShouldBe("1.0"); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Output_Application_Version_To_The_Console_With_Default_Command() | ||||
|         { | ||||
| @@ -69,8 +69,8 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             // Then | ||||
|             result.Output.ShouldBe("1.0"); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Output_Application_Version_To_The_Console_With_Branch_Default_Command() | ||||
|         { | ||||
| @@ -78,11 +78,11 @@ public sealed partial class CommandAppTests | ||||
|             var fixture = new CommandAppTester(); | ||||
|             fixture.Configure(configurator => | ||||
|             { | ||||
|                 configurator.SetApplicationVersion("1.0"); | ||||
|  | ||||
|                 configurator.AddBranch<EmptyCommandSettings>("branch", branch => | ||||
|                 { | ||||
|                     branch.SetDefaultCommand<EmptyCommand>(); | ||||
|                 configurator.SetApplicationVersion("1.0"); | ||||
|  | ||||
|                 configurator.AddBranch<EmptyCommandSettings>("branch", branch => | ||||
|                 { | ||||
|                     branch.SetDefaultCommand<EmptyCommand>(); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|   | ||||
| @@ -1041,66 +1041,66 @@ public sealed partial class CommandAppTests | ||||
|         // Then | ||||
|         result.Context.ShouldNotBeNull(); | ||||
|         result.Context.Data.ShouldBe(123); | ||||
|     } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     public sealed class Default_Command | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Be_Able_To_Set_The_Default_Command() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.SetDefaultCommand<DogCommand>(); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Be_Able_To_Set_The_Default_Command() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandAppTester(); | ||||
|             app.SetDefaultCommand<DogCommand>(); | ||||
|  | ||||
|             // When | ||||
|             var result = app.Run(new[] | ||||
|             { | ||||
|                 "4", "12", "--good-boy", "--name", "Rufus", | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<DogSettings>().And(dog => | ||||
|             { | ||||
|                 dog.Legs.ShouldBe(4); | ||||
|                 dog.Age.ShouldBe(12); | ||||
|                 dog.GoodBoy.ShouldBe(true); | ||||
|                 dog.Name.ShouldBe("Rufus"); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Set_The_Default_Command_Description_Data_CommandApp() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandApp(); | ||||
|             app.SetDefaultCommand<DogCommand>() | ||||
|                 .WithDescription("The default command") | ||||
|                 .WithData(new string[] { "foo", "bar" }); | ||||
|  | ||||
|             // When | ||||
|  | ||||
|             // Then | ||||
|             app.GetConfigurator().DefaultCommand.ShouldNotBeNull(); | ||||
|             app.GetConfigurator().DefaultCommand.Description.ShouldBe("The default command"); | ||||
|             app.GetConfigurator().DefaultCommand.Data.ShouldBe(new string[] { "foo", "bar" }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Set_The_Default_Command_Description_Data_CommandAppOfT() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandApp<DogCommand>() | ||||
|                 .WithDescription("The default command") | ||||
|                 .WithData(new string[] { "foo", "bar" }); | ||||
|  | ||||
|             // When | ||||
|  | ||||
|             // Then | ||||
|             app.GetConfigurator().DefaultCommand.ShouldNotBeNull(); | ||||
|             app.GetConfigurator().DefaultCommand.Description.ShouldBe("The default command"); | ||||
|             app.GetConfigurator().DefaultCommand.Data.ShouldBe(new string[] { "foo", "bar" }); | ||||
|         } | ||||
|             }); | ||||
|  | ||||
|             // Then | ||||
|             result.ExitCode.ShouldBe(0); | ||||
|             result.Settings.ShouldBeOfType<DogSettings>().And(dog => | ||||
|             { | ||||
|                 dog.Legs.ShouldBe(4); | ||||
|                 dog.Age.ShouldBe(12); | ||||
|                 dog.GoodBoy.ShouldBe(true); | ||||
|                 dog.Name.ShouldBe("Rufus"); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Set_The_Default_Command_Description_Data_CommandApp() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandApp(); | ||||
|             app.SetDefaultCommand<DogCommand>() | ||||
|                 .WithDescription("The default command") | ||||
|                 .WithData(new string[] { "foo", "bar" }); | ||||
|  | ||||
|             // When | ||||
|  | ||||
|             // Then | ||||
|             app.GetConfigurator().DefaultCommand.ShouldNotBeNull(); | ||||
|             app.GetConfigurator().DefaultCommand.Description.ShouldBe("The default command"); | ||||
|             app.GetConfigurator().DefaultCommand.Data.ShouldBe(new string[] { "foo", "bar" }); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Set_The_Default_Command_Description_Data_CommandAppOfT() | ||||
|         { | ||||
|             // Given | ||||
|             var app = new CommandApp<DogCommand>() | ||||
|                 .WithDescription("The default command") | ||||
|                 .WithData(new string[] { "foo", "bar" }); | ||||
|  | ||||
|             // When | ||||
|  | ||||
|             // Then | ||||
|             app.GetConfigurator().DefaultCommand.ShouldNotBeNull(); | ||||
|             app.GetConfigurator().DefaultCommand.Description.ShouldBe("The default command"); | ||||
|             app.GetConfigurator().DefaultCommand.Data.ShouldBe(new string[] { "foo", "bar" }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public sealed class Delegate_Commands | ||||
| @@ -1114,7 +1114,7 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             var app = new CommandApp(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddDelegate<DogSettings>( | ||||
|                     "foo", (context, settings) => | ||||
| @@ -1134,8 +1134,8 @@ public sealed partial class CommandAppTests | ||||
|             dog.Age.ShouldBe(12); | ||||
|             dog.Legs.ShouldBe(4); | ||||
|             data.ShouldBe(2); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public async void Should_Execute_Async_Delegate_Command_At_Root_Level() | ||||
|         { | ||||
| @@ -1145,7 +1145,7 @@ public sealed partial class CommandAppTests | ||||
|  | ||||
|             var app = new CommandApp(); | ||||
|             app.Configure(config => | ||||
|             { | ||||
|             { | ||||
|                 config.PropagateExceptions(); | ||||
|                 config.AddAsyncDelegate<DogSettings>( | ||||
|                     "foo", (context, settings) => | ||||
| @@ -1199,8 +1199,8 @@ public sealed partial class CommandAppTests | ||||
|             dog.Age.ShouldBe(12); | ||||
|             dog.Legs.ShouldBe(4); | ||||
|             data.ShouldBe(2); | ||||
|         } | ||||
|  | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public async void Should_Execute_Nested_Async_Delegate_Command() | ||||
|         { | ||||
|   | ||||
| @@ -1,314 +1,314 @@ | ||||
| namespace Spectre.Console.Tests.Unit.Cli.Parsing; | ||||
|  | ||||
| public class CommandTreeTokenizerTests | ||||
| { | ||||
|     public sealed class ScanString | ||||
|     { | ||||
|         [Theory] | ||||
|         [InlineData("")] | ||||
|         [InlineData(" ")] | ||||
|         [InlineData("  ")] | ||||
|         [InlineData("\t")] | ||||
|         [InlineData("\r\n\t")] | ||||
|         [InlineData("👋🏻")] | ||||
|         [InlineData("🐎👋🏻🔥❤️")] | ||||
|         [InlineData("\"🐎👋🏻🔥❤️\" is an emoji sequence")] | ||||
|         public void Should_Preserve_Edgecase_Inputs(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|  | ||||
|         // Double-quote handling | ||||
|         [InlineData("\"")] | ||||
|         [InlineData("\"\"")] | ||||
|         [InlineData("\"Rufus\"")] | ||||
|         [InlineData("\" Rufus\"")] | ||||
|         [InlineData("\"-R\"")] | ||||
|         [InlineData("\"-Rufus\"")] | ||||
|         [InlineData("\" -Rufus\"")] | ||||
|  | ||||
|         // Single-quote handling | ||||
|         [InlineData("'")] | ||||
|         [InlineData("''")] | ||||
|         [InlineData("'Rufus'")] | ||||
|         [InlineData("' Rufus'")] | ||||
|         [InlineData("'-R'")] | ||||
|         [InlineData("'-Rufus'")] | ||||
|         [InlineData("' -Rufus'")] | ||||
|         public void Should_Preserve_Quotes(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("Rufus-")] | ||||
|         [InlineData("Rufus--")] | ||||
|         [InlineData("R-u-f-u-s")] | ||||
|         public void Should_Preserve_Hyphen_Delimiters(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData(" Rufus")] | ||||
|         [InlineData("Rufus ")] | ||||
|         [InlineData(" Rufus ")] | ||||
|         public void Should_Preserve_Spaces(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData(" \" -Rufus -- ")] | ||||
|         [InlineData("Name=\" -Rufus --' ")] | ||||
|         public void Should_Preserve_Quotes_Hyphen_Delimiters_Spaces(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public sealed class ScanLongOption | ||||
|     { | ||||
|         [Theory] | ||||
|         [InlineData("--Name-", "Name-")] | ||||
|         [InlineData("--Name_", "Name_")] | ||||
|         public void Should_Allow_Hyphens_And_Underscores_In_Option_Name(string actual, string expected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actual }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(expected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-- ")] | ||||
|         [InlineData("--Name ")] | ||||
|         [InlineData("--Name\"")] | ||||
|         [InlineData("--Nam\"e")] | ||||
|         public void Should_Throw_On_Invalid_Option_Name(string actual) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("Invalid long option name."); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public sealed class ScanShortOptions | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Accept_Option_Without_Value() | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new[] { "-a" }); | ||||
|  | ||||
|             // Then | ||||
|             result.Remaining.ShouldBeEmpty(); | ||||
|             result.Tokens.ShouldHaveSingleItem(); | ||||
|  | ||||
|             var t = result.Tokens[0]; | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(0); | ||||
|             t.Value.ShouldBe("a"); | ||||
|             t.Representation.ShouldBe("-a"); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-a:foo")] | ||||
|         [InlineData("-a=foo")] | ||||
|         public void Should_Accept_Option_With_Value(string param) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new[] { param }); | ||||
|  | ||||
|             // Then | ||||
|             result.Remaining.ShouldBeEmpty(); | ||||
|             result.Tokens.Count.ShouldBe(2); | ||||
|  | ||||
|             var t = result.Tokens.Consume(); | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(0); | ||||
|             t.Value.ShouldBe("a"); | ||||
|             t.Representation.ShouldBe("-a"); | ||||
|  | ||||
|             t = result.Tokens.Consume(); | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(3); | ||||
|             t.Value.ShouldBe("foo"); | ||||
|             t.Representation.ShouldBe("foo"); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|  | ||||
|         // Positive values | ||||
|         [InlineData("-a:1.5", null, "1.5")] | ||||
|         [InlineData("-a=1.5", null, "1.5")] | ||||
|         [InlineData("-a", "1.5", "1.5")] | ||||
|  | ||||
|         // Negative values | ||||
|         [InlineData("-a:-1.5", null, "-1.5")] | ||||
|         [InlineData("-a=-1.5", null, "-1.5")] | ||||
|         [InlineData("-a", "-1.5", "-1.5")] | ||||
|         public void Should_Accept_Option_With_Numeric_Value(string firstArg, string secondArg, string expectedValue) | ||||
|         { | ||||
|             // Given | ||||
|             List<string> args = new List<string>(); | ||||
|             args.Add(firstArg); | ||||
|             if (secondArg != null) | ||||
|             { | ||||
|                 args.Add(secondArg); | ||||
|             } | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(args); | ||||
|  | ||||
|             // Then | ||||
|             result.Remaining.ShouldBeEmpty(); | ||||
|             result.Tokens.Count.ShouldBe(2); | ||||
|  | ||||
|             var t = result.Tokens.Consume(); | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(0); | ||||
|             t.Value.ShouldBe("a"); | ||||
|             t.Representation.ShouldBe("-a"); | ||||
|  | ||||
|             t = result.Tokens.Consume(); | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(3); | ||||
|             t.Value.ShouldBe(expectedValue); | ||||
|             t.Representation.ShouldBe(expectedValue); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Accept_Option_With_Negative_Numeric_Prefixed_String_Value() | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new[] { "-6..2 " }); | ||||
|  | ||||
|             // Then | ||||
|             result.Remaining.ShouldBeEmpty(); | ||||
|             result.Tokens.ShouldHaveSingleItem(); | ||||
|  | ||||
|             var t = result.Tokens[0]; | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(0); | ||||
|             t.Value.ShouldBe("-6..2"); | ||||
|             t.Representation.ShouldBe("-6..2"); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-N ", "N")] | ||||
|         public void Should_Remove_Trailing_Spaces_In_Option_Name(string actual, string expected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actual }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(expected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-N-")] | ||||
|         [InlineData("-N\"")] | ||||
|         [InlineData("-a1")] | ||||
|         public void Should_Throw_On_Invalid_Option_Name(string actual) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("Short option does not have a valid name."); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Theory] | ||||
|     [InlineData("-")] | ||||
|     [InlineData("- ")] | ||||
|     public void Should_Throw_On_Missing_Option_Name(string actual) | ||||
|     { | ||||
|         // Given | ||||
|  | ||||
|         // When | ||||
|         var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual })); | ||||
|  | ||||
|         // Then | ||||
|         result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|         { | ||||
|             ex.Message.ShouldBe("Option does not have a name."); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| namespace Spectre.Console.Tests.Unit.Cli.Parsing; | ||||
|  | ||||
| public class CommandTreeTokenizerTests | ||||
| { | ||||
|     public sealed class ScanString | ||||
|     { | ||||
|         [Theory] | ||||
|         [InlineData("")] | ||||
|         [InlineData(" ")] | ||||
|         [InlineData("  ")] | ||||
|         [InlineData("\t")] | ||||
|         [InlineData("\r\n\t")] | ||||
|         [InlineData("👋🏻")] | ||||
|         [InlineData("🐎👋🏻🔥❤️")] | ||||
|         [InlineData("\"🐎👋🏻🔥❤️\" is an emoji sequence")] | ||||
|         public void Should_Preserve_Edgecase_Inputs(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|  | ||||
|         // Double-quote handling | ||||
|         [InlineData("\"")] | ||||
|         [InlineData("\"\"")] | ||||
|         [InlineData("\"Rufus\"")] | ||||
|         [InlineData("\" Rufus\"")] | ||||
|         [InlineData("\"-R\"")] | ||||
|         [InlineData("\"-Rufus\"")] | ||||
|         [InlineData("\" -Rufus\"")] | ||||
|  | ||||
|         // Single-quote handling | ||||
|         [InlineData("'")] | ||||
|         [InlineData("''")] | ||||
|         [InlineData("'Rufus'")] | ||||
|         [InlineData("' Rufus'")] | ||||
|         [InlineData("'-R'")] | ||||
|         [InlineData("'-Rufus'")] | ||||
|         [InlineData("' -Rufus'")] | ||||
|         public void Should_Preserve_Quotes(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("Rufus-")] | ||||
|         [InlineData("Rufus--")] | ||||
|         [InlineData("R-u-f-u-s")] | ||||
|         public void Should_Preserve_Hyphen_Delimiters(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData(" Rufus")] | ||||
|         [InlineData("Rufus ")] | ||||
|         [InlineData(" Rufus ")] | ||||
|         public void Should_Preserve_Spaces(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData(" \" -Rufus -- ")] | ||||
|         [InlineData("Name=\" -Rufus --' ")] | ||||
|         public void Should_Preserve_Quotes_Hyphen_Delimiters_Spaces(string actualAndExpected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(actualAndExpected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public sealed class ScanLongOption | ||||
|     { | ||||
|         [Theory] | ||||
|         [InlineData("--Name-", "Name-")] | ||||
|         [InlineData("--Name_", "Name_")] | ||||
|         public void Should_Allow_Hyphens_And_Underscores_In_Option_Name(string actual, string expected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actual }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(expected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-- ")] | ||||
|         [InlineData("--Name ")] | ||||
|         [InlineData("--Name\"")] | ||||
|         [InlineData("--Nam\"e")] | ||||
|         public void Should_Throw_On_Invalid_Option_Name(string actual) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("Invalid long option name."); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public sealed class ScanShortOptions | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Accept_Option_Without_Value() | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new[] { "-a" }); | ||||
|  | ||||
|             // Then | ||||
|             result.Remaining.ShouldBeEmpty(); | ||||
|             result.Tokens.ShouldHaveSingleItem(); | ||||
|  | ||||
|             var t = result.Tokens[0]; | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(0); | ||||
|             t.Value.ShouldBe("a"); | ||||
|             t.Representation.ShouldBe("-a"); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-a:foo")] | ||||
|         [InlineData("-a=foo")] | ||||
|         public void Should_Accept_Option_With_Value(string param) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new[] { param }); | ||||
|  | ||||
|             // Then | ||||
|             result.Remaining.ShouldBeEmpty(); | ||||
|             result.Tokens.Count.ShouldBe(2); | ||||
|  | ||||
|             var t = result.Tokens.Consume(); | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(0); | ||||
|             t.Value.ShouldBe("a"); | ||||
|             t.Representation.ShouldBe("-a"); | ||||
|  | ||||
|             t = result.Tokens.Consume(); | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(3); | ||||
|             t.Value.ShouldBe("foo"); | ||||
|             t.Representation.ShouldBe("foo"); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|  | ||||
|         // Positive values | ||||
|         [InlineData("-a:1.5", null, "1.5")] | ||||
|         [InlineData("-a=1.5", null, "1.5")] | ||||
|         [InlineData("-a", "1.5", "1.5")] | ||||
|  | ||||
|         // Negative values | ||||
|         [InlineData("-a:-1.5", null, "-1.5")] | ||||
|         [InlineData("-a=-1.5", null, "-1.5")] | ||||
|         [InlineData("-a", "-1.5", "-1.5")] | ||||
|         public void Should_Accept_Option_With_Numeric_Value(string firstArg, string secondArg, string expectedValue) | ||||
|         { | ||||
|             // Given | ||||
|             List<string> args = new List<string>(); | ||||
|             args.Add(firstArg); | ||||
|             if (secondArg != null) | ||||
|             { | ||||
|                 args.Add(secondArg); | ||||
|             } | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(args); | ||||
|  | ||||
|             // Then | ||||
|             result.Remaining.ShouldBeEmpty(); | ||||
|             result.Tokens.Count.ShouldBe(2); | ||||
|  | ||||
|             var t = result.Tokens.Consume(); | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(0); | ||||
|             t.Value.ShouldBe("a"); | ||||
|             t.Representation.ShouldBe("-a"); | ||||
|  | ||||
|             t = result.Tokens.Consume(); | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(3); | ||||
|             t.Value.ShouldBe(expectedValue); | ||||
|             t.Representation.ShouldBe(expectedValue); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Accept_Option_With_Negative_Numeric_Prefixed_String_Value() | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new[] { "-6..2 " }); | ||||
|  | ||||
|             // Then | ||||
|             result.Remaining.ShouldBeEmpty(); | ||||
|             result.Tokens.ShouldHaveSingleItem(); | ||||
|  | ||||
|             var t = result.Tokens[0]; | ||||
|             t.TokenKind.ShouldBe(CommandTreeToken.Kind.String); | ||||
|             t.IsGrouped.ShouldBe(false); | ||||
|             t.Position.ShouldBe(0); | ||||
|             t.Value.ShouldBe("-6..2"); | ||||
|             t.Representation.ShouldBe("-6..2"); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-N ", "N")] | ||||
|         public void Should_Remove_Trailing_Spaces_In_Option_Name(string actual, string expected) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = CommandTreeTokenizer.Tokenize(new string[] { actual }); | ||||
|  | ||||
|             // Then | ||||
|             result.Tokens.Count.ShouldBe(1); | ||||
|             result.Tokens[0].Value.ShouldBe(expected); | ||||
|             result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption); | ||||
|         } | ||||
|  | ||||
|         [Theory] | ||||
|         [InlineData("-N-")] | ||||
|         [InlineData("-N\"")] | ||||
|         [InlineData("-a1")] | ||||
|         public void Should_Throw_On_Invalid_Option_Name(string actual) | ||||
|         { | ||||
|             // Given | ||||
|  | ||||
|             // When | ||||
|             var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual })); | ||||
|  | ||||
|             // Then | ||||
|             result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|             { | ||||
|                 ex.Message.ShouldBe("Short option does not have a valid name."); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Theory] | ||||
|     [InlineData("-")] | ||||
|     [InlineData("- ")] | ||||
|     public void Should_Throw_On_Missing_Option_Name(string actual) | ||||
|     { | ||||
|         // Given | ||||
|  | ||||
|         // When | ||||
|         var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual })); | ||||
|  | ||||
|         // Then | ||||
|         result.ShouldBeOfType<CommandParseException>().And(ex => | ||||
|         { | ||||
|             ex.Message.ShouldBe("Option does not have a name."); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace Spectre.Console.Tests.Unit.Cli.Testing; | ||||
| namespace Spectre.Console.Tests.Unit.Cli.Testing; | ||||
|  | ||||
| public class FakeTypeRegistrarTests | ||||
| { | ||||
|   | ||||
| @@ -1,20 +1,20 @@ | ||||
| namespace Spectre.Console.Tests.Unit; | ||||
|  | ||||
| public sealed class StyleTests | ||||
| { | ||||
| { | ||||
|     [Fact] | ||||
|     public void Should_Convert_From_Color_As_Expected() | ||||
|     { | ||||
|         // Given | ||||
|         Style style; | ||||
|  | ||||
|         // When | ||||
|         style = Color.Red; | ||||
|  | ||||
|         // Then | ||||
|         style.Foreground.ShouldBe(Color.Red); | ||||
|     } | ||||
|  | ||||
|     { | ||||
|         // Given | ||||
|         Style style; | ||||
|  | ||||
|         // When | ||||
|         style = Color.Red; | ||||
|  | ||||
|         // Then | ||||
|         style.Foreground.ShouldBe(Color.Red); | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public void Should_Combine_Two_Styles_As_Expected() | ||||
|     { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| namespace Spectre.Console.Tests; | ||||
| namespace Spectre.Console.Tests; | ||||
|  | ||||
| [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] | ||||
| public sealed class GitHubIssueAttribute : Attribute | ||||
|   | ||||
		Reference in New Issue
	
	Block a user