mirror of
				https://github.com/spectreconsole/spectre.console.git
				synced 2025-10-25 15:19:23 +00:00 
			
		
		
		
	Compare commits
	
		
			44 Commits
		
	
	
		
			0.50.0
			...
			renovate/m
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 818fe36712 | ||
|  | e51812237e | ||
|  | 749f0fded8 | ||
|  | f5f61ca610 | ||
|  | d90e94dbb3 | ||
|  | 169abca986 | ||
|  | 3c2156268c | ||
|  | 6fb81103f0 | ||
|  | 880e83b27c | ||
|  | 0b270e1ccd | ||
|  | 2d9e8069fd | ||
|  | b551bbd244 | ||
|  | 3a70fbec75 | ||
|  | c67b3df3ba | ||
|  | 8e474f514c | ||
|  | 097f740bbd | ||
|  | ba7299adcf | ||
|  | d84f9ae713 | ||
|  | 3a6d3e4520 | ||
|  | a8b2f1f1e0 | ||
|  | 0889c2f97c | ||
|  | f4782d9916 | ||
|  | 8b59ddfd41 | ||
|  | 6ad814cab0 | ||
|  | f32f80dc57 | ||
|  | 7f3ebe02c4 | ||
|  | d77bfb6391 | ||
|  | 7819f0693d | ||
|  | 465be9391b | ||
|  | 7e5ddb1efe | ||
|  | aabe8eeaf8 | ||
|  | 108b23fca8 | ||
|  | 7051bc9e2d | ||
|  | 65bab890f2 | ||
|  | bd0e2d3e22 | ||
|  | 9efc426eb9 | ||
|  | 2570202990 | ||
|  | e4b5b56d93 | ||
|  | 67c3909bbb | ||
|  | d836ad1805 | ||
|  | 57dd8ee410 | ||
|  | 6105ee2a86 | ||
|  | b5c839030c | ||
|  | b08ca1c4d7 | 
| @@ -8,7 +8,7 @@ indent_size = 4 | ||||
| insert_final_newline = false | ||||
| trim_trailing_whitespace = true | ||||
|  | ||||
| [*.sln] | ||||
| [*.{sln,slnx}] | ||||
| indent_style = tab | ||||
|  | ||||
| [*.{csproj,vbproj,vcxproj,vcxproj.filters}] | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/ci.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -18,12 +18,12 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|         uses: actions/checkout@v5 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Setup .NET SDK | ||||
|         uses: actions/setup-dotnet@v4 | ||||
|         uses: actions/setup-dotnet@v5 | ||||
|         with: | ||||
|           dotnet-version: | | ||||
|             8.0.x | ||||
| @@ -37,7 +37,7 @@ jobs: | ||||
|  | ||||
|       - name: Upload Verify Test Results | ||||
|         if: failure() | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         uses: actions/upload-artifact@v5 | ||||
|         with: | ||||
|           name: verify-test-results | ||||
|           path: | | ||||
|   | ||||
							
								
								
									
										12
									
								
								.github/workflows/publish.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/publish.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -24,12 +24,12 @@ jobs: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v4 | ||||
|         uses: actions/checkout@v5 | ||||
|         with: | ||||
|           fetch-depth: 0 | ||||
|  | ||||
|       - name: Setup .NET SDK | ||||
|         uses: actions/setup-dotnet@v4 | ||||
|         uses: actions/setup-dotnet@v5 | ||||
|         with: | ||||
|           dotnet-version: | | ||||
|             8.0.x | ||||
| @@ -53,17 +53,17 @@ jobs: | ||||
|     runs-on: windows-latest | ||||
|     steps: | ||||
|     - name: Checkout | ||||
|       uses: actions/checkout@v4 | ||||
|       uses: actions/checkout@v5 | ||||
|       with: | ||||
|         fetch-depth: 0 | ||||
|  | ||||
|     - name: Setup .NET SDK | ||||
|       uses: actions/setup-dotnet@v4 | ||||
|       uses: actions/setup-dotnet@v5 | ||||
|  | ||||
|     - name: Setup Node.js | ||||
|       uses: actions/setup-node@v4 | ||||
|       uses: actions/setup-node@v5 | ||||
|       with: | ||||
|         node-version: '16' | ||||
|         node-version: '22' | ||||
|  | ||||
|     - name: Cache dependencies | ||||
|       uses: actions/cache@v4 | ||||
|   | ||||
							
								
								
									
										11
									
								
								build.cake
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								build.cake
									
									
									
									
									
								
							| @@ -15,7 +15,8 @@ Task("Build") | ||||
|     .Does(context => | ||||
| { | ||||
|     Information("Compiling generator..."); | ||||
|     DotNetBuild("./resources/scripts/Generator/Generator.sln", new DotNetBuildSettings { | ||||
|     DotNetBuild("./resources/scripts/Generator/Generator.slnx", new DotNetBuildSettings | ||||
|     { | ||||
|         Configuration = configuration, | ||||
|         Verbosity = DotNetVerbosity.Minimal, | ||||
|         NoLogo = true, | ||||
| @@ -25,7 +26,8 @@ Task("Build") | ||||
|     }); | ||||
|  | ||||
|     Information("\nCompiling Spectre.Console..."); | ||||
|     DotNetBuild("./src/Spectre.Console.sln", new DotNetBuildSettings { | ||||
|     DotNetBuild("./src/Spectre.Console.slnx", new DotNetBuildSettings | ||||
|     { | ||||
|         Configuration = configuration, | ||||
|         Verbosity = DotNetVerbosity.Minimal, | ||||
|         NoLogo = true, | ||||
| @@ -60,7 +62,8 @@ Task("Package") | ||||
|     .IsDependentOn("Test") | ||||
|     .Does(context => | ||||
| { | ||||
|     context.DotNetPack($"./src/Spectre.Console.sln", new DotNetPackSettings { | ||||
|     context.DotNetPack($"./src/Spectre.Console.slnx", new DotNetPackSettings | ||||
|     { | ||||
|         Configuration = configuration, | ||||
|         Verbosity = DotNetVerbosity.Minimal, | ||||
|         NoLogo = true, | ||||
| @@ -106,4 +109,4 @@ Task("Default") | ||||
| //////////////////////////////////////////////////////////////// | ||||
| // Execution | ||||
|  | ||||
| RunTarget(target) | ||||
| RunTarget(target); | ||||
| @@ -38,8 +38,8 @@ | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Microsoft.Playwright" Version="1.51.0" /> | ||||
|     <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||||
|     <PackageReference Include="Microsoft.Playwright" Version="1.55.0" /> | ||||
|     <PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> | ||||
|     <PackageReference Include="Statiq.CodeAnalysis" Version="1.0.0-beta.72" /> | ||||
|     <PackageReference Include="Statiq.Common" Version="1.0.0-beta.72" /> | ||||
|     <PackageReference Include="Statiq.Web" Version="1.0.0-beta.60" /> | ||||
|   | ||||
| @@ -1,25 +0,0 @@ | ||||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio Version 16 | ||||
| VisualStudioVersion = 16.0.30011.22 | ||||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Docs", "Docs.csproj", "{C337F609-A890-4E52-BDA3-91658039B0E3}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| 		Release|Any CPU = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{C337F609-A890-4E52-BDA3-91658039B0E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{C337F609-A890-4E52-BDA3-91658039B0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{C337F609-A890-4E52-BDA3-91658039B0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{C337F609-A890-4E52-BDA3-91658039B0E3}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {2FB3922B-494A-45EB-A479-FC507B8E107C} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
							
								
								
									
										3
									
								
								docs/Docs.slnx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/Docs.slnx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| <Solution> | ||||
|   <Project Path="Docs.csproj" /> | ||||
| </Solution> | ||||
| @@ -1,6 +0,0 @@ | ||||
| { | ||||
|   "sdk": { | ||||
|     "version": "9.0.202", | ||||
|     "rollForward": "latestFeature" | ||||
|   } | ||||
| } | ||||
| @@ -29,7 +29,7 @@ Optional: Embed an asciicast. The cast parameter should be the base name of the | ||||
| one suffixed with -rich.cast and a second named -plain.cast. The cast attribute should be the name without | ||||
| the suffix.  | ||||
|  | ||||
| To generate a new cast file, open the \resources\scripts\Generator\Generator.sln project and add a new sample in the | ||||
| To generate a new cast file, open the \resources\scripts\Generator\Generator.slnx project and add a new sample in the | ||||
| Commands/AsciiCast/Samples/ folder. If the widget is static such as a tree or a table, try and animate the widget | ||||
| using the Live widget to change the content or styling.  | ||||
|  | ||||
|   | ||||
							
								
								
									
										2
									
								
								docs/input/assets/casts/align-rich.cast
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								docs/input/assets/casts/align-rich.cast
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| {"version": 2, "width": 40, "height": 3, "timestamp": 1667342769, "env": {"SHELL": "/bin/bash", "TERM": "xterm-256color"}} | ||||
| [0.0, "o", "\u001b[H\u001b[2B\u001b[38;5;9;48;5;0mSpectre!\u001b[0m"] | ||||
| @@ -0,0 +1,71 @@ | ||||
| Title: Spectre.Console 0.50 released! | ||||
| Description: Now with 25% less lead! | ||||
| Published: 2025-04-08 | ||||
| Category: Release Notes | ||||
| Excluded: false | ||||
| --- | ||||
|  | ||||
| Version 0.50 of Spectre.Console has been released! | ||||
|  | ||||
| ## New Contributors | ||||
|  | ||||
| * [@Kissaki](https://github.com/Kissaki) made their first contribution in [#1575](https://github.com/spectreconsole/spectre.console/pull/1575) | ||||
| * [@z4ryy](https://github.com/z4ryy) made their first contribution in [#1590](https://github.com/spectreconsole/spectre.console/pull/1590) | ||||
| * [@TonWin618](https://github.com/TonWin618) made their first contribution in [#1595](https://github.com/spectreconsole/spectre.console/pull/1595) | ||||
| * [@KirillOsenkov](https://github.com/KirillOsenkov) made their first contribution in [#1623](https://github.com/spectreconsole/spectre.console/pull/1623) | ||||
| * [@davide-pi](https://github.com/davide-pi) made their first contribution in [#1246](https://github.com/spectreconsole/spectre.console/pull/1246) | ||||
| * [@armanossiloko](https://github.com/armanossiloko) made their first contribution in [#1668](https://github.com/spectreconsole/spectre.console/pull/1668) | ||||
| * [@PascalSenn](https://github.com/PascalSenn) made their first contribution in [#1687](https://github.com/spectreconsole/spectre.console/pull/1687) | ||||
| * [@tpill90](https://github.com/tpill90) made their first contribution in [#904](https://github.com/spectreconsole/spectre.console/pull/904) | ||||
| * [@tmds](https://github.com/tmds) made their first contribution in [#1194](https://github.com/spectreconsole/spectre.console/pull/1194) | ||||
| * [@TheMarteh](https://github.com/TheMarteh) made their first contribution in [#1708](https://github.com/spectreconsole/spectre.console/pull/1708) | ||||
| * [@Tolitech](https://github.com/Tolitech) made their first contribution in [#1717](https://github.com/spectreconsole/spectre.console/pull/1717) | ||||
| * [@TheTonttu](https://github.com/TheTonttu) made their first contribution in [#1740](https://github.com/spectreconsole/spectre.console/pull/1740) | ||||
| * [@byte2pixel](https://github.com/byte2pixel) made their first contribution in [#1762](https://github.com/spectreconsole/spectre.console/pull/1762) | ||||
| * [@Moustafaa91](https://github.com/Moustafaa91) made their first contribution in [#1779](https://github.com/spectreconsole/spectre.console/pull/1779) | ||||
|  | ||||
| ### General | ||||
|  | ||||
| * Strong name the assemblies by [@KirillOsenkov](https://github.com/KirillOsenkov) in [#1623](https://github.com/spectreconsole/spectre.console/pull/1623) | ||||
| * Update MSDN link to learn.microsoft.com by [@Kissaki](https://github.com/Kissaki) in [#1575](https://github.com/spectreconsole/spectre.console/pull/1575) | ||||
| * Add spanish translation for help strings by [@kzu](https://github.com/kzu) in [#1597](https://github.com/spectreconsole/spectre.console/pull/1597) | ||||
| * Update documentation: add example for the Text Prompt usage by [@davide-pi](https://github.com/davide-pi) in [#1636](https://github.com/spectreconsole/spectre.console/pull/1636) | ||||
| * Fix typos xml docs by [@devlead](https://github.com/devlead) in [#1684](https://github.com/spectreconsole/spectre.console/pull/1684) | ||||
| * Upgrade SixLabors.ImageSharp to 3.1.7 by [@Moustafaa91](https://github.com/Moustafaa91) in [#1779](https://github.com/spectreconsole/spectre.console/pull/1779) | ||||
|  | ||||
| ### Console | ||||
|  | ||||
| * AOT Support for Spectre.Console by [@phil-scott-78](https://github.com/phil-scott-78) in [#1690](https://github.com/spectreconsole/spectre.console/pull/1690) | ||||
| * Make method reference to Markup.Escape more obvious by [@Kissaki](https://github.com/Kissaki) in [#1574](https://github.com/spectreconsole/spectre.console/pull/1574) | ||||
| * Fix `HtmlEncoder` Incorrectly Applying Italics to Bold Text by [@z4ryy](https://github.com/z4ryy) in [#1590](https://github.com/spectreconsole/spectre.console/pull/1590) | ||||
| * Fix Console Display Issue with Deleting Wide Characters by [@TonWin618](https://github.com/TonWin618) in [#1595](https://github.com/spectreconsole/spectre.console/pull/1595) | ||||
| * Fix search bug in prompt related to custom item types by [@patriksvensson](https://github.com/patriksvensson) in [#1627](https://github.com/spectreconsole/spectre.console/pull/1627) | ||||
| * Cleanup the prompt tests by [@0xced](https://github.com/0xced) in [#1635](https://github.com/spectreconsole/spectre.console/pull/1635) | ||||
| * Add custom style for each calendar event by [@davide-pi](https://github.com/davide-pi) in [#1246](https://github.com/spectreconsole/spectre.console/pull/1246) | ||||
| * Fix tree expansion bug by [@davide-pi](https://github.com/davide-pi) in [#1245](https://github.com/spectreconsole/spectre.console/pull/1245) | ||||
| * Enhance the style of the checkboxes for multi-selection by [@davide-pi](https://github.com/davide-pi) in [#1244](https://github.com/spectreconsole/spectre.console/pull/1244) | ||||
| * Improve exception if a (multi)selection prompt is used incorrectly by [@0xced](https://github.com/0xced) in [#1637](https://github.com/spectreconsole/spectre.console/pull/1637) | ||||
| * Fix incorrect panel height calculation in complex layout by [@BlazeFace](https://github.com/BlazeFace) in [#1514](https://github.com/spectreconsole/spectre.console/pull/1514) | ||||
| * Adding Enricher for Azure Pipelines by [@BlazeFace](https://github.com/BlazeFace) in [#1675](https://github.com/spectreconsole/spectre.console/pull/1675) | ||||
| * Added hex color conversion by [@jsheely](https://github.com/jsheely) in [#1432](https://github.com/spectreconsole/spectre.console/pull/1432) | ||||
| * Fixed type in Segment description by [@PascalSenn](https://github.com/PascalSenn) in [#1687](https://github.com/spectreconsole/spectre.console/pull/1687) | ||||
| * Adding TransferSpeedColumn configuration to display bits/bytes + binary/decimal prefixes by [@tpill90](https://github.com/tpill90) in [#904](https://github.com/spectreconsole/spectre.console/pull/904) | ||||
| * Changes Emoji dictionary to OrdinalIgnoreCase for performance by [@phil-scott-78](https://github.com/phil-scott-78) in [#1691](https://github.com/spectreconsole/spectre.console/pull/1691) | ||||
| * ProgressTask.GetPercentage() returns 100 when max value is 0 by [@FrankRay78](https://github.com/FrankRay78) in [#1694](https://github.com/spectreconsole/spectre.console/pull/1694) | ||||
| * Async overloads for AnsiConsole Prompt/Ask/Confirm. by [@tmds](https://github.com/tmds) in [#1194](https://github.com/spectreconsole/spectre.console/pull/1194) | ||||
| * Support 3-digit hex codes in markup by [@TheMarteh](https://github.com/TheMarteh) in [#1708](https://github.com/spectreconsole/spectre.console/pull/1708) | ||||
| * Add async spinner extension methods and related documentation by [@phil-scott-78](https://github.com/phil-scott-78) in [#1747](https://github.com/spectreconsole/spectre.console/pull/1747) | ||||
| * Fix generic exception formatting by [@0xced](https://github.com/0xced) in [#1755](https://github.com/spectreconsole/spectre.console/pull/1755) | ||||
|  | ||||
| ### CLI | ||||
|  | ||||
| * Remove redundant explain settings ctor by [@gitfool](https://github.com/gitfool) in [#1534](https://github.com/spectreconsole/spectre.console/pull/1534) | ||||
| * Trim trailing comma in settings by [@devlead](https://github.com/devlead) in [#1550](https://github.com/spectreconsole/spectre.console/pull/1550) | ||||
| * Consider -? as an alias to -h by [@kzu](https://github.com/kzu) in [#1552](https://github.com/spectreconsole/spectre.console/pull/1552) | ||||
| * Trimming of TestConsole output by CommandAppTester is user configurable. by [@FrankRay78](https://github.com/FrankRay78) in [#1739](https://github.com/spectreconsole/spectre.console/pull/1739) | ||||
| * Include resource files for additional cultures in HelpProvider. by [@Tolitech](https://github.com/Tolitech) in [#1717](https://github.com/spectreconsole/spectre.console/pull/1717) | ||||
| * Conditionally trim trailing periods of argument and option descriptions by [@TheTonttu](https://github.com/TheTonttu) in [#1740](https://github.com/spectreconsole/spectre.console/pull/1740) | ||||
| * Changed IConfigurator to return IConfigurator instead of void by [@byte2pixel](https://github.com/byte2pixel) in [#1762](https://github.com/spectreconsole/spectre.console/pull/1762) | ||||
| * Add parsed unknown flag to remaining arguments for a branch with a default command by [@FrankRay78](https://github.com/FrankRay78) in [#1660](https://github.com/spectreconsole/spectre.console/pull/1660) | ||||
| * Correctly show application version; execution of command with version option by [@FrankRay78](https://github.com/FrankRay78) in [#1663](https://github.com/spectreconsole/spectre.console/pull/1663) | ||||
| * Help output correctly decides when to show the version option by [@FrankRay78](https://github.com/FrankRay78) in [#1664](https://github.com/spectreconsole/spectre.console/pull/1664) | ||||
| @@ -0,0 +1,43 @@ | ||||
| Title: Spectre.Console 0.51.1 released! | ||||
| Description: Not a substitute for human interaction. | ||||
| Published: 2025-09-07 | ||||
| Category: Release Notes | ||||
| Excluded: false | ||||
| --- | ||||
|  | ||||
| Version `0.51.1` of Spectre.Console has been released! | ||||
|    | ||||
| _Note: Due to an issue discovered after the release of version 0.51.0, that version has now been unlisted. Let’s all pretend it never existed 😅_ | ||||
|  | ||||
| ## What's Changed | ||||
|  | ||||
| * Fix IndexOutOfRangeException in ExceptionFormatter by [@martincostello](https://github.com/martincostello) in [#1800](https://github.com/spectreconsole/spectre.console/pull/1800) | ||||
| * TestConsole can now be configured and accessed in CommandAppTester by [@magiino](https://github.com/magiino) in [#1803](https://github.com/spectreconsole/spectre.console/pull/1803) | ||||
| * Add ShowRowSeparators in Table Widget docs by [@bartoginski](https://github.com/bartoginski) in [#1807](https://github.com/spectreconsole/spectre.console/pull/1807) | ||||
| * Add support for required options by [@patriksvensson](https://github.com/patriksvensson) in [#1825](https://github.com/spectreconsole/spectre.console/pull/1825) | ||||
| * Added documentation for align widget by [@Elementttto](https://github.com/Elementttto) in [#1746](https://github.com/spectreconsole/spectre.console/pull/1746) | ||||
| * Fixed link not displayed in markup in Style.cs and added unit test cases by [@Elementttto](https://github.com/Elementttto) in [#1750](https://github.com/spectreconsole/spectre.console/pull/1750) | ||||
| * Update System.Memory dependency by [@WeihanLi](https://github.com/WeihanLi) in [#1832](https://github.com/spectreconsole/spectre.console/pull/1832) | ||||
| * Reduce memory usage for rune width cache. by [@Pannoniae](https://github.com/Pannoniae) in [#1756](https://github.com/spectreconsole/spectre.console/pull/1756) | ||||
| * Fix resizing of Live views with reduced size. by [@belucha](https://github.com/belucha) in [#1840](https://github.com/spectreconsole/spectre.console/pull/1840) | ||||
| * Corrects comment for optional text prompt by [@aljanabim](https://github.com/aljanabim) in [#1857](https://github.com/spectreconsole/spectre.console/pull/1857) | ||||
| * Update spinners by [@FroggieFrog](https://github.com/FroggieFrog) in [#1873](https://github.com/spectreconsole/spectre.console/pull/1873) | ||||
| * Support J and K for navigating list prompts by [@tobias-tengler](https://github.com/tobias-tengler) in [#1877](https://github.com/spectreconsole/spectre.console/pull/1877) | ||||
| * Fix space triggering selection when items in the selection list have a space. by [@mitchdenny](https://github.com/mitchdenny) in [#1881](https://github.com/spectreconsole/spectre.console/pull/1881) | ||||
| * Fix bug setting Header by [@mattfennerom](https://github.com/mattfennerom) in [#1890](https://github.com/spectreconsole/spectre.console/pull/1890) | ||||
|  | ||||
| ## New Contributors | ||||
|  | ||||
| * [@magiino](https://github.com/magiino) made their first contribution in [#1803](https://github.com/spectreconsole/spectre.console/pull/1803) | ||||
| * [@bartoginski](https://github.com/bartoginski) made their first contribution in [#1807](https://github.com/spectreconsole/spectre.console/pull/1807) | ||||
| * [@Elementttto](https://github.com/Elementttto) made their first contribution in [#1746](https://github.com/spectreconsole/spectre.console/pull/1746) | ||||
| * [@WeihanLi](https://github.com/WeihanLi) made their first contribution in [#1832](https://github.com/spectreconsole/spectre.console/pull/1832) | ||||
| * [@Pannoniae](https://github.com/Pannoniae) made their first contribution in [#1756](https://github.com/spectreconsole/spectre.console/pull/1756) | ||||
| * [@belucha](https://github.com/belucha) made their first contribution in [#1840](https://github.com/spectreconsole/spectre.console/pull/1840) | ||||
| * [@aljanabim](https://github.com/aljanabim) made their first contribution in [#1857](https://github.com/spectreconsole/spectre.console/pull/1857) | ||||
| * [@FroggieFrog](https://github.com/FroggieFrog) made their first contribution in [#1873](https://github.com/spectreconsole/spectre.console/pull/1873) | ||||
| * [@tobias-tengler](https://github.com/tobias-tengler) made their first contribution in [#1877](https://github.com/spectreconsole/spectre.console/pull/1877) | ||||
| * [@mitchdenny](https://github.com/mitchdenny) made their first contribution in [#1881](https://github.com/spectreconsole/spectre.console/pull/1881) | ||||
| * [@mattfennerom](https://github.com/mattfennerom) made their first contribution in [#1890](https://github.com/spectreconsole/spectre.console/pull/1890) | ||||
|  | ||||
| **Full Changelog**: [0.50.0...0.51.0](https://github.com/spectreconsole/spectre.console/compare/0.50.0...0.51.1) | ||||
| @@ -0,0 +1,17 @@ | ||||
| Title: Spectre.Console 0.52.0 released! | ||||
| Description: Don't eat (too much) glue. | ||||
| Published: 2025-10-10 | ||||
| Category: Release Notes | ||||
| Excluded: false | ||||
| --- | ||||
|  | ||||
| Version `0.52.0` of Spectre.Console has been released! | ||||
|  | ||||
| Exciting things are happening. We’ve merged support for my love child, OpenCli, in this release. That means you can now pass the parameter `--help-dump-opencli` to your application to get an [OpenCli](https://opencli.org) description dumped to stdout. | ||||
|  | ||||
| ## What's Changed | ||||
|  | ||||
| * Add OpenCLI integration to Spectre.Console.Cli by [@patriksvensson](https://github.com/patriksvensson) in [#1909](https://github.com/spectreconsole/spectre.console/pull/1909) | ||||
| * Fix OPENCLI_VISIBILITY_INTERNAL to DefineConstants concat by [@devlead](https://github.com/devlead) in [#1912](https://github.com/spectreconsole/spectre.console/pull/1912) | ||||
|  | ||||
| **Full Changelog**: https://github.com/spectreconsole/spectre.console/compare/0.51.1...0.52.0 | ||||
| @@ -18,7 +18,7 @@ public class HelloCommand : Command<HelloCommand.Settings> | ||||
|     } | ||||
|  | ||||
|  | ||||
|     public override int Execute(CommandContext context, Settings settings) | ||||
|     public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         AnsiConsole.MarkupLine($"Hello, [blue]{settings.Name}[/]"); | ||||
|         return 0; | ||||
|   | ||||
| @@ -55,7 +55,7 @@ in the previous step. | ||||
| ```csharp | ||||
| public class AddPackageCommand : Command<AddPackageSettings> | ||||
| { | ||||
|     public override int Execute(CommandContext context, AddPackageSettings settings) | ||||
|     public override int Execute(CommandContext context, AddPackageSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         // Omitted | ||||
|         return 0; | ||||
| @@ -64,7 +64,7 @@ public class AddPackageCommand : Command<AddPackageSettings> | ||||
|  | ||||
| public class AddReferenceCommand : Command<AddReferenceSettings> | ||||
| { | ||||
|     public override int Execute(CommandContext context, AddReferenceSettings settings) | ||||
|     public override int Execute(CommandContext context, AddReferenceSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         // Omitted | ||||
|         return 0; | ||||
|   | ||||
| @@ -33,7 +33,7 @@ internal sealed class FileSizeCommand : Command<FileSizeCommand.Settings> | ||||
|         public bool IncludeHidden { get; init; } | ||||
|     } | ||||
|  | ||||
|     public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) | ||||
|     public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         var searchOptions = new EnumerationOptions | ||||
|         { | ||||
|   | ||||
| @@ -61,7 +61,7 @@ in the previous step. | ||||
| ```csharp | ||||
| public class AddPackageCommand : Command<AddPackageSettings> | ||||
| { | ||||
|     public override int Execute(CommandContext context, AddPackageSettings settings) | ||||
|     public override int Execute(CommandContext context, AddPackageSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         // Omitted | ||||
|         return 0; | ||||
| @@ -70,7 +70,7 @@ public class AddPackageCommand : Command<AddPackageSettings> | ||||
|  | ||||
| public class AddReferenceCommand : Command<AddReferenceSettings> | ||||
| { | ||||
|     public override int Execute(CommandContext context, AddReferenceSettings settings) | ||||
|     public override int Execute(CommandContext context, AddReferenceSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         // Omitted | ||||
|         return 0; | ||||
|   | ||||
							
								
								
									
										22
									
								
								docs/input/cli/opencli.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								docs/input/cli/opencli.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| Title: OpenCLI Integration | ||||
| Order: 15 | ||||
| Description: OpenCLI integration | ||||
| Highlights: | ||||
|  - Generate OpenCLI descriptions | ||||
| --- | ||||
|  | ||||
| From version `0.52.0` and above, you will be able to generate [OpenCLI](https://opencli.org) | ||||
| descriptions from your `Spectre.Console.Cli` applications. | ||||
|  | ||||
| Simply add the `--help-dump-opencli` option to your application, and an  | ||||
| OpenCLI description will be written to stdout. | ||||
|  | ||||
| ```shell | ||||
| $ ./myapp --help-dump-opencli | ||||
| ``` | ||||
|  | ||||
| If you want to save it to disk, pipe it to a file. | ||||
|  | ||||
| ```shell | ||||
| $ ./myapp --help-dump-opencli > myapp.openapi.json | ||||
| ``` | ||||
| @@ -40,7 +40,7 @@ The following example validates the exit code and terminal output of a `Spectre. | ||||
|             _console = console; | ||||
|         } | ||||
|  | ||||
|         public override int Execute(CommandContext context) | ||||
|         public override int Execute(CommandContext context, CancellationToken cancellationToken) | ||||
|         { | ||||
|             _console.WriteLine("Hello world."); | ||||
|             return 0; | ||||
| @@ -63,6 +63,90 @@ The following example validates the exit code and terminal output of a `Spectre. | ||||
|     } | ||||
| ``` | ||||
|  | ||||
| The following example demonstrates how to mock user inputs for an interactive command. | ||||
| This test (InteractiveCommand_WithMockedUserInputs_ProducesExpectedOutput) simulates user interactions by pushing predefined inputs to the console, then verifies that the resulting output is as expected. | ||||
|  | ||||
| ```csharp | ||||
| public sealed class InteractiveCommandTests | ||||
| { | ||||
|     private sealed class InteractiveCommand : Command | ||||
|     { | ||||
|         private readonly IAnsiConsole _console; | ||||
|  | ||||
|         public InteractiveCommand(IAnsiConsole console) | ||||
|         { | ||||
|             _console = console; | ||||
|         } | ||||
|  | ||||
|         public override int Execute(CommandContext context, CancellationToken cancellationToken) | ||||
|         { | ||||
|             var fruits = _console.Prompt( | ||||
|                 new MultiSelectionPrompt<string>() | ||||
|                     .Title("What are your [green]favorite fruits[/]?") | ||||
|                     .NotRequired() // Not required to have a favorite fruit | ||||
|                     .PageSize(10) | ||||
|                     .MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]") | ||||
|                     .InstructionsText( | ||||
|                         "[grey](Press [blue]<space>[/] to toggle a fruit, " + | ||||
|                         "[green]<enter>[/] to accept)[/]") | ||||
|                     .AddChoices(new[] { | ||||
|                         "Apple", "Apricot", "Avocado", | ||||
|                         "Banana", "Blackcurrant", "Blueberry", | ||||
|                         "Cherry", "Cloudberry", "Coconut", | ||||
|                     })); | ||||
|  | ||||
|             var fruit = _console.Prompt( | ||||
|                 new SelectionPrompt<string>() | ||||
|                     .Title("What's your [green]favorite fruit[/]?") | ||||
|                     .PageSize(10) | ||||
|                     .MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]") | ||||
|                     .AddChoices(new[] { | ||||
|                         "Apple", "Apricot", "Avocado", | ||||
|                         "Banana", "Blackcurrant", "Blueberry", | ||||
|                         "Cherry", "Cloudberry", "Cocunut", | ||||
|                     })); | ||||
|  | ||||
|             var name = _console.Ask<string>("What's your name?"); | ||||
|  | ||||
|             _console.WriteLine($"[{string.Join(',', fruits)};{fruit};{name}]"); | ||||
|  | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     [Fact] | ||||
|     public void InteractiveCommand_WithMockedUserInputs_ProducesExpectedOutput() | ||||
|     { | ||||
|         // Given | ||||
|         TestConsole console = new(); | ||||
|         console.Interactive(); | ||||
|  | ||||
|         // Your mocked inputs must always end with "Enter" for each prompt! | ||||
|  | ||||
|         // Multi selection prompt: Choose first option | ||||
|         console.Input.PushKey(ConsoleKey.Spacebar); | ||||
|         console.Input.PushKey(ConsoleKey.Enter); | ||||
|  | ||||
|         // Selection prompt: Choose second option | ||||
|         console.Input.PushKey(ConsoleKey.DownArrow); | ||||
|         console.Input.PushKey(ConsoleKey.Enter); | ||||
|  | ||||
|         // Ask text prompt: Enter name | ||||
|         console.Input.PushTextWithEnter("Spectre Console"); | ||||
|  | ||||
|         var app = new CommandAppTester(null, new CommandAppTesterSettings(), console); | ||||
|         app.SetDefaultCommand<InteractiveCommand>(); | ||||
|  | ||||
|         // When | ||||
|         var result = app.Run(); | ||||
|  | ||||
|         // Then | ||||
|         result.ExitCode.ShouldBe(0); | ||||
|         result.Output.EndsWith("[Apple;Apricot;Spectre Console]"); | ||||
|     } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Testing console behaviour | ||||
|  | ||||
|  `TestConsole` and `TestConsoleInput` are testable implementations of `IAnsiConsole` and `IAnsiConsoleInput`, allowing you fine-grain control over testing console output and interactivity. | ||||
|   | ||||
| @@ -197,7 +197,7 @@ Console.WriteLine($"Your password is {password}"); | ||||
| ### Usage | ||||
|  | ||||
| ```csharp | ||||
| // Ask the user to enter the password | ||||
| // Ask for the user's favorite color (optional) | ||||
| var color = AnsiConsole.Prompt( | ||||
|     new TextPrompt<string>("[[Optional]] Favorite color?") | ||||
|         .AllowEmpty()); | ||||
|   | ||||
							
								
								
									
										66
									
								
								docs/input/widgets/align.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								docs/input/widgets/align.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| Title: Align | ||||
| Description: "Use **Align** to render and position widgets in the console." | ||||
| Highlights: | ||||
|     - Custom colors | ||||
|     - Labels | ||||
|     - Use your own data with a converter. | ||||
| Reference: T:Spectre.Console.Align | ||||
|  | ||||
| --- | ||||
|  | ||||
| Use `Align` to render and position widgets in the console. | ||||
|  | ||||
| <?# AsciiCast cast="align" /?> | ||||
|  | ||||
| ## Usage | ||||
|  | ||||
| ### Basic usage | ||||
|  | ||||
| ```csharp | ||||
| // Render an item and align it in the bottom-left corner of the console | ||||
| AnsiConsole.Write(new Align( | ||||
|             new Text("Spectre!"), | ||||
|             HorizontalAlignment.Left, | ||||
|             VerticalAlignment.Bottom | ||||
|         )); | ||||
| ``` | ||||
|  | ||||
| ### Align items from an IEnumerable | ||||
|  | ||||
| ```csharp | ||||
| // Create a list of items | ||||
| var alignItems = new List<Text>(){ | ||||
|         new Text("Spectre"), | ||||
|         new Text("Console"), | ||||
|         new Text("Is Awesome!") | ||||
|     }; | ||||
|  | ||||
| // Render the items in the middle-right of the console | ||||
| AnsiConsole.Write(new Align( | ||||
|             alignItems, | ||||
|             HorizontalAlignment.Right, | ||||
|             VerticalAlignment.Middle | ||||
|         )); | ||||
| ``` | ||||
|  | ||||
| ### Dynamically align with different widgets | ||||
|  | ||||
| ```csharp | ||||
| // Create a table  | ||||
| var table = new Table() | ||||
|             .AddColumn("ID") | ||||
|             .AddColumn("Methods") | ||||
|             .AddColumn("Purpose") | ||||
|             .AddRow("1", "Center()", "Initializes a new instance that is center aligned") | ||||
|             .AddRow("2", "Measure()", "Measures the renderable object") | ||||
|             .AddRow("3", "Right()", "Initializes a new instance that is right aligned."); | ||||
|  | ||||
| // Create a panel | ||||
| var panel = new Panel(table) | ||||
|             .Header("Other Align Methods") | ||||
|             .Border(BoxBorder.Double); | ||||
|  | ||||
| // Renders the panel in the top-center of the console | ||||
| AnsiConsole.Write(new Align(panel, HorizontalAlignment.Center, VerticalAlignment.Top)); | ||||
| ``` | ||||
|  | ||||
| @@ -138,3 +138,10 @@ table.Columns[0].NoWrap(); | ||||
| // Set the column width | ||||
| table.Columns[0].Width(15); | ||||
| ``` | ||||
|  | ||||
| ### Show row separators | ||||
|  | ||||
| ```csharp | ||||
| // Shows separator between each row | ||||
| table.ShowRowSeparators(); | ||||
| ``` | ||||
| @@ -3,7 +3,7 @@ | ||||
|   "isRoot": true, | ||||
|   "tools": { | ||||
|     "cake.tool": { | ||||
|       "version": "5.0.0", | ||||
|       "version": "5.1.0", | ||||
|       "commands": [ | ||||
|         "dotnet-cake" | ||||
|       ] | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| { | ||||
|   "$schema": "http://json.schemastore.org/global", | ||||
|   "sdk": { | ||||
|     "version": "9.0.202", | ||||
|     "version": "9.0.305", | ||||
|     "rollForward": "latestFeature" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -19,4 +19,4 @@ if(!$?) { | ||||
| Pop-Location | ||||
|  | ||||
| # Copy the files to the correct location | ||||
| Copy-Item  (Join-Path "$Output" "Spinner.Generated.cs") -Destination "$Source/Widgets/Progress/Spinner.Generated.cs" | ||||
| Copy-Item  (Join-Path "$Output" "Spinner.Generated.cs") -Destination "$Source/Live/Progress/Spinner.Generated.cs" | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| using System.IO; | ||||
| using System.Threading; | ||||
| using Generator.Models; | ||||
| using Scriban; | ||||
| using Spectre.Console.Cli; | ||||
| @@ -21,7 +22,7 @@ namespace Generator.Commands | ||||
|             public string Input { get; set; } | ||||
|         } | ||||
|  | ||||
|         public override int Execute(CommandContext context, Settings settings) | ||||
|         public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken) | ||||
|         { | ||||
|             var templates = new FilePath[] | ||||
|             { | ||||
|   | ||||
| @@ -2,6 +2,7 @@ using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Net.Http; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using AngleSharp.Html.Parser; | ||||
| using Generator.Models; | ||||
| @@ -39,7 +40,7 @@ namespace Generator.Commands | ||||
|             _parser = new HtmlParser(); | ||||
|         } | ||||
|  | ||||
|         public override async Task<int> ExecuteAsync(CommandContext context, Settings settings) | ||||
|         public override async Task<int> ExecuteAsync(CommandContext context, Settings settings, CancellationToken cancellationToken) | ||||
|         { | ||||
|             var output = new DirectoryPath(settings.Output); | ||||
|             if (!_fileSystem.Directory.Exists(settings.Output)) | ||||
|   | ||||
| @@ -4,6 +4,7 @@ using System.Diagnostics.CodeAnalysis; | ||||
| using System.IO; | ||||
| using System.Linq; | ||||
| using System.Text; | ||||
| using System.Threading; | ||||
| using Generator.Commands.Samples; | ||||
| using Spectre.Console; | ||||
| using Spectre.Console.Cli; | ||||
| @@ -38,7 +39,7 @@ namespace Generator.Commands | ||||
|             _console = new AsciiCastConsole(console); | ||||
|         } | ||||
|  | ||||
|         public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings) | ||||
|         public override int Execute([NotNull] CommandContext context, [NotNull] Settings settings, CancellationToken cancellationToken) | ||||
|         { | ||||
|             var samples = typeof(BaseSample).Assembly | ||||
|                 .GetTypes() | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Threading; | ||||
| using Generator.Models; | ||||
| using Scriban; | ||||
| using Spectre.Console.Cli; | ||||
| @@ -16,7 +17,7 @@ namespace Generator.Commands | ||||
|             _fileSystem = new FileSystem(); | ||||
|         } | ||||
|  | ||||
|         public override int Execute(CommandContext context, GeneratorSettings settings) | ||||
|         public override int Execute(CommandContext context, GeneratorSettings settings, CancellationToken cancellationToken) | ||||
|         { | ||||
|             // Read the spinner model. | ||||
|             var spinners = new List<Spinner>(); | ||||
|   | ||||
| @@ -286,6 +286,38 @@ | ||||
|       "⠀⡀" | ||||
|     ] | ||||
|   }, | ||||
|   "dots13": { | ||||
|     "interval": 80, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "⣼", | ||||
|       "⣹", | ||||
|       "⢻", | ||||
|       "⠿", | ||||
|       "⡟", | ||||
|       "⣏", | ||||
|       "⣧", | ||||
|       "⣶" | ||||
|     ] | ||||
|   }, | ||||
|   "dots14": { | ||||
|     "interval": 80, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "⠉⠉", | ||||
|       "⠈⠙", | ||||
|       "⠀⠹", | ||||
|       "⠀⢸", | ||||
|       "⠀⣰", | ||||
|       "⢀⣠", | ||||
|       "⣀⣀", | ||||
|       "⣄⡀", | ||||
|       "⣆⠀", | ||||
|       "⡇⠀", | ||||
|       "⠏⠀", | ||||
|       "⠋⠁" | ||||
|     ] | ||||
|   }, | ||||
|   "dots8Bit": { | ||||
|     "interval": 80, | ||||
|     "unicode": true, | ||||
| @@ -548,6 +580,61 @@ | ||||
|       "⣿" | ||||
|     ] | ||||
|   }, | ||||
|   "dotsCircle": { | ||||
|     "interval": 80, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "⢎ ", | ||||
|       "⠎⠁", | ||||
|       "⠊⠑", | ||||
|       "⠈⠱", | ||||
|       " ⡱", | ||||
|       "⢀⡰", | ||||
|       "⢄⡠", | ||||
|       "⢆⡀" | ||||
|     ] | ||||
|   }, | ||||
|   "sand": { | ||||
|     "interval": 80, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "⠁", | ||||
|       "⠂", | ||||
|       "⠄", | ||||
|       "⡀", | ||||
|       "⡈", | ||||
|       "⡐", | ||||
|       "⡠", | ||||
|       "⣀", | ||||
|       "⣁", | ||||
|       "⣂", | ||||
|       "⣄", | ||||
|       "⣌", | ||||
|       "⣔", | ||||
|       "⣤", | ||||
|       "⣥", | ||||
|       "⣦", | ||||
|       "⣮", | ||||
|       "⣶", | ||||
|       "⣷", | ||||
|       "⣿", | ||||
|       "⡿", | ||||
|       "⠿", | ||||
|       "⢟", | ||||
|       "⠟", | ||||
|       "⡛", | ||||
|       "⠛", | ||||
|       "⠫", | ||||
|       "⢋", | ||||
|       "⠋", | ||||
|       "⠍", | ||||
|       "⡉", | ||||
|       "⠉", | ||||
|       "⠑", | ||||
|       "⠡", | ||||
|       "⢁" | ||||
|     ] | ||||
|   }, | ||||
|   "line": { | ||||
|     "interval": 130, | ||||
|     "unicode": false, | ||||
| @@ -763,6 +850,22 @@ | ||||
|       "◥" | ||||
|     ] | ||||
|   }, | ||||
|   "binary": { | ||||
|     "interval": 80, | ||||
|     "unicode": false, | ||||
|     "frames": [ | ||||
|       "010010", | ||||
|       "001100", | ||||
|       "100101", | ||||
|       "111010", | ||||
|       "111101", | ||||
|       "010111", | ||||
|       "101011", | ||||
|       "111000", | ||||
|       "110011", | ||||
|       "110101" | ||||
|     ] | ||||
|   }, | ||||
|   "arc": { | ||||
|     "interval": 100, | ||||
|     "unicode": true, | ||||
| @@ -978,6 +1081,7 @@ | ||||
|       "[=   ]", | ||||
|       "[==  ]", | ||||
|       "[=== ]", | ||||
|       "[====]", | ||||
|       "[ ===]", | ||||
|       "[  ==]", | ||||
|       "[   =]", | ||||
| @@ -1351,6 +1455,135 @@ | ||||
|       "ββββββρ" | ||||
|     ] | ||||
|   }, | ||||
|   "fingerDance": { | ||||
|     "interval": 160, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "🤘 ", | ||||
|       "🤟 ", | ||||
|       "🖖 ", | ||||
|       "✋ ", | ||||
|       "🤚 ", | ||||
|       "👆 " | ||||
|     ] | ||||
|   }, | ||||
|   "fistBump": { | ||||
|     "interval": 80, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "🤜\u3000\u3000\u3000\u3000🤛 ", | ||||
|       "🤜\u3000\u3000\u3000\u3000🤛 ", | ||||
|       "🤜\u3000\u3000\u3000\u3000🤛 ", | ||||
|       "\u3000🤜\u3000\u3000🤛\u3000 ", | ||||
|       "\u3000\u3000🤜🤛\u3000\u3000 ", | ||||
|       "\u3000🤜✨🤛\u3000\u3000 ", | ||||
|       "🤜\u3000✨\u3000🤛\u3000 " | ||||
|     ] | ||||
|   }, | ||||
|   "soccerHeader": { | ||||
|     "interval": 80, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       " 🧑⚽️       🧑 ", | ||||
|       "🧑  ⚽️      🧑 ", | ||||
|       "🧑   ⚽️     🧑 ", | ||||
|       "🧑    ⚽️    🧑 ", | ||||
|       "🧑     ⚽️   🧑 ", | ||||
|       "🧑      ⚽️  🧑 ", | ||||
|       "🧑       ⚽️🧑  ", | ||||
|       "🧑      ⚽️  🧑 ", | ||||
|       "🧑     ⚽️   🧑 ", | ||||
|       "🧑    ⚽️    🧑 ", | ||||
|       "🧑   ⚽️     🧑 ", | ||||
|       "🧑  ⚽️      🧑 " | ||||
|     ] | ||||
|   }, | ||||
|   "mindblown": { | ||||
|     "interval": 160, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "😐 ", | ||||
|       "😐 ", | ||||
|       "😮 ", | ||||
|       "😮 ", | ||||
|       "😦 ", | ||||
|       "😦 ", | ||||
|       "😧 ", | ||||
|       "😧 ", | ||||
|       "🤯 ", | ||||
|       "💥 ", | ||||
|       "✨ ", | ||||
|       "\u3000 ", | ||||
|       "\u3000 ", | ||||
|       "\u3000 " | ||||
|     ] | ||||
|   }, | ||||
|   "speaker": { | ||||
|     "interval": 160, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "🔈 ", | ||||
|       "🔉 ", | ||||
|       "🔊 ", | ||||
|       "🔉 " | ||||
|     ] | ||||
|   }, | ||||
|   "orangePulse": { | ||||
|     "interval": 100, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "🔸 ", | ||||
|       "🔶 ", | ||||
|       "🟠 ", | ||||
|       "🟠 ", | ||||
|       "🔶 " | ||||
|     ] | ||||
|   }, | ||||
|   "bluePulse": { | ||||
|     "interval": 100, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "🔹 ", | ||||
|       "🔷 ", | ||||
|       "🔵 ", | ||||
|       "🔵 ", | ||||
|       "🔷 " | ||||
|     ] | ||||
|   }, | ||||
|   "orangeBluePulse": { | ||||
|     "interval": 100, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "🔸 ", | ||||
|       "🔶 ", | ||||
|       "🟠 ", | ||||
|       "🟠 ", | ||||
|       "🔶 ", | ||||
|       "🔹 ", | ||||
|       "🔷 ", | ||||
|       "🔵 ", | ||||
|       "🔵 ", | ||||
|       "🔷 " | ||||
|     ] | ||||
|   }, | ||||
|   "timeTravel": { | ||||
|     "interval": 100, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       "🕛 ", | ||||
|       "🕚 ", | ||||
|       "🕙 ", | ||||
|       "🕘 ", | ||||
|       "🕗 ", | ||||
|       "🕖 ", | ||||
|       "🕕 ", | ||||
|       "🕔 ", | ||||
|       "🕓 ", | ||||
|       "🕒 ", | ||||
|       "🕑 ", | ||||
|       "🕐 " | ||||
|     ] | ||||
|   }, | ||||
|   "aesthetic": { | ||||
|     "interval": 80, | ||||
|     "unicode": true, | ||||
| @@ -1364,5 +1597,144 @@ | ||||
|       "▰▰▰▰▰▰▰", | ||||
|       "▰▱▱▱▱▱▱" | ||||
|     ] | ||||
|   }, | ||||
|   "dwarfFortress": { | ||||
|     "interval": 80, | ||||
|     "unicode": true, | ||||
|     "frames": [ | ||||
|       " ██████£££  ", | ||||
|       "☺██████£££  ", | ||||
|       "☺██████£££  ", | ||||
|       "☺▓█████£££  ", | ||||
|       "☺▓█████£££  ", | ||||
|       "☺▒█████£££  ", | ||||
|       "☺▒█████£££  ", | ||||
|       "☺░█████£££  ", | ||||
|       "☺░█████£££  ", | ||||
|       "☺ █████£££  ", | ||||
|       " ☺█████£££  ", | ||||
|       " ☺█████£££  ", | ||||
|       " ☺▓████£££  ", | ||||
|       " ☺▓████£££  ", | ||||
|       " ☺▒████£££  ", | ||||
|       " ☺▒████£££  ", | ||||
|       " ☺░████£££  ", | ||||
|       " ☺░████£££  ", | ||||
|       " ☺ ████£££  ", | ||||
|       "  ☺████£££  ", | ||||
|       "  ☺████£££  ", | ||||
|       "  ☺▓███£££  ", | ||||
|       "  ☺▓███£££  ", | ||||
|       "  ☺▒███£££  ", | ||||
|       "  ☺▒███£££  ", | ||||
|       "  ☺░███£££  ", | ||||
|       "  ☺░███£££  ", | ||||
|       "  ☺ ███£££  ", | ||||
|       "   ☺███£££  ", | ||||
|       "   ☺███£££  ", | ||||
|       "   ☺▓██£££  ", | ||||
|       "   ☺▓██£££  ", | ||||
|       "   ☺▒██£££  ", | ||||
|       "   ☺▒██£££  ", | ||||
|       "   ☺░██£££  ", | ||||
|       "   ☺░██£££  ", | ||||
|       "   ☺ ██£££  ", | ||||
|       "    ☺██£££  ", | ||||
|       "    ☺██£££  ", | ||||
|       "    ☺▓█£££  ", | ||||
|       "    ☺▓█£££  ", | ||||
|       "    ☺▒█£££  ", | ||||
|       "    ☺▒█£££  ", | ||||
|       "    ☺░█£££  ", | ||||
|       "    ☺░█£££  ", | ||||
|       "    ☺ █£££  ", | ||||
|       "     ☺█£££  ", | ||||
|       "     ☺█£££  ", | ||||
|       "     ☺▓£££  ", | ||||
|       "     ☺▓£££  ", | ||||
|       "     ☺▒£££  ", | ||||
|       "     ☺▒£££  ", | ||||
|       "     ☺░£££  ", | ||||
|       "     ☺░£££  ", | ||||
|       "     ☺ £££  ", | ||||
|       "      ☺£££  ", | ||||
|       "      ☺£££  ", | ||||
|       "      ☺▓££  ", | ||||
|       "      ☺▓££  ", | ||||
|       "      ☺▒££  ", | ||||
|       "      ☺▒££  ", | ||||
|       "      ☺░££  ", | ||||
|       "      ☺░££  ", | ||||
|       "      ☺ ££  ", | ||||
|       "       ☺££  ", | ||||
|       "       ☺££  ", | ||||
|       "       ☺▓£  ", | ||||
|       "       ☺▓£  ", | ||||
|       "       ☺▒£  ", | ||||
|       "       ☺▒£  ", | ||||
|       "       ☺░£  ", | ||||
|       "       ☺░£  ", | ||||
|       "       ☺ £  ", | ||||
|       "        ☺£  ", | ||||
|       "        ☺£  ", | ||||
|       "        ☺▓  ", | ||||
|       "        ☺▓  ", | ||||
|       "        ☺▒  ", | ||||
|       "        ☺▒  ", | ||||
|       "        ☺░  ", | ||||
|       "        ☺░  ", | ||||
|       "        ☺   ", | ||||
|       "        ☺  &", | ||||
|       "        ☺ ☼&", | ||||
|       "       ☺ ☼ &", | ||||
|       "       ☺☼  &", | ||||
|       "      ☺☼  & ", | ||||
|       "      ‼   & ", | ||||
|       "     ☺   &  ", | ||||
|       "    ‼    &  ", | ||||
|       "   ☺    &   ", | ||||
|       "  ‼     &   ", | ||||
|       " ☺     &    ", | ||||
|       "‼      &    ", | ||||
|       "      &     ", | ||||
|       "      &     ", | ||||
|       "     &   ░  ", | ||||
|       "     &   ▒  ", | ||||
|       "    &    ▓  ", | ||||
|       "    &    £  ", | ||||
|       "   &    ░£  ", | ||||
|       "   &    ▒£  ", | ||||
|       "  &     ▓£  ", | ||||
|       "  &     ££  ", | ||||
|       " &     ░££  ", | ||||
|       " &     ▒££  ", | ||||
|       "&      ▓££  ", | ||||
|       "&      £££  ", | ||||
|       "      ░£££  ", | ||||
|       "      ▒£££  ", | ||||
|       "      ▓£££  ", | ||||
|       "      █£££  ", | ||||
|       "     ░█£££  ", | ||||
|       "     ▒█£££  ", | ||||
|       "     ▓█£££  ", | ||||
|       "     ██£££  ", | ||||
|       "    ░██£££  ", | ||||
|       "    ▒██£££  ", | ||||
|       "    ▓██£££  ", | ||||
|       "    ███£££  ", | ||||
|       "   ░███£££  ", | ||||
|       "   ▒███£££  ", | ||||
|       "   ▓███£££  ", | ||||
|       "   ████£££  ", | ||||
|       "  ░████£££  ", | ||||
|       "  ▒████£££  ", | ||||
|       "  ▓████£££  ", | ||||
|       "  █████£££  ", | ||||
|       " ░█████£££  ", | ||||
|       " ▒█████£££  ", | ||||
|       " ▓█████£££  ", | ||||
|       " ██████£££  ", | ||||
|       " ██████£££  " | ||||
|     ] | ||||
|   } | ||||
| } | ||||
| @@ -43,11 +43,11 @@ | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="AngleSharp" Version="1.2.0" /> | ||||
|     <PackageReference Include="AngleSharp" Version="1.3.0" /> | ||||
|     <PackageReference Include="Humanizer.Core" Version="2.14.1" /> | ||||
|     <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> | ||||
|     <PackageReference Include="Scriban" Version="6.2.0" /> | ||||
|     <PackageReference Include="Spectre.IO" Version="0.18.0" /> | ||||
|     <PackageReference Include="Newtonsoft.Json" Version="13.0.4" /> | ||||
|     <PackageReference Include="Scriban" Version="6.4.0" /> | ||||
|     <PackageReference Include="Spectre.IO" Version="0.20.0" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|   | ||||
| @@ -1,101 +0,0 @@ | ||||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio Version 17 | ||||
| VisualStudioVersion = 17.3.32922.545 | ||||
| MinimumVisualStudioVersion = 15.0.26124.0 | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Generator", "Generator.csproj", "{5668D267-53E3-4B99-97AE-59AA597D22ED}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console", "..\..\..\src\Spectre.Console\Spectre.Console.csproj", "{F75B882A-06DB-426B-9580-A7302D32E684}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.ImageSharp", "..\..\..\src\Extensions\Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj", "{112A37CB-1EFE-4A90-BD5B-5437038BE276}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{CFE7445D-F971-429D-B6E6-9E68456AE00F}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "..\..\..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj", "{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Spectre.Console.Json", "..\..\..\src\Extensions\Spectre.Console.Json\Spectre.Console.Json.csproj", "{6C96C268-CEEE-478A-A36F-E1450AC33B73}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| 		Debug|x64 = Debug|x64 | ||||
| 		Debug|x86 = Debug|x86 | ||||
| 		Release|Any CPU = Release|Any CPU | ||||
| 		Release|x64 = Release|x64 | ||||
| 		Release|x86 = Release|x86 | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{5668D267-53E3-4B99-97AE-59AA597D22ED}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73}.Release|x86.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(NestedProjects) = preSolution | ||||
| 		{F75B882A-06DB-426B-9580-A7302D32E684} = {CFE7445D-F971-429D-B6E6-9E68456AE00F} | ||||
| 		{112A37CB-1EFE-4A90-BD5B-5437038BE276} = {CFE7445D-F971-429D-B6E6-9E68456AE00F} | ||||
| 		{18A3F32D-FECD-463B-A194-6EE74EA9E5EC} = {CFE7445D-F971-429D-B6E6-9E68456AE00F} | ||||
| 		{6C96C268-CEEE-478A-A36F-E1450AC33B73} = {CFE7445D-F971-429D-B6E6-9E68456AE00F} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {5F37FDE3-D591-4D43-8DDE-2ED6BAB0A7B4} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
							
								
								
									
										14
									
								
								resources/scripts/Generator/Generator.slnx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								resources/scripts/Generator/Generator.slnx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| <Solution> | ||||
|   <Configurations> | ||||
|     <Platform Name="Any CPU" /> | ||||
|     <Platform Name="x64" /> | ||||
|     <Platform Name="x86" /> | ||||
|   </Configurations> | ||||
|   <Folder Name="/Library/"> | ||||
|     <Project Path="../../../src/Extensions/Spectre.Console.ImageSharp/Spectre.Console.ImageSharp.csproj" /> | ||||
|     <Project Path="../../../src/Extensions/Spectre.Console.Json/Spectre.Console.Json.csproj" /> | ||||
|     <Project Path="../../../src/Spectre.Console.Cli/Spectre.Console.Cli.csproj" /> | ||||
|     <Project Path="../../../src/Spectre.Console/Spectre.Console.csproj" /> | ||||
|   </Folder> | ||||
|   <Project Path="Generator.csproj" /> | ||||
| </Solution> | ||||
| @@ -100,5 +100,8 @@ dotnet_diagnostic.RCS1047.severity = none | ||||
| # RCS1090: Call 'ConfigureAwait(false)'. | ||||
| dotnet_diagnostic.RCS1090.severity = warning | ||||
|  | ||||
| # The file header is missing or not located at the top of the file | ||||
| # SA1633: The file header is missing or not located at the top of the file | ||||
| dotnet_diagnostic.SA1633.severity = none | ||||
|  | ||||
| # CA2016: Forward the CancellationToken parameter to methods that take one | ||||
| dotnet_diagnostic.CA2016.severity = warning | ||||
| @@ -1,27 +1,28 @@ | ||||
| <Project> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <PackageVersion Include="IsExternalInit" Version="1.0.3"/> | ||||
|     <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3"/> | ||||
|     <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.13.0"/> | ||||
|     <PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0"/> | ||||
|     <PackageVersion Include="MinVer" PrivateAssets="All" Version="6.0.0"/> | ||||
|     <PackageVersion Include="PolySharp" Version="1.15.0"/> | ||||
|     <PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.13.1"/> | ||||
|     <PackageVersion Include="Shouldly" Version="4.3.0"/> | ||||
|     <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.7"/> | ||||
|     <PackageVersion Include="Spectre.Verify.Extensions" Version="28.16.0"/> | ||||
|     <PackageVersion Include="StyleCop.Analyzers" PrivateAssets="All" Version="1.2.0-beta.556"/> | ||||
|     <PackageVersion Include="System.Memory" Version="4.6.3"/> | ||||
|     <PackageVersion Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160"/> | ||||
|     <PackageVersion Include="Verify.Xunit" Version="29.2.0"/> | ||||
|     <PackageVersion Include="Wcwidth.Sources" Version="2.0.0"/> | ||||
|     <PackageVersion Include="xunit" Version="2.9.3"/> | ||||
|     <PackageVersion Include="xunit.runner.visualstudio" Version="3.0.2"/> | ||||
|     <PackageVersion Include="IsExternalInit" Version="1.0.3" /> | ||||
|     <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.9" /> | ||||
|     <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" /> | ||||
|     <PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0" /> | ||||
|     <PackageVersion Include="MinVer" PrivateAssets="All" Version="6.0.0" /> | ||||
|     <PackageVersion Include="OpenCli.Sources" Version="0.5.0" /> | ||||
|     <PackageVersion Include="PolySharp" Version="1.15.0" /> | ||||
|     <PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.14.1" /> | ||||
|     <PackageVersion Include="Shouldly" Version="4.3.0" /> | ||||
|     <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.11" /> | ||||
|     <PackageVersion Include="Spectre.Verify.Extensions" Version="28.16.0" /> | ||||
|     <PackageVersion Include="StyleCop.Analyzers" PrivateAssets="All" Version="1.2.0-beta.556" /> | ||||
|     <PackageVersion Include="System.Memory" Version="4.6.3" /> | ||||
|     <PackageVersion Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" /> | ||||
|     <PackageVersion Include="Verify.Xunit" Version="31.0.1" /> | ||||
|     <PackageVersion Include="Wcwidth.Sources" Version="3.0.0" /> | ||||
|     <PackageVersion Include="xunit" Version="2.9.3" /> | ||||
|     <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> | ||||
|     </PackageVersion> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
| @@ -45,6 +45,6 @@ public sealed class CommandArgumentAttribute : Attribute | ||||
|         // Assign the result. | ||||
|         Position = position; | ||||
|         ValueName = result.Value; | ||||
|         IsRequired = result.Required; | ||||
|         IsRequired = result.IsRequired; | ||||
|     } | ||||
| } | ||||
| @@ -30,6 +30,11 @@ public sealed class CommandOptionAttribute : Attribute | ||||
|     /// </summary> | ||||
|     public bool ValueIsOptional { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the value is required. | ||||
|     /// </summary> | ||||
|     public bool IsRequired { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether this option is hidden from the help text. | ||||
|     /// </summary> | ||||
| @@ -39,7 +44,8 @@ public sealed class CommandOptionAttribute : Attribute | ||||
|     /// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class. | ||||
|     /// </summary> | ||||
|     /// <param name="template">The option template.</param> | ||||
|     public CommandOptionAttribute(string template) | ||||
|     /// <param name="isRequired">Indicates whether the option is required or not.</param> | ||||
|     public CommandOptionAttribute(string template, bool isRequired = false) | ||||
|     { | ||||
|         if (template == null) | ||||
|         { | ||||
| @@ -54,6 +60,7 @@ public sealed class CommandOptionAttribute : Attribute | ||||
|         ShortNames = result.ShortNames; | ||||
|         ValueName = result.Value; | ||||
|         ValueIsOptional = result.ValueIsOptional; | ||||
|         IsRequired = isRequired; | ||||
|     } | ||||
|  | ||||
|     internal bool IsMatch(string name) | ||||
|   | ||||
| @@ -9,19 +9,20 @@ public abstract class AsyncCommand : ICommand<EmptyCommandSettings> | ||||
|     /// Executes the command. | ||||
|     /// </summary> | ||||
|     /// <param name="context">The command context.</param> | ||||
|     /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param> | ||||
|     /// <returns>An integer indicating whether or not the command executed successfully.</returns> | ||||
|     public abstract Task<int> ExecuteAsync(CommandContext context); | ||||
|     public abstract Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings) | ||||
|     Task<int> ICommand<EmptyCommandSettings>.ExecuteAsync(CommandContext context, EmptyCommandSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return ExecuteAsync(context); | ||||
|         return ExecuteAsync(context, cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     Task<int> ICommand.Execute(CommandContext context, CommandSettings settings) | ||||
|     Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return ExecuteAsync(context); | ||||
|         return ExecuteAsync(context, cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -23,8 +23,9 @@ public abstract class AsyncCommand<TSettings> : ICommand<TSettings> | ||||
|     /// </summary> | ||||
|     /// <param name="context">The command context.</param> | ||||
|     /// <param name="settings">The settings.</param> | ||||
|     /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param> | ||||
|     /// <returns>An integer indicating whether or not the command executed successfully.</returns> | ||||
|     public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings); | ||||
|     public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings, CancellationToken cancellationToken); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings) | ||||
| @@ -33,15 +34,15 @@ public abstract class AsyncCommand<TSettings> : ICommand<TSettings> | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     Task<int> ICommand.Execute(CommandContext context, CommandSettings settings) | ||||
|     Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         Debug.Assert(settings is TSettings, "Command settings is of unexpected type."); | ||||
|         return ExecuteAsync(context, (TSettings)settings); | ||||
|         return ExecuteAsync(context, (TSettings)settings, cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings) | ||||
|     Task<int> ICommand<TSettings>.ExecuteAsync(CommandContext context, TSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return ExecuteAsync(context, settings); | ||||
|         return ExecuteAsync(context, settings, cancellationToken); | ||||
|     } | ||||
| } | ||||
| @@ -10,19 +10,20 @@ public abstract class Command : ICommand<EmptyCommandSettings> | ||||
|     /// Executes the command. | ||||
|     /// </summary> | ||||
|     /// <param name="context">The command context.</param> | ||||
|     /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param> | ||||
|     /// <returns>An integer indicating whether or not the command executed successfully.</returns> | ||||
|     public abstract int Execute(CommandContext context); | ||||
|     public abstract int Execute(CommandContext context, CancellationToken cancellationToken); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings) | ||||
|     Task<int> ICommand<EmptyCommandSettings>.ExecuteAsync(CommandContext context, EmptyCommandSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return Task.FromResult(Execute(context)); | ||||
|         return Task.FromResult(Execute(context, cancellationToken)); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     Task<int> ICommand.Execute(CommandContext context, CommandSettings settings) | ||||
|     Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return Task.FromResult(Execute(context)); | ||||
|         return Task.FromResult(Execute(context, cancellationToken)); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|   | ||||
| @@ -26,10 +26,7 @@ public sealed class CommandApp : ICommandApp | ||||
|         _executor = new CommandExecutor(registrar); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Configures the command line application. | ||||
|     /// </summary> | ||||
|     /// <param name="configuration">The configuration.</param> | ||||
|     /// <inheritdoc/> | ||||
|     public void Configure(Action<IConfigurator> configuration) | ||||
|     { | ||||
|         if (configuration == null) | ||||
| @@ -51,22 +48,14 @@ public sealed class CommandApp : ICommandApp | ||||
|         return new DefaultCommandConfigurator(GetConfigurator().SetDefaultCommand<TCommand>()); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Runs the command line application with specified arguments. | ||||
|     /// </summary> | ||||
|     /// <param name="args">The arguments.</param> | ||||
|     /// <returns>The exit code from the executed command.</returns> | ||||
|     public int Run(IEnumerable<string> args) | ||||
|     /// <inheritdoc/> | ||||
|     public int Run(IEnumerable<string> args, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return RunAsync(args).GetAwaiter().GetResult(); | ||||
|         return RunAsync(args, cancellationToken).GetAwaiter().GetResult(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Runs the command line application with specified arguments. | ||||
|     /// </summary> | ||||
|     /// <param name="args">The arguments.</param> | ||||
|     /// <returns>The exit code from the executed command.</returns> | ||||
|     public async Task<int> RunAsync(IEnumerable<string> args) | ||||
|     /// <inheritdoc/> | ||||
|     public async Task<int> RunAsync(IEnumerable<string> args, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -79,13 +68,14 @@ public sealed class CommandApp : ICommandApp | ||||
|                     cli.AddCommand<VersionCommand>(CliConstants.Commands.Version); | ||||
|                     cli.AddCommand<XmlDocCommand>(CliConstants.Commands.XmlDoc); | ||||
|                     cli.AddCommand<ExplainCommand>(CliConstants.Commands.Explain); | ||||
|                     cli.AddCommand<OpenCliGeneratorCommand>(CliConstants.Commands.OpenCli); | ||||
|                 }); | ||||
|  | ||||
|                 _executed = true; | ||||
|             } | ||||
|  | ||||
|             return await _executor | ||||
|                 .Execute(_configurator, args) | ||||
|                 .ExecuteAsync(_configurator, args, cancellationToken) | ||||
|                 .ConfigureAwait(false); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
| @@ -108,6 +98,11 @@ public sealed class CommandApp : ICommandApp | ||||
|                 return _configurator.Settings.ExceptionHandler(ex, null); | ||||
|             } | ||||
|  | ||||
|             if (ex is OperationCanceledException) | ||||
|             { | ||||
|                 return _configurator.Settings.CancellationExitCode; | ||||
|             } | ||||
|  | ||||
|             // Render the exception. | ||||
|             var pretty = GetRenderableErrorMessage(ex); | ||||
|             if (pretty != null) | ||||
|   | ||||
| @@ -25,33 +25,22 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp | ||||
|         _defaultCommandConfigurator = _app.SetDefaultCommand<TDefaultCommand>(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Configures the command line application. | ||||
|     /// </summary> | ||||
|     /// <param name="configuration">The configuration.</param> | ||||
|     /// <inheritdoc/> | ||||
|     public void Configure(Action<IConfigurator> configuration) | ||||
|     { | ||||
|         _app.Configure(configuration); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Runs the command line application with specified arguments. | ||||
|     /// </summary> | ||||
|     /// <param name="args">The arguments.</param> | ||||
|     /// <returns>The exit code from the executed command.</returns> | ||||
|     public int Run(IEnumerable<string> args) | ||||
|     /// <inheritdoc/> | ||||
|     public int Run(IEnumerable<string> args, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return _app.Run(args); | ||||
|         return _app.Run(args, cancellationToken); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Runs the command line application with specified arguments. | ||||
|     /// </summary> | ||||
|     /// <param name="args">The arguments.</param> | ||||
|     /// <returns>The exit code from the executed command.</returns> | ||||
|     public Task<int> RunAsync(IEnumerable<string> args) | ||||
|     /// <inheritdoc/> | ||||
|     public Task<int> RunAsync(IEnumerable<string> args, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         return _app.RunAsync(args); | ||||
|         return _app.RunAsync(args, cancellationToken); | ||||
|     } | ||||
|  | ||||
|     internal Configurator GetConfigurator() | ||||
|   | ||||
| @@ -24,8 +24,9 @@ public abstract class Command<TSettings> : ICommand<TSettings> | ||||
|     /// </summary> | ||||
|     /// <param name="context">The command context.</param> | ||||
|     /// <param name="settings">The settings.</param> | ||||
|     /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param> | ||||
|     /// <returns>An integer indicating whether or not the command executed successfully.</returns> | ||||
|     public abstract int Execute(CommandContext context, TSettings settings); | ||||
|     public abstract int Execute(CommandContext context, TSettings settings, CancellationToken cancellationToken); | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings) | ||||
| @@ -34,15 +35,15 @@ public abstract class Command<TSettings> : ICommand<TSettings> | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     Task<int> ICommand.Execute(CommandContext context, CommandSettings settings) | ||||
|     Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         Debug.Assert(settings is TSettings, "Command settings is of unexpected type."); | ||||
|         return Task.FromResult(Execute(context, (TSettings)settings)); | ||||
|         return Task.FromResult(Execute(context, (TSettings)settings, cancellationToken)); | ||||
|     } | ||||
|  | ||||
|     /// <inheritdoc/> | ||||
|     Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings) | ||||
|     Task<int> ICommand<TSettings>.ExecuteAsync(CommandContext context, TSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return Task.FromResult(Execute(context, settings)); | ||||
|         return Task.FromResult(Execute(context, settings, cancellationToken)); | ||||
|     } | ||||
| } | ||||
| @@ -37,6 +37,16 @@ public class CommandRuntimeException : CommandAppException | ||||
|         return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{argument.Value}'."); | ||||
|     } | ||||
|  | ||||
|     internal static CommandRuntimeException MissingRequiredOption(CommandTree node, CommandOption option) | ||||
|     { | ||||
|         if (node.Command.Name == CliConstants.DefaultCommandName) | ||||
|         { | ||||
|             return new CommandRuntimeException($"Missing required option '{option.GetOptionName()}'."); | ||||
|         } | ||||
|  | ||||
|         return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{option.GetOptionName()}'."); | ||||
|     } | ||||
|  | ||||
|     internal static CommandRuntimeException NoConverterFound(CommandParameter parameter) | ||||
|     { | ||||
|         return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'."); | ||||
|   | ||||
| @@ -201,6 +201,24 @@ public static class ConfiguratorExtensions | ||||
|         return configurator; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Tells the command line application to return the specified exit code when it's aborted through the <see cref="CancellationToken"/>. | ||||
|     /// The default cancellation exit code is 130. | ||||
|     /// </summary> | ||||
|     /// <param name="configurator">The configurator.</param> | ||||
|     /// <param name="exitCode">The exit code to return in case of cancellation.</param> | ||||
|     /// <returns>A configurator that can be used to configure the application further.</returns> | ||||
|     public static IConfigurator CancellationExitCode(this IConfigurator configurator, int exitCode) | ||||
|     { | ||||
|         if (configurator == null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(configurator)); | ||||
|         } | ||||
|  | ||||
|         configurator.Settings.CancellationExitCode = exitCode; | ||||
|         return configurator; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Configures case sensitivity. | ||||
|     /// </summary> | ||||
| @@ -304,14 +322,14 @@ public static class ConfiguratorExtensions | ||||
|     public static ICommandConfigurator AddDelegate( | ||||
|         this IConfigurator configurator, | ||||
|         string name, | ||||
|         Func<CommandContext, int> func) | ||||
|         Func<CommandContext, CancellationToken, int> func) | ||||
|     { | ||||
|         if (configurator == null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(configurator)); | ||||
|         } | ||||
|  | ||||
|         return configurator.AddDelegate<EmptyCommandSettings>(name, (c, _) => func(c)); | ||||
|         return configurator.AddDelegate<EmptyCommandSettings>(name, (c, _, ct) => func(c, ct)); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -324,14 +342,14 @@ public static class ConfiguratorExtensions | ||||
|     public static ICommandConfigurator AddAsyncDelegate( | ||||
|         this IConfigurator configurator, | ||||
|         string name, | ||||
|         Func<CommandContext, Task<int>> func) | ||||
|         Func<CommandContext, CancellationToken, Task<int>> func) | ||||
|     { | ||||
|         if (configurator == null) | ||||
|         { | ||||
|             throw new ArgumentNullException(nameof(configurator)); | ||||
|         } | ||||
|  | ||||
|         return configurator.AddAsyncDelegate<EmptyCommandSettings>(name, (c, _) => func(c)); | ||||
|         return configurator.AddAsyncDelegate<EmptyCommandSettings>(name, (c, _, ct) => func(c, ct)); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -345,7 +363,7 @@ public static class ConfiguratorExtensions | ||||
|     public static ICommandConfigurator AddDelegate<TSettings>( | ||||
|         this IConfigurator<TSettings>? configurator, | ||||
|         string name, | ||||
|         Func<CommandContext, int> func) | ||||
|         Func<CommandContext, CancellationToken, int> func) | ||||
|         where TSettings : CommandSettings | ||||
|     { | ||||
|         if (typeof(TSettings).IsAbstract) | ||||
| @@ -358,7 +376,7 @@ public static class ConfiguratorExtensions | ||||
|             throw new ArgumentNullException(nameof(configurator)); | ||||
|         } | ||||
|  | ||||
|         return configurator.AddDelegate<TSettings>(name, (c, _) => func(c)); | ||||
|         return configurator.AddDelegate<TSettings>(name, (c, _, ct) => func(c, ct)); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -372,7 +390,7 @@ public static class ConfiguratorExtensions | ||||
|     public static ICommandConfigurator AddAsyncDelegate<TSettings>( | ||||
|         this IConfigurator<TSettings> configurator, | ||||
|         string name, | ||||
|         Func<CommandContext, Task<int>> func) | ||||
|         Func<CommandContext, CancellationToken, Task<int>> func) | ||||
|         where TSettings : CommandSettings | ||||
|     { | ||||
|         if (configurator == null) | ||||
| @@ -380,7 +398,7 @@ public static class ConfiguratorExtensions | ||||
|             throw new ArgumentNullException(nameof(configurator)); | ||||
|         } | ||||
|  | ||||
|         return configurator.AddAsyncDelegate<TSettings>(name, (c, _) => func(c)); | ||||
|         return configurator.AddAsyncDelegate<TSettings>(name, (c, _, ct) => func(c, ct)); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -53,7 +53,7 @@ public class HelpProvider : IHelpProvider | ||||
|         { | ||||
|             var arguments = new List<HelpArgument>(); | ||||
|             arguments.AddRange(command?.Parameters?.OfType<ICommandArgument>()?.Select( | ||||
|                 x => new HelpArgument(x.Value, x.Position, x.Required, x.Description)) | ||||
|                                    x => new HelpArgument(x.Value, x.Position, x.IsRequired, x.Description)) | ||||
|                                ?? Array.Empty<HelpArgument>()); | ||||
|             return arguments; | ||||
|         } | ||||
| @@ -65,15 +65,20 @@ public class HelpProvider : IHelpProvider | ||||
|         public string? Long { get; } | ||||
|         public string? Value { get; } | ||||
|         public bool? ValueIsOptional { get; } | ||||
|         public bool IsRequired { get; } | ||||
|         public string? Description { get; } | ||||
|         public object? DefaultValue { get; } | ||||
|  | ||||
|         private HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue) | ||||
|         private HelpOption( | ||||
|             string? @short, string? @long, string? @value, | ||||
|             bool? valueIsOptional, bool isRequired, | ||||
|             string? description, object? defaultValue) | ||||
|         { | ||||
|             Short = @short; | ||||
|             Long = @long; | ||||
|             Value = value; | ||||
|             ValueIsOptional = valueIsOptional; | ||||
|             IsRequired = isRequired; | ||||
|             Description = description; | ||||
|             DefaultValue = defaultValue; | ||||
|         } | ||||
| @@ -85,7 +90,8 @@ public class HelpProvider : IHelpProvider | ||||
|         { | ||||
|             var parameters = new List<HelpOption> | ||||
|             { | ||||
|                 new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null), | ||||
|                 new HelpOption("h", "help", null, null, false, | ||||
|                     resources.PrintHelpDescription, null), | ||||
|             }; | ||||
|  | ||||
|             // Version information applies to the entire CLI application. | ||||
| @@ -107,7 +113,8 @@ public class HelpProvider : IHelpProvider | ||||
|                     // Only show the version option if there is an application version set. | ||||
|                     if (model.ApplicationVersion != null) | ||||
|                     { | ||||
|                         parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null)); | ||||
|                         parameters.Add(new HelpOption("v", "version", null, null, false, | ||||
|                             resources.PrintVersionDescription, null)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -115,7 +122,7 @@ public class HelpProvider : IHelpProvider | ||||
|             parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o => | ||||
|                                     new HelpOption( | ||||
|                                         o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(), | ||||
|                     o.ValueName, o.ValueIsOptional, o.Description, | ||||
|                                         o.ValueName, o.ValueIsOptional, o.IsRequired, o.Description, | ||||
|                                         o.IsFlag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value)) | ||||
|                                 ?? Array.Empty<HelpOption>()); | ||||
|             return parameters; | ||||
| @@ -215,7 +222,9 @@ public class HelpProvider : IHelpProvider | ||||
|                 { | ||||
|                     if (isCurrent) | ||||
|                     { | ||||
|                         parameters.Add(NewComposer().Style(helpStyles?.Usage?.CurrentCommand ?? Style.Plain, $"{current.Name}")); | ||||
|                         parameters.Add(NewComposer().Style( | ||||
|                             helpStyles?.Usage?.CurrentCommand ?? Style.Plain, | ||||
|                             $"{current.Name}")); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
| @@ -228,38 +237,46 @@ public class HelpProvider : IHelpProvider | ||||
|                     if (isCurrent) | ||||
|                     { | ||||
|                         foreach (var argument in current.Parameters.OfType<ICommandArgument>() | ||||
|                             .Where(a => a.Required).OrderBy(a => a.Position).ToArray()) | ||||
|                                      .Where(a => a.IsRequired).OrderBy(a => a.Position).ToArray()) | ||||
|                         { | ||||
|                             parameters.Add(NewComposer().Style(helpStyles?.Usage?.RequiredArgument ?? Style.Plain, $"<{argument.Value}>")); | ||||
|                             parameters.Add(NewComposer().Style( | ||||
|                                 helpStyles?.Usage?.RequiredArgument ?? Style.Plain, | ||||
|                                 $"<{argument.Value}>")); | ||||
|                         } | ||||
|                     } | ||||
|  | ||||
|                     var optionalArguments = current.Parameters.OfType<ICommandArgument>().Where(x => !x.Required).ToArray(); | ||||
|                     var optionalArguments = current.Parameters.OfType<ICommandArgument>().Where(x => !x.IsRequired) | ||||
|                         .ToArray(); | ||||
|                     if (optionalArguments.Length > 0 || !isCurrent) | ||||
|                     { | ||||
|                         foreach (var optionalArgument in optionalArguments) | ||||
|                         { | ||||
|                             parameters.Add(NewComposer().Style(helpStyles?.Usage?.OptionalArgument ?? Style.Plain, $"[{optionalArgument.Value}]")); | ||||
|                             parameters.Add(NewComposer().Style( | ||||
|                                 helpStyles?.Usage?.OptionalArgument ?? Style.Plain, | ||||
|                                 $"[{optionalArgument.Value}]")); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 if (isCurrent) | ||||
|                 { | ||||
|                     parameters.Add(NewComposer().Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]")); | ||||
|                     parameters.Add(NewComposer() | ||||
|                         .Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]")); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (command.IsBranch && command.DefaultCommand == null) | ||||
|             { | ||||
|                 // The user must specify the command | ||||
|                 parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.Command}>")); | ||||
|                 parameters.Add(NewComposer() | ||||
|                     .Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.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(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]")); | ||||
|                 parameters.Add(NewComposer() | ||||
|                     .Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]")); | ||||
|             } | ||||
|             else if (command.IsDefaultCommand) | ||||
|             { | ||||
| @@ -269,7 +286,8 @@ public class HelpProvider : IHelpProvider | ||||
|                 { | ||||
|                     // Commands other than the default are present | ||||
|                     // So make these optional in the usage statement | ||||
|                     parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]")); | ||||
|                     parameters.Add(NewComposer() | ||||
|                         .Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]")); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
| @@ -338,7 +356,8 @@ public class HelpProvider : IHelpProvider | ||||
|             for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++) | ||||
|             { | ||||
|                 var args = string.Join(" ", examples[index]); | ||||
|                 composer.Tab().Text(model.ApplicationName).Space().Style(helpStyles?.Examples?.Arguments ?? Style.Plain, args); | ||||
|                 composer.Tab().Text(model.ApplicationName).Space() | ||||
|                     .Style(helpStyles?.Examples?.Arguments ?? Style.Plain, args); | ||||
|                 composer.LineBreak(); | ||||
|             } | ||||
|  | ||||
| @@ -364,7 +383,8 @@ public class HelpProvider : IHelpProvider | ||||
|  | ||||
|         var result = new List<IRenderable> | ||||
|         { | ||||
|             NewComposer().LineBreak().Style(helpStyles?.Arguments?.Header ?? Style.Plain, $"{resources.Arguments}:").LineBreak(), | ||||
|             NewComposer().LineBreak().Style(helpStyles?.Arguments?.Header ?? Style.Plain, $"{resources.Arguments}:") | ||||
|                 .LineBreak(), | ||||
|         }; | ||||
|  | ||||
|         var grid = new Grid(); | ||||
| @@ -407,7 +427,8 @@ public class HelpProvider : IHelpProvider | ||||
|  | ||||
|         var result = new List<IRenderable> | ||||
|         { | ||||
|             NewComposer().LineBreak().Style(helpStyles?.Options?.Header ?? Style.Plain, $"{resources.Options}:").LineBreak(), | ||||
|             NewComposer().LineBreak().Style(helpStyles?.Options?.Header ?? Style.Plain, $"{resources.Options}:") | ||||
|                 .LineBreak(), | ||||
|         }; | ||||
|  | ||||
|         var helpOptions = parameters.ToArray(); | ||||
| @@ -439,7 +460,15 @@ public class HelpProvider : IHelpProvider | ||||
|                 columns.Add(GetDefaultValueForOption(option.DefaultValue)); | ||||
|             } | ||||
|  | ||||
|             columns.Add(NewComposer().Text(NormalizeDescription(option.Description))); | ||||
|             var description = option.Description; | ||||
|             if (option.IsRequired) | ||||
|             { | ||||
|                 description = string.IsNullOrWhiteSpace(description) | ||||
|                     ? "[i]Required[/]" | ||||
|                     : description.TrimEnd('.') + ". [i]Required[/]"; | ||||
|             } | ||||
|  | ||||
|             columns.Add(NewComposer().Text(NormalizeDescription(description))); | ||||
|  | ||||
|             grid.AddRow(columns.ToArray()); | ||||
|         } | ||||
| @@ -470,7 +499,8 @@ public class HelpProvider : IHelpProvider | ||||
|  | ||||
|         var result = new List<IRenderable> | ||||
|         { | ||||
|             NewComposer().LineBreak().Style(helpStyles?.Commands?.Header ?? Style.Plain, $"{resources.Commands}:").LineBreak(), | ||||
|             NewComposer().LineBreak().Style(helpStyles?.Commands?.Header ?? Style.Plain, $"{resources.Commands}:") | ||||
|                 .LineBreak(), | ||||
|         }; | ||||
|  | ||||
|         var grid = new Grid(); | ||||
| @@ -546,11 +576,11 @@ public class HelpProvider : IHelpProvider | ||||
|             composer.Text(" "); | ||||
|             if (option.ValueIsOptional ?? false) | ||||
|             { | ||||
|                 composer.Style(helpStyles?.Options?.OptionalOption ?? Style.Plain, $"[{option.Value}]"); | ||||
|                 composer.Style(helpStyles?.Options?.OptionalOptionValue ?? Style.Plain, $"[{option.Value}]"); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 composer.Style(helpStyles?.Options?.RequiredOption ?? Style.Plain, $"<{option.Value}>"); | ||||
|                 composer.Style(helpStyles?.Options?.RequiredOptionValue ?? Style.Plain, $"<{option.Value}>"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -564,8 +594,15 @@ public class HelpProvider : IHelpProvider | ||||
|             null => NewComposer().Text(" "), | ||||
|             "" => NewComposer().Text(" "), | ||||
|             Array { Length: 0 } => NewComposer().Text(" "), | ||||
|             Array array => NewComposer().Join(", ", array.Cast<object>().Select(o => NewComposer().Style(helpStyles?.Options?.DefaultValue ?? Style.Plain, o.ToString() ?? string.Empty))), | ||||
|             _ => NewComposer().Style(helpStyles?.Options?.DefaultValue ?? Style.Plain, defaultValue?.ToString() ?? string.Empty), | ||||
|             Array array => NewComposer().Join( | ||||
|                 ", ", | ||||
|                 array.Cast<object>().Select(o => | ||||
|                     NewComposer().Style( | ||||
|                         helpStyles?.Options?.DefaultValue ?? Style.Plain, | ||||
|                         o.ToString() ?? string.Empty))), | ||||
|             _ => NewComposer().Style( | ||||
|                 helpStyles?.Options?.DefaultValue ?? Style.Plain, | ||||
|                 defaultValue?.ToString() ?? string.Empty), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -76,8 +76,8 @@ public sealed class HelpProviderStyle | ||||
|                 Header = "yellow", | ||||
|                 DefaultValueHeader = "lime", | ||||
|                 DefaultValue = "bold", | ||||
|                 RequiredOption = "silver", | ||||
|                 OptionalOption = "grey", | ||||
|                 RequiredOptionValue = "silver", | ||||
|                 OptionalOptionValue = "grey", | ||||
|             }, | ||||
|         }; | ||||
| } | ||||
| @@ -212,8 +212,13 @@ public sealed class OptionStyle | ||||
|     /// </summary> | ||||
|     public Style? RequiredOption { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets the style for required option values. | ||||
|     /// </summary> | ||||
|     public Style? RequiredOptionValue { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets the style for optional options. | ||||
|     /// </summary> | ||||
|     public Style? OptionalOption { get; set; } | ||||
|     public Style? OptionalOptionValue { get; set; } | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,7 @@ public interface ICommandParameter | ||||
|     /// <summary> | ||||
|     /// Gets a value indicating whether the parameter is required. | ||||
|     /// </summary> | ||||
|     bool Required { get; } | ||||
|     bool IsRequired { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the description of the parameter. | ||||
|   | ||||
| @@ -18,6 +18,7 @@ public interface ICommand | ||||
|     /// </summary> | ||||
|     /// <param name="context">The command context.</param> | ||||
|     /// <param name="settings">The settings.</param> | ||||
|     /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param> | ||||
|     /// <returns>The validation result.</returns> | ||||
|     Task<int> Execute(CommandContext context, CommandSettings settings); | ||||
|     Task<int> ExecuteAsync(CommandContext context, CommandSettings settings, CancellationToken cancellationToken); | ||||
| } | ||||
| @@ -15,13 +15,15 @@ public interface ICommandApp | ||||
|     /// Runs the command line application with specified arguments. | ||||
|     /// </summary> | ||||
|     /// <param name="args">The arguments.</param> | ||||
|     /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the application.</param> | ||||
|     /// <returns>The exit code from the executed command.</returns> | ||||
|     int Run(IEnumerable<string> args); | ||||
|     int Run(IEnumerable<string> args, CancellationToken cancellationToken = default); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Runs the command line application with specified arguments. | ||||
|     /// </summary> | ||||
|     /// <param name="args">The arguments.</param> | ||||
|     /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the application.</param> | ||||
|     /// <returns>The exit code from the executed command.</returns> | ||||
|     Task<int> RunAsync(IEnumerable<string> args); | ||||
|     Task<int> RunAsync(IEnumerable<string> args, CancellationToken cancellationToken = default); | ||||
| } | ||||
| @@ -88,6 +88,12 @@ public interface ICommandAppSettings | ||||
|     /// </summary> | ||||
|     bool PropagateExceptions { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets the value used as the application exit code when it's aborted through the <see cref="CancellationToken"/>. | ||||
|     /// The default cancellation exit code is 130. | ||||
|     /// </summary> | ||||
|     int CancellationExitCode { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets a value indicating whether or not examples should be validated. | ||||
|     /// </summary> | ||||
|   | ||||
| @@ -12,6 +12,7 @@ public interface ICommand<TSettings> : ICommandLimiter<TSettings> | ||||
|     /// </summary> | ||||
|     /// <param name="context">The command context.</param> | ||||
|     /// <param name="settings">The settings.</param> | ||||
|     /// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param> | ||||
|     /// <returns>An integer indicating whether or not the command executed successfully.</returns> | ||||
|     Task<int> Execute(CommandContext context, TSettings settings); | ||||
|     Task<int> ExecuteAsync(CommandContext context, TSettings settings, CancellationToken cancellationToken); | ||||
| } | ||||
| @@ -48,7 +48,7 @@ public interface IConfigurator | ||||
|     /// <param name="name">The name of the command.</param> | ||||
|     /// <param name="func">The delegate to execute as part of command execution.</param> | ||||
|     /// <returns>A command configurator that can be used to configure the command further.</returns> | ||||
|     ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, int> func) | ||||
|     ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, CancellationToken, int> func) | ||||
|         where TSettings : CommandSettings; | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -58,7 +58,7 @@ public interface IConfigurator | ||||
|     /// <param name="name">The name of the command.</param> | ||||
|     /// <param name="func">The delegate to execute as part of command execution.</param> | ||||
|     /// <returns>A command configurator that can be used to configure the command further.</returns> | ||||
|     ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContext, TSettings, Task<int>> func) | ||||
|     ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContext, TSettings, CancellationToken, Task<int>> func) | ||||
|         where TSettings : CommandSettings; | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -54,7 +54,7 @@ public interface IConfigurator<in TSettings> | ||||
|     /// <param name="name">The name of the command.</param> | ||||
|     /// <param name="func">The delegate to execute as part of command execution.</param> | ||||
|     /// <returns>A command configurator that can be used to configure the command further.</returns> | ||||
|     ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func) | ||||
|     ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, CancellationToken, int> func) | ||||
|         where TDerivedSettings : TSettings; | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -64,7 +64,7 @@ public interface IConfigurator<in TSettings> | ||||
|     /// <param name="name">The name of the command.</param> | ||||
|     /// <param name="func">The delegate to execute as part of command execution.</param> | ||||
|     /// <returns>A command configurator that can be used to configure the command further.</returns> | ||||
|     ICommandConfigurator AddAsyncDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, Task<int>> func) | ||||
|     ICommandConfigurator AddAsyncDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, CancellationToken, Task<int>> func) | ||||
|         where TDerivedSettings : TSettings; | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -12,7 +12,7 @@ internal sealed class CommandExecutor | ||||
|         _registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor)); | ||||
|     } | ||||
|  | ||||
|     public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args) | ||||
|     public async Task<int> ExecuteAsync(IConfiguration configuration, IEnumerable<string> args, CancellationToken cancellationToken) | ||||
|     { | ||||
|         CommandTreeParserResult parsedResult; | ||||
|  | ||||
| @@ -68,6 +68,13 @@ internal sealed class CommandExecutor | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             // OpenCLI? | ||||
|             if (firstArgument.Equals(CliConstants.DumpHelpOpenCliOption, StringComparison.OrdinalIgnoreCase)) | ||||
|             { | ||||
|                 // Replace all arguments with the opencligen command | ||||
|                 arguments = ["cli", "opencli"]; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         // Parse and map the model against the arguments. | ||||
| @@ -103,7 +110,7 @@ internal sealed class CommandExecutor | ||||
|             } | ||||
|  | ||||
|             // Is this the default and is it called without arguments when there are required arguments? | ||||
|             if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.Required)) | ||||
|             if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.IsRequired)) | ||||
|             { | ||||
|                 // Display help for default command. | ||||
|                 configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command)); | ||||
| @@ -118,7 +125,7 @@ internal sealed class CommandExecutor | ||||
|                 leaf.Command.Data); | ||||
|  | ||||
|             // Execute the command tree. | ||||
|             return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false); | ||||
|             return await ExecuteAsync(leaf, parsedResult.Tree, context, resolver, configuration, cancellationToken).ConfigureAwait(false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -215,12 +222,13 @@ internal sealed class CommandExecutor | ||||
|         return (parsedResult, tokenizerResult); | ||||
|     } | ||||
|  | ||||
|     private static async Task<int> Execute( | ||||
|     private static async Task<int> ExecuteAsync( | ||||
|         CommandTree leaf, | ||||
|         CommandTree tree, | ||||
|         CommandContext context, | ||||
|         ITypeResolver resolver, | ||||
|         IConfiguration configuration) | ||||
|         IConfiguration configuration, | ||||
|         CancellationToken cancellationToken) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
| @@ -249,7 +257,7 @@ internal sealed class CommandExecutor | ||||
|             } | ||||
|  | ||||
|             // Execute the command. | ||||
|             var result = await command.Execute(context, settings); | ||||
|             var result = await command.ExecuteAsync(context, settings, cancellationToken); | ||||
|             foreach (var interceptor in interceptors) | ||||
|             { | ||||
|                 interceptor.InterceptResult(context, settings, ref result); | ||||
|   | ||||
| @@ -9,12 +9,14 @@ internal static class CommandValidator | ||||
|         { | ||||
|             foreach (var parameter in node.Unmapped) | ||||
|             { | ||||
|                 if (parameter.Required) | ||||
|                 if (parameter.IsRequired) | ||||
|                 { | ||||
|                     switch (parameter) | ||||
|                     { | ||||
|                         case CommandArgument argument: | ||||
|                             throw CommandRuntimeException.MissingRequiredArgument(node, argument); | ||||
|                         case CommandOption option: | ||||
|                             throw CommandRuntimeException.MissingRequiredOption(node, option); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ namespace Spectre.Console.Cli; | ||||
|  | ||||
| [Description("Displays diagnostics about CLI configurations")] | ||||
| [SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")] | ||||
| internal sealed class ExplainCommand : Command<ExplainCommand.Settings> | ||||
| internal sealed class ExplainCommand : Command<ExplainCommand.Settings>, IBuiltInCommand | ||||
| { | ||||
|     private readonly CommandModel _commandModel; | ||||
|     private readonly IAnsiConsole _writer; | ||||
| @@ -27,7 +27,7 @@ internal sealed class ExplainCommand : Command<ExplainCommand.Settings> | ||||
|         public bool IncludeHidden { get; set; } | ||||
|     } | ||||
|  | ||||
|     public override int Execute(CommandContext context, Settings settings) | ||||
|     public override int Execute(CommandContext context, Settings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         var tree = new Tree("CLI Configuration"); | ||||
|         tree.AddNode(ValueMarkup("Application Name", _commandModel.ApplicationName, "no application name")); | ||||
| @@ -212,7 +212,7 @@ internal sealed class ExplainCommand : Command<ExplainCommand.Settings> | ||||
|             parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value)); | ||||
|         } | ||||
|  | ||||
|         parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString())); | ||||
|         parameterNode.AddNode(ValueMarkup("Required", parameter.IsRequired.ToString())); | ||||
|  | ||||
|         if (parameter.Converter != null) | ||||
|         { | ||||
|   | ||||
| @@ -0,0 +1,9 @@ | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| /// <summary> | ||||
| /// Represents a built-in command. | ||||
| /// Used as a marker interface. | ||||
| /// </summary> | ||||
| internal interface IBuiltInCommand : ICommand | ||||
| { | ||||
| } | ||||
| @@ -0,0 +1,224 @@ | ||||
| using OpenCli; | ||||
|  | ||||
| namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal sealed class OpenCliGeneratorCommand : Command, IBuiltInCommand | ||||
| { | ||||
|     private readonly IConfiguration _configuration; | ||||
|     private readonly CommandModel _model; | ||||
|  | ||||
|     public OpenCliGeneratorCommand(IConfiguration configuration, CommandModel model) | ||||
|     { | ||||
|         _configuration = configuration; | ||||
|         _model = model ?? throw new ArgumentNullException(nameof(model)); | ||||
|     } | ||||
|  | ||||
|     public override int Execute(CommandContext context, CancellationToken cancellationToken) | ||||
|     { | ||||
|         var document = new OpenCliDocument | ||||
|         { | ||||
|             OpenCli = "0.1-draft", | ||||
|             Info = new OpenCliInfo | ||||
|             { | ||||
|                 Title = ((ICommandModel)_model).ApplicationName, Version = _model.ApplicationVersion ?? "1.0", | ||||
|             }, | ||||
|             Commands = CreateCommands(_model.Commands), | ||||
|             Arguments = CreateArguments(_model.DefaultCommand?.GetArguments()), | ||||
|             Options = CreateOptions(_model.DefaultCommand?.GetOptions()), | ||||
|         }; | ||||
|  | ||||
|         var writer = _configuration.Settings.Console.GetConsole(); | ||||
|         writer.WriteLine(document.Write()); | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     private List<OpenCliCommand> CreateCommands(IList<CommandInfo> commands) | ||||
|     { | ||||
|         var result = new List<OpenCliCommand>(); | ||||
|  | ||||
|         foreach (var command in commands.OrderBy(o => o.Name, StringComparer.OrdinalIgnoreCase)) | ||||
|         { | ||||
|             if (typeof(IBuiltInCommand).IsAssignableFrom(command.CommandType)) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             var openCliCommand = new OpenCliCommand | ||||
|             { | ||||
|                 Name = command.Name, | ||||
|                 Aliases = | ||||
|                 [ | ||||
|                     ..command.Aliases.OrderBy(str => str) | ||||
|                 ], | ||||
|                 Commands = CreateCommands(command.Children), | ||||
|                 Arguments = CreateArguments(command.GetArguments()), | ||||
|                 Options = CreateOptions(command.GetOptions()), | ||||
|                 Description = command.Description, | ||||
|                 Hidden = command.IsHidden, | ||||
|                 Examples = [..command.Examples.Select(example => string.Join(" ", example))], | ||||
|             }; | ||||
|  | ||||
|             // Skip branches without commands | ||||
|             if (command.IsBranch && openCliCommand.Commands.Count == 0) | ||||
|             { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             result.Add(openCliCommand); | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private List<OpenCliArgument> CreateArguments(IEnumerable<CommandArgument>? arguments) | ||||
|     { | ||||
|         var result = new List<OpenCliArgument>(); | ||||
|  | ||||
|         if (arguments == null) | ||||
|         { | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         foreach (var argument in arguments.OrderBy(x => x.Position)) | ||||
|         { | ||||
|             var metadata = default(List<OpenCliMetadata>); | ||||
|             if (argument.ParameterType != typeof(void) && | ||||
|                 argument.ParameterType != typeof(bool)) | ||||
|             { | ||||
|                 metadata = | ||||
|                 [ | ||||
|                     new OpenCliMetadata { Name = "ClrType", Value = argument.ParameterType.ToCliTypeString(), }, | ||||
|                 ]; | ||||
|             } | ||||
|  | ||||
|             result.Add(new OpenCliArgument | ||||
|             { | ||||
|                 Name = argument.Value, | ||||
|                 Required = argument.IsRequired, | ||||
|                 Arity = new OpenCliArity | ||||
|                 { | ||||
|                     // TODO: Look into this | ||||
|                     Minimum = 1, | ||||
|                     Maximum = 1, | ||||
|                 }, | ||||
|                 Description = argument.Description, | ||||
|                 Hidden = argument.IsHidden, | ||||
|                 Metadata = metadata, | ||||
|                 AcceptedValues = null, | ||||
|                 Group = null, | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private List<OpenCliOption> CreateOptions(IEnumerable<CommandOption>? options) | ||||
|     { | ||||
|         var result = new List<OpenCliOption>(); | ||||
|  | ||||
|         if (options == null) | ||||
|         { | ||||
|             return result; | ||||
|         } | ||||
|  | ||||
|         foreach (var option in options.OrderBy(o => o.GetOptionName(), StringComparer.OrdinalIgnoreCase)) | ||||
|         { | ||||
|             var arguments = new List<OpenCliArgument>(); | ||||
|             if (option.ParameterType != typeof(void) && | ||||
|                 option.ParameterType != typeof(bool)) | ||||
|             { | ||||
|                 arguments.Add(new OpenCliArgument | ||||
|                 { | ||||
|                     Name = option.ValueName ?? "VALUE", | ||||
|                     Required = !option.ValueIsOptional, | ||||
|                     Arity = new OpenCliArity | ||||
|                     { | ||||
|                         // TODO: Look into this | ||||
|                         Minimum = option.ValueIsOptional | ||||
|                             ? 0 | ||||
|                             : 1, | ||||
|                         Maximum = 1, | ||||
|                     }, | ||||
|                     AcceptedValues = null, | ||||
|                     Group = null, | ||||
|                     Hidden = null, | ||||
|                     Metadata = | ||||
|                     [ | ||||
|                         new OpenCliMetadata | ||||
|                         { | ||||
|                             Name = "ClrType", | ||||
|                             Value = option.ParameterType.ToCliTypeString(), | ||||
|                         }, | ||||
|                     ], | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             var optionMetadata = default(List<OpenCliMetadata>); | ||||
|             if (arguments.Count == 0 && option.ParameterType != typeof(void) && | ||||
|                 option.ParameterType != typeof(bool)) | ||||
|             { | ||||
|                 optionMetadata = | ||||
|                 [ | ||||
|                     new OpenCliMetadata { Name = "ClrType", Value = option.ParameterType.ToCliTypeString(), }, | ||||
|                 ]; | ||||
|             } | ||||
|  | ||||
|             var (optionName, optionAliases) = GetOptionNames(option); | ||||
|             result.Add(new OpenCliOption | ||||
|             { | ||||
|                 Name = optionName, | ||||
|                 Required = option.IsRequired, | ||||
|                 Aliases = [..optionAliases.OrderBy(str => str)], | ||||
|                 Arguments = arguments, | ||||
|                 Description = option.Description, | ||||
|                 Group = null, | ||||
|                 Hidden = option.IsHidden, | ||||
|                 Recursive = option.IsShadowed, // TODO: Is this correct? | ||||
|                 Metadata = optionMetadata, | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
|  | ||||
|     private static (string Name, HashSet<string> Aliases) GetOptionNames(CommandOption option) | ||||
|     { | ||||
|         var name = GetOptionName(option); | ||||
|         var aliases = new HashSet<string>(); | ||||
|  | ||||
|         if (option.LongNames.Count > 0) | ||||
|         { | ||||
|             foreach (var alias in option.LongNames.Skip(1)) | ||||
|             { | ||||
|                 aliases.Add("--" + alias); | ||||
|             } | ||||
|  | ||||
|             foreach (var alias in option.ShortNames) | ||||
|             { | ||||
|                 aliases.Add("-" + alias); | ||||
|             } | ||||
|         } | ||||
|         else | ||||
|         { | ||||
|             foreach (var alias in option.LongNames) | ||||
|             { | ||||
|                 aliases.Add("--" + alias); | ||||
|             } | ||||
|  | ||||
|             foreach (var alias in option.ShortNames.Skip(1)) | ||||
|             { | ||||
|                 aliases.Add("-" + alias); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return (name, aliases); | ||||
|     } | ||||
|  | ||||
|     private static string GetOptionName(CommandOption option) | ||||
|     { | ||||
|         return option.LongNames.Count > 0 | ||||
|             ? "--" + option.LongNames[0] | ||||
|             : "-" + option.ShortNames[0]; | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,7 @@ namespace Spectre.Console.Cli; | ||||
|  | ||||
| [Description("Displays the CLI library version")] | ||||
| [SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")] | ||||
| internal sealed class VersionCommand : Command<VersionCommand.Settings> | ||||
| internal sealed class VersionCommand : Command, IBuiltInCommand | ||||
| { | ||||
|     private readonly IAnsiConsole _writer; | ||||
|  | ||||
| @@ -11,11 +11,7 @@ internal sealed class VersionCommand : Command<VersionCommand.Settings> | ||||
|         _writer = configuration.Settings.Console.GetConsole(); | ||||
|     } | ||||
|  | ||||
|     public sealed class Settings : CommandSettings | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     public override int Execute(CommandContext context, Settings settings) | ||||
|     public override int Execute(CommandContext context, CancellationToken cancellationToken) | ||||
|     { | ||||
|         _writer.MarkupLine( | ||||
|             "[yellow]Spectre.Cli[/] version [aqua]{0}[/]", | ||||
|   | ||||
| @@ -2,7 +2,7 @@ namespace Spectre.Console.Cli; | ||||
|  | ||||
| [Description("Generates an XML representation of the CLI configuration.")] | ||||
| [SuppressMessage("Performance", "CA1812: Avoid uninstantiated internal classes")] | ||||
| internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings> | ||||
| internal sealed class XmlDocCommand : Command, IBuiltInCommand | ||||
| { | ||||
|     private readonly CommandModel _model; | ||||
|     private readonly IAnsiConsole _writer; | ||||
| @@ -13,11 +13,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings> | ||||
|         _writer = configuration.Settings.Console.GetConsole(); | ||||
|     } | ||||
|  | ||||
|     public sealed class Settings : CommandSettings | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     public override int Execute(CommandContext context, Settings settings) | ||||
|     public override int Execute(CommandContext context, CancellationToken cancellationToken) | ||||
|     { | ||||
|         _writer.Write(Serialize(_model), Style.Plain); | ||||
|         return 0; | ||||
| @@ -142,7 +138,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings> | ||||
|             var node = document.CreateElement("Argument"); | ||||
|             node.SetNullableAttribute("Name", argument.Value); | ||||
|             node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture)); | ||||
|             node.SetBooleanAttribute("Required", argument.Required); | ||||
|             node.SetBooleanAttribute("Required", argument.IsRequired); | ||||
|             node.SetEnumAttribute("Kind", argument.ParameterKind); | ||||
|             node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName); | ||||
|  | ||||
| @@ -186,7 +182,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings> | ||||
|             node.SetNullableAttribute("Short", option.ShortNames); | ||||
|             node.SetNullableAttribute("Long", option.LongNames); | ||||
|             node.SetNullableAttribute("Value", option.ValueName); | ||||
|             node.SetBooleanAttribute("Required", option.Required); | ||||
|             node.SetBooleanAttribute("Required", option.IsRequired); | ||||
|             node.SetEnumAttribute("Kind", option.ParameterKind); | ||||
|             node.SetNullableAttribute("ClrType", option.ParameterType?.FullName); | ||||
|  | ||||
|   | ||||
| @@ -18,6 +18,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings | ||||
|     public HelpProviderStyle? HelpProviderStyles { get; set; } | ||||
|     public bool StrictParsing { get; set; } | ||||
|     public bool ConvertFlagsToRemainingArguments { get; set; } | ||||
|     public int CancellationExitCode { get; set; } | ||||
|  | ||||
|     public ParsingMode ParsingMode => | ||||
|         StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed; | ||||
| @@ -33,6 +34,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings | ||||
|         TrimTrailingPeriod = true; | ||||
|         HelpProviderStyles = HelpProviderStyle.Default; | ||||
|         ConvertFlagsToRemainingArguments = false; | ||||
|         CancellationExitCode = 130; | ||||
|     } | ||||
|  | ||||
|     public bool IsTrue(Func<CommandAppSettings, bool> func, string environmentVariableName) | ||||
|   | ||||
| @@ -56,19 +56,19 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig | ||||
|         return new CommandConfigurator(command); | ||||
|     } | ||||
|  | ||||
|     public ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, int> func) | ||||
|     public ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, CancellationToken, int> func) | ||||
|         where TSettings : CommandSettings | ||||
|     { | ||||
|         var command = Commands.AddAndReturn(ConfiguredCommand.FromDelegate<TSettings>( | ||||
|             name, (context, settings) => Task.FromResult(func(context, (TSettings)settings)))); | ||||
|             name, (context, settings, cancellationToken) => Task.FromResult(func(context, (TSettings)settings, cancellationToken)))); | ||||
|         return new CommandConfigurator(command); | ||||
|     } | ||||
|  | ||||
|     public ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContext, TSettings, Task<int>> func) | ||||
|     public ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContext, TSettings, CancellationToken, Task<int>> func) | ||||
|         where TSettings : CommandSettings | ||||
|     { | ||||
|         var command = Commands.AddAndReturn(ConfiguredCommand.FromDelegate<TSettings>( | ||||
|             name, (context, settings) => func(context, (TSettings)settings))); | ||||
|             name, (context, settings, cancellationToken) => func(context, (TSettings)settings, cancellationToken))); | ||||
|         return new CommandConfigurator(command); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -46,21 +46,21 @@ internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConf | ||||
|         return configurator; | ||||
|     } | ||||
|  | ||||
|     public ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func) | ||||
|     public ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, CancellationToken, int> func) | ||||
|         where TDerivedSettings : TSettings | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromDelegate<TDerivedSettings>( | ||||
|             name, (context, settings) => Task.FromResult(func(context, (TDerivedSettings)settings))); | ||||
|             name, (context, settings, cancellationToken) => Task.FromResult(func(context, (TDerivedSettings)settings, cancellationToken))); | ||||
|  | ||||
|         _command.Children.Add(command); | ||||
|         return new CommandConfigurator(command); | ||||
|     } | ||||
|  | ||||
|     public ICommandConfigurator AddAsyncDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, Task<int>> func) | ||||
|     public ICommandConfigurator AddAsyncDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, CancellationToken, Task<int>> func) | ||||
|         where TDerivedSettings : TSettings | ||||
|     { | ||||
|         var command = ConfiguredCommand.FromDelegate<TDerivedSettings>( | ||||
|             name, (context, settings) => func(context, (TDerivedSettings)settings)); | ||||
|             name, (context, settings, cancellationToken) => func(context, (TDerivedSettings)settings, cancellationToken)); | ||||
|  | ||||
|         _command.Children.Add(command); | ||||
|         return new CommandConfigurator(command); | ||||
|   | ||||
| @@ -8,7 +8,7 @@ internal sealed class ConfiguredCommand | ||||
|     public object? Data { get; set; } | ||||
|     public Type? CommandType { get; } | ||||
|     public Type SettingsType { get; } | ||||
|     public Func<CommandContext, CommandSettings, Task<int>>? Delegate { get; } | ||||
|     public Func<CommandContext, CommandSettings, CancellationToken, Task<int>>? Delegate { get; } | ||||
|     public bool IsDefaultCommand { get; } | ||||
|     public bool IsHidden { get; set; } | ||||
|  | ||||
| @@ -19,7 +19,7 @@ internal sealed class ConfiguredCommand | ||||
|         string name, | ||||
|         Type? commandType, | ||||
|         Type settingsType, | ||||
|         Func<CommandContext, CommandSettings, Task<int>>? @delegate, | ||||
|         Func<CommandContext, CommandSettings, CancellationToken, Task<int>>? @delegate, | ||||
|         bool isDefaultCommand) | ||||
|     { | ||||
|         Name = name; | ||||
| @@ -60,7 +60,7 @@ internal sealed class ConfiguredCommand | ||||
|     } | ||||
|  | ||||
|     public static ConfiguredCommand FromDelegate<TSettings>( | ||||
|         string name, Func<CommandContext, CommandSettings, Task<int>>? @delegate = null) | ||||
|         string name, Func<CommandContext, CommandSettings, CancellationToken, Task<int>>? @delegate = null) | ||||
|         where TSettings : CommandSettings | ||||
|     { | ||||
|         return new ConfiguredCommand(name, null, typeof(TSettings), @delegate, false); | ||||
|   | ||||
| @@ -5,12 +5,12 @@ internal static class TemplateParser | ||||
|     public sealed class ArgumentResult | ||||
|     { | ||||
|         public string Value { get; set; } | ||||
|         public bool Required { get; set; } | ||||
|         public bool IsRequired { get; set; } | ||||
|  | ||||
|         public ArgumentResult(string value, bool required) | ||||
|         public ArgumentResult(string value, bool isRequired) | ||||
|         { | ||||
|             Value = value; | ||||
|             Required = required; | ||||
|             IsRequired = isRequired; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ internal static class CliConstants | ||||
|     public const string DefaultCommandName = "__default_command"; | ||||
|     public const string True = "true"; | ||||
|     public const string False = "false"; | ||||
|     public const string DumpHelpOpenCliOption = "--help-dump-opencli"; | ||||
|  | ||||
|     public static string[] AcceptedBooleanValues { get; } = new string[] | ||||
|     { | ||||
| @@ -18,5 +19,6 @@ internal static class CliConstants | ||||
|         public const string Version = "version"; | ||||
|         public const string XmlDoc = "xmldoc"; | ||||
|         public const string Explain = "explain"; | ||||
|         public const string OpenCli = "opencli"; | ||||
|     } | ||||
| } | ||||
| @@ -2,16 +2,16 @@ namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal sealed class DelegateCommand : ICommand | ||||
| { | ||||
|     private readonly Func<CommandContext, CommandSettings, Task<int>> _func; | ||||
|     private readonly Func<CommandContext, CommandSettings, CancellationToken, Task<int>> _func; | ||||
|  | ||||
|     public DelegateCommand(Func<CommandContext, CommandSettings, Task<int>> func) | ||||
|     public DelegateCommand(Func<CommandContext, CommandSettings, CancellationToken, Task<int>> func) | ||||
|     { | ||||
|         _func = func; | ||||
|     } | ||||
|  | ||||
|     public Task<int> Execute(CommandContext context, CommandSettings settings) | ||||
|     public Task<int> ExecuteAsync(CommandContext context, CommandSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return _func(context, settings); | ||||
|         return _func(context, settings, cancellationToken); | ||||
|     } | ||||
|  | ||||
|     public ValidationResult Validate(CommandContext context, CommandSettings settings) | ||||
|   | ||||
| @@ -0,0 +1,13 @@ | ||||
| #if NETSTANDARD2_0 | ||||
| namespace System.IO; | ||||
|  | ||||
| // Polyfills needed for OpenCli. | ||||
| // This can be removed once me migrate over to the Polyfill library. | ||||
| internal static class OpenCliExtensions | ||||
| { | ||||
|     public static Task<string> ReadToEndAsync(this StreamReader reader, CancellationToken cancellationToken) | ||||
|     { | ||||
|         return reader.ReadToEndAsync(); | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| @@ -16,4 +16,19 @@ internal static class TypeExtensions | ||||
|  | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     // Taken from https://github.com/dotnet/sdk/blob/main/src/Cli/Microsoft.DotNet.Cli.Utils/Extensions/TypeExtensions.cs#L15 | ||||
|     // Licensed under MIT | ||||
|     public static string ToCliTypeString(this Type type) | ||||
|     { | ||||
|         var typeName = type.FullName ?? string.Empty; | ||||
|         if (!type.IsGenericType) | ||||
|         { | ||||
|             return typeName; | ||||
|         } | ||||
|  | ||||
|         var genericTypeName = typeName.Substring(0, typeName.IndexOf('`')); | ||||
|         var genericTypes = string.Join(", ", type.GenericTypeArguments.Select(generic => generic.ToCliTypeString())); | ||||
|         return $"{genericTypeName}<{genericTypes}>"; | ||||
|     } | ||||
| } | ||||
| @@ -8,7 +8,7 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo | ||||
|     public object? Data { get; } | ||||
|     public Type? CommandType { get; } | ||||
|     public Type SettingsType { get; } | ||||
|     public Func<CommandContext, CommandSettings, Task<int>>? Delegate { get; } | ||||
|     public Func<CommandContext, CommandSettings, CancellationToken, Task<int>>? Delegate { get; } | ||||
|     public bool IsDefaultCommand { get; } | ||||
|     public CommandInfo? Parent { get; } | ||||
|     public IList<CommandInfo> Children { get; } | ||||
|   | ||||
| @@ -2,6 +2,16 @@ namespace Spectre.Console.Cli; | ||||
|  | ||||
| internal static class CommandInfoExtensions | ||||
| { | ||||
|     public static IEnumerable<CommandArgument>? GetArguments(this CommandInfo? command) | ||||
|     { | ||||
|         return command?.Parameters.OfType<CommandArgument>(); | ||||
|     } | ||||
|  | ||||
|     public static IEnumerable<CommandOption>? GetOptions(this CommandInfo? command) | ||||
|     { | ||||
|         return command?.Parameters.OfType<CommandOption>(); | ||||
|     } | ||||
|  | ||||
|     public static bool HaveParentWithOption(this CommandInfo command, CommandOption option) | ||||
|     { | ||||
|         var parent = command?.Parent; | ||||
|   | ||||
| @@ -86,7 +86,7 @@ internal static class CommandModelValidator | ||||
|         // Arguments | ||||
|         foreach (var argument in arguments) | ||||
|         { | ||||
|             if (argument.Required && argument.DefaultValue != null) | ||||
|             if (argument.IsRequired && argument.DefaultValue != null) | ||||
|             { | ||||
|                 throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument); | ||||
|             } | ||||
|   | ||||
| @@ -15,7 +15,8 @@ internal sealed class CommandOption : CommandParameter, ICommandOption | ||||
|         IEnumerable<ParameterValidationAttribute> validators, | ||||
|         DefaultValueAttribute? defaultValue, bool valueIsOptional) | ||||
|         : base(parameterType, parameterKind, property, description, converter, | ||||
|                   defaultValue, deconstructor, valueProvider, validators, false, optionAttribute.IsHidden) | ||||
|             defaultValue, deconstructor, valueProvider, validators, | ||||
|             optionAttribute.IsRequired, optionAttribute.IsHidden) | ||||
|     { | ||||
|         LongNames = optionAttribute.LongNames; | ||||
|         ShortNames = optionAttribute.ShortNames; | ||||
|   | ||||
| @@ -12,7 +12,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame | ||||
|     public PairDeconstructorAttribute? PairDeconstructor { get; } | ||||
|     public List<ParameterValidationAttribute> Validators { get; } | ||||
|     public ParameterValueProviderAttribute? ValueProvider { get; } | ||||
|     public bool Required { get; set; } | ||||
|     public bool IsRequired { get; set; } | ||||
|     public bool IsHidden { get; } | ||||
|     public string PropertyName => Property.Name; | ||||
|  | ||||
| @@ -38,8 +38,8 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame | ||||
|         DefaultValue = defaultValue; | ||||
|         PairDeconstructor = deconstructor; | ||||
|         ValueProvider = valueProvider; | ||||
|         Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>()); | ||||
|         Required = required; | ||||
|         Validators = new List<ParameterValidationAttribute>(validators ?? []); | ||||
|         IsRequired = required; | ||||
|         IsHidden = isHidden; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -9,6 +9,7 @@ global using System.IO; | ||||
| global using System.Linq; | ||||
| global using System.Reflection; | ||||
| global using System.Text; | ||||
| global using System.Threading; | ||||
| global using System.Threading.Tasks; | ||||
| global using System.Xml; | ||||
| global using Spectre.Console.Cli.Help; | ||||
|   | ||||
| @@ -1,19 +1,15 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks> | ||||
|     <IsPackable>true</IsPackable> | ||||
|   </PropertyGroup> | ||||
|   <PropertyGroup> | ||||
|     <IsAotCompatible Condition="'$(TargetFramework)' != 'netstandard2.0'" >false</IsAotCompatible> | ||||
|     <IsAotCompatible Condition="'$(TargetFramework)' != 'netstandard2.0'">false</IsAotCompatible> | ||||
|     <IsTrimmable>false</IsTrimmable> | ||||
|     <DefineConstants>$(DefineConstants);OPENCLI_VISIBILITY_INTERNAL</DefineConstants> | ||||
|   </PropertyGroup> | ||||
|   <ItemGroup Label="REMOVE THIS"> | ||||
|     <InternalsVisibleTo Include="Spectre.Console.Cli.Tests" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" /> | ||||
|   <ItemGroup Label="REMOVE THIS"> | ||||
|     <InternalsVisibleTo Include="Spectre.Console.Cli.Tests"/> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <PropertyGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> | ||||
| @@ -22,16 +18,16 @@ | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup Label="Dependencies"> | ||||
|     <PackageReference Include="OpenCli.Sources" /> | ||||
|     <PackageReference Include="PolySharp"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|       <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> | ||||
|     </PackageReference> | ||||
|   </ItemGroup> | ||||
|    | ||||
|  | ||||
|   <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> | ||||
|     <PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" PrivateAssets="all" /> | ||||
|     <PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]" /> | ||||
|     <PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" PrivateAssets="all"/> | ||||
|     <PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]"/> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
| @@ -49,4 +45,8 @@ | ||||
|     </EmbeddedResource> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
|   | ||||
| @@ -8,6 +8,11 @@ public sealed class CommandAppTester | ||||
|     private Action<CommandApp>? _appConfiguration; | ||||
|     private Action<IConfigurator>? _configuration; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets the test console used by both the CommandAppTester and CommandApp. | ||||
|     /// </summary> | ||||
|     public TestConsole Console { get; } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Gets or sets the Registrar to use in the CommandApp. | ||||
|     /// </summary> | ||||
| @@ -23,10 +28,15 @@ public sealed class CommandAppTester | ||||
|     /// </summary> | ||||
|     /// <param name="registrar">The registrar.</param> | ||||
|     /// <param name="settings">The settings.</param> | ||||
|     public CommandAppTester(ITypeRegistrar? registrar = null, CommandAppTesterSettings? settings = null) | ||||
|     /// <param name="console">The test console that overrides the default one.</param> | ||||
|     public CommandAppTester( | ||||
|         ITypeRegistrar? registrar = null, | ||||
|         CommandAppTesterSettings? settings = null, | ||||
|         TestConsole? console = null) | ||||
|     { | ||||
|         Registrar = registrar; | ||||
|         TestSettings = settings ?? new CommandAppTesterSettings(); | ||||
|         Console = console ?? new TestConsole().Width(int.MaxValue); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -36,6 +46,7 @@ public sealed class CommandAppTester | ||||
|     public CommandAppTester(CommandAppTesterSettings settings) | ||||
|     { | ||||
|         TestSettings = settings; | ||||
|         Console = new TestConsole().Width(int.MaxValue); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -85,25 +96,23 @@ public sealed class CommandAppTester | ||||
|     public CommandAppFailure RunAndCatch<T>(params string[] args) | ||||
|         where T : Exception | ||||
|     { | ||||
|         var console = new TestConsole().Width(int.MaxValue); | ||||
|  | ||||
|         try | ||||
|         { | ||||
|             Run(args, console, c => c.PropagateExceptions()); | ||||
|             RunAsync(args, Console, c => c.PropagateExceptions()).GetAwaiter().GetResult(); | ||||
|             throw new InvalidOperationException("Expected an exception to be thrown, but there was none."); | ||||
|         } | ||||
|         catch (T ex) | ||||
|         { | ||||
|             if (ex is CommandAppException commandAppException && commandAppException.Pretty != null) | ||||
|             { | ||||
|                 console.Write(commandAppException.Pretty); | ||||
|                 Console.Write(commandAppException.Pretty); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 console.WriteLine(ex.Message); | ||||
|                 Console.WriteLine(ex.Message); | ||||
|             } | ||||
|  | ||||
|             return new CommandAppFailure(ex, console.Output); | ||||
|             return new CommandAppFailure(ex, Console.Output); | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
| @@ -120,55 +129,21 @@ public sealed class CommandAppTester | ||||
|     /// <returns>The result.</returns> | ||||
|     public CommandAppResult Run(params string[] args) | ||||
|     { | ||||
|         var console = new TestConsole().Width(int.MaxValue); | ||||
|         return Run(args, console); | ||||
|     } | ||||
|  | ||||
|     private CommandAppResult Run(string[] args, TestConsole console, Action<IConfigurator>? config = null) | ||||
|     { | ||||
|         CommandContext? context = null; | ||||
|         CommandSettings? settings = null; | ||||
|  | ||||
|         var app = new CommandApp(Registrar); | ||||
|         _appConfiguration?.Invoke(app); | ||||
|  | ||||
|         if (_configuration != null) | ||||
|         { | ||||
|             app.Configure(_configuration); | ||||
|         } | ||||
|  | ||||
|         if (config != null) | ||||
|         { | ||||
|             app.Configure(config); | ||||
|         } | ||||
|  | ||||
|         app.Configure(c => c.ConfigureConsole(console)); | ||||
|         app.Configure(c => c.SetInterceptor(new CallbackCommandInterceptor((ctx, s) => | ||||
|         { | ||||
|             context = ctx; | ||||
|             settings = s; | ||||
|         }))); | ||||
|  | ||||
|         var result = app.Run(args); | ||||
|  | ||||
|         var output = console.Output.NormalizeLineEndings(); | ||||
|         output = TestSettings.TrimConsoleOutput ? output.TrimLines().Trim() : output; | ||||
|  | ||||
|         return new CommandAppResult(result, output, context, settings); | ||||
|         return RunAsync(args, Console).GetAwaiter().GetResult(); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Runs the command application asynchronously. | ||||
|     /// </summary> | ||||
|     /// <param name="args">The arguments.</param> | ||||
|     /// <param name="cancellationToken">The token to monitor for cancellation requests.</param> | ||||
|     /// <returns>The result.</returns> | ||||
|     public async Task<CommandAppResult> RunAsync(params string[] args) | ||||
|     public async Task<CommandAppResult> RunAsync(string[]? args = null, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         var console = new TestConsole().Width(int.MaxValue); | ||||
|         return await RunAsync(args, console); | ||||
|         return await RunAsync(args ?? [], Console, cancellationToken: cancellationToken); | ||||
|     } | ||||
|  | ||||
|     private async Task<CommandAppResult> RunAsync(string[] args, TestConsole console, Action<IConfigurator>? config = null) | ||||
|     private async Task<CommandAppResult> RunAsync(string[] args, TestConsole console, Action<IConfigurator>? config = null, CancellationToken cancellationToken = default) | ||||
|     { | ||||
|         CommandContext? context = null; | ||||
|         CommandSettings? settings = null; | ||||
| @@ -193,7 +168,7 @@ public sealed class CommandAppTester | ||||
|             settings = s; | ||||
|         }))); | ||||
|  | ||||
|         var result = await app.RunAsync(args); | ||||
|         var result = await app.RunAsync(args, cancellationToken); | ||||
|  | ||||
|         var output = console.Output.NormalizeLineEndings(); | ||||
|         output = TestSettings.TrimConsoleOutput ? output.TrimLines().Trim() : output; | ||||
|   | ||||
| @@ -1,7 +1,20 @@ | ||||
| namespace Spectre.Console; | ||||
| namespace Spectre.Console.Testing; | ||||
|  | ||||
| internal static class ShouldlyExtensions | ||||
| /// <summary> | ||||
| /// Provides extensions for testing using the Shouldly-style fluent assertions. | ||||
| /// </summary> | ||||
| public static class ShouldlyExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Performs the specified action on the given object and then returns the object. | ||||
|     /// Useful for fluent testing patterns where additional assertions or operations | ||||
|     /// are chained together in a readable manner. | ||||
|     /// </summary> | ||||
|     /// <typeparam name="T">The type of the object.</typeparam> | ||||
|     /// <param name="item">The object to operate on.</param> | ||||
|     /// <param name="action">An action to perform on the object.</param> | ||||
|     /// <returns>The original object, to allow further chaining.</returns> | ||||
|     /// <exception cref="ArgumentNullException">Thrown if <paramref name="action"/> is null.</exception> | ||||
|     [DebuggerStepThrough] | ||||
|     public static T And<T>(this T item, Action<T> action) | ||||
|     { | ||||
|   | ||||
| @@ -0,0 +1,59 @@ | ||||
| namespace Spectre.Console.Testing; | ||||
|  | ||||
| /// <summary> | ||||
| /// Provides extension methods for working with <see cref="TestConsole"/> in a testing context, | ||||
| /// including stack trace normalization for consistent and deterministic test output. | ||||
| /// </summary> | ||||
| public static partial class TestConsoleExtensions | ||||
| { | ||||
|     private static readonly Regex _lineNumberRegex = new Regex(":\\d+", RegexOptions.Singleline); | ||||
|     private static readonly Regex _filenameRegex = new Regex("\\sin\\s.*cs:nn", RegexOptions.Multiline); | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Writes the given exception to the <see cref="TestConsole"/> and returns a normalized string | ||||
|     /// representation of the exception, with file paths and line numbers sanitized. | ||||
|     /// </summary> | ||||
|     /// <param name="console">The <see cref="TestConsole"/> to write to.</param> | ||||
|     /// <param name="ex">The exception to write and normalize.</param> | ||||
|     /// <param name="formats">Optional formatting options for exception output.</param> | ||||
|     /// <returns>A normalized string of the exception's output, safe for snapshot testing.</returns> | ||||
|     /// <exception cref="InvalidOperationException"> | ||||
|     /// Thrown if the console's output buffer is not empty before writing the exception. | ||||
|     /// </exception> | ||||
|     public static string WriteNormalizedException(this TestConsole console, Exception ex, ExceptionFormats formats = ExceptionFormats.Default) | ||||
|     { | ||||
|         if (!string.IsNullOrWhiteSpace(console.Output)) | ||||
|         { | ||||
|             throw new InvalidOperationException("Output buffer is not empty."); | ||||
|         } | ||||
|  | ||||
|         console.WriteException(ex, formats); | ||||
|         return string.Join("\n", NormalizeStackTrace(console.Output) | ||||
|             .NormalizeLineEndings() | ||||
|             .Split(new char[] { '\n' }) | ||||
|             .Select(line => line.TrimEnd())); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Normalizes a stack trace string by replacing line numbers with ":nn" | ||||
|     /// and converting full file paths to a fixed placeholder path ("/xyz/filename.cs"). | ||||
|     /// </summary> | ||||
|     /// <param name="text">The stack trace text to normalize.</param> | ||||
|     /// <returns>A sanitized stack trace suitable for stable testing output.</returns> | ||||
|     public static string NormalizeStackTrace(string text) | ||||
|     { | ||||
|         text = _lineNumberRegex.Replace(text, match => | ||||
|         { | ||||
|             return ":nn"; | ||||
|         }); | ||||
|  | ||||
|         return _filenameRegex.Replace(text, match => | ||||
|         { | ||||
|             var value = match.Value; | ||||
|             var index = value.LastIndexOfAny(new[] { '\\', '/' }); | ||||
|             var filename = value.Substring(index + 1, value.Length - index - 1); | ||||
|  | ||||
|             return $" in /xyz/{filename}"; | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -3,7 +3,7 @@ namespace Spectre.Console.Testing; | ||||
| /// <summary> | ||||
| /// Contains extensions for <see cref="TestConsole"/>. | ||||
| /// </summary> | ||||
| public static class TestConsoleExtensions | ||||
| public static partial class TestConsoleExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Sets the console's color system. | ||||
| @@ -3,6 +3,7 @@ global using System.Collections.Generic; | ||||
| global using System.Diagnostics; | ||||
| global using System.IO; | ||||
| global using System.Linq; | ||||
| global using System.Text.RegularExpressions; | ||||
| global using System.Threading; | ||||
| global using System.Threading.Tasks; | ||||
| global using Spectre.Console.Cli; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <TargetFrameworks>net9.0;net8.0;netstandard2.0</TargetFrameworks> | ||||
| @@ -7,12 +7,7 @@ | ||||
|     <Description>Contains testing utilities for Spectre.Console.</Description> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup Label="REMOVE THIS"> | ||||
|     <InternalsVisibleTo Include="Spectre.Console.Tests" /> | ||||
|     <InternalsVisibleTo Include="Spectre.Console.Cli.Tests" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup Label="Project References"> | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\Spectre.Console.Cli\Spectre.Console.Cli.csproj" /> | ||||
|     <ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" /> | ||||
|   </ItemGroup> | ||||
|   | ||||
| @@ -1,153 +0,0 @@ | ||||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio Version 17 | ||||
| VisualStudioVersion = 17.1.32414.318 | ||||
| MinimumVisualStudioVersion = 15.0.26124.0 | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console", "Spectre.Console\Spectre.Console.csproj", "{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{20595AD4-8D75-4AF8-B6BC-9C38C160423F}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		.editorconfig = .editorconfig | ||||
| 		Directory.Build.props = Directory.Build.props | ||||
| 		Directory.Build.targets = Directory.Build.targets | ||||
| 		..\dotnet-tools.json = ..\dotnet-tools.json | ||||
| 		..\global.json = ..\global.json | ||||
| 		stylecop.json = stylecop.json | ||||
| 		Directory.Packages.props = Directory.Packages.props | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		..\.github\workflows\ci.yaml = ..\.github\workflows\ci.yaml | ||||
| 		..\.github\workflows\publish.yaml = ..\.github\workflows\publish.yaml | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.ImageSharp", "Extensions\Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj", "{0EFE694D-0770-4E71-BF4E-EC2B41362F79}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Testing", "Spectre.Console.Testing\Spectre.Console.Testing.csproj", "{7D5F6704-8249-46DD-906C-9E66419F215F}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{E0E45070-123C-4A4D-AA98-2A780308876C}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Tests", "Tests\Spectre.Console.Tests\Spectre.Console.Tests.csproj", "{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "Spectre.Console.Cli\Spectre.Console.Cli.csproj", "{1B67B74F-1243-4381-9A2B-86EA66D135C5}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli.Tests", "Tests\Spectre.Console.Cli.Tests\Spectre.Console.Cli.Tests.csproj", "{E07C46D2-714F-4116-BADD-FEE09617A9C4}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Json", "Extensions\Spectre.Console.Json\Spectre.Console.Json.csproj", "{579E6E31-1E2F-4FE1-8F8C-9770878993E9}" | ||||
| EndProject | ||||
| Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{F34EFD87-6CEA-453F-858B-094EA413578C}" | ||||
| 	ProjectSection(SolutionItems) = preProject | ||||
| 		Tests\Directory.Build.props = Tests\Directory.Build.props | ||||
| 		Tests\.editorconfig = Tests\.editorconfig | ||||
| 	EndProjectSection | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| 		Debug|x64 = Debug|x64 | ||||
| 		Debug|x86 = Debug|x86 | ||||
| 		Release|Any CPU = Release|Any CPU | ||||
| 		Release|x64 = Release|x64 | ||||
| 		Release|x86 = Release|x86 | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{80DCBEF3-99D6-46C0-9C5B-42B4534D9113}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{7D5F6704-8249-46DD-906C-9E66419F215F}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{1B67B74F-1243-4381-9A2B-86EA66D135C5}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x86.Build.0 = Release|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|x64.ActiveCfg = Debug|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|x64.Build.0 = Debug|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|x86.ActiveCfg = Debug|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|x86.Build.0 = Debug|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|x64.ActiveCfg = Release|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|x64.Build.0 = Release|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|x86.ActiveCfg = Release|Any CPU | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|x86.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(NestedProjects) = preSolution | ||||
| 		{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F} | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79} = {E0E45070-123C-4A4D-AA98-2A780308876C} | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9} = {E0E45070-123C-4A4D-AA98-2A780308876C} | ||||
| 		{60A4CADD-2B3D-48ED-89C0-1637A1F111AE} = {F34EFD87-6CEA-453F-858B-094EA413578C} | ||||
| 		{E07C46D2-714F-4116-BADD-FEE09617A9C4} = {F34EFD87-6CEA-453F-858B-094EA413578C} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
							
								
								
									
										31
									
								
								src/Spectre.Console.slnx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/Spectre.Console.slnx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| <Solution> | ||||
|   <Configurations> | ||||
|     <Platform Name="Any CPU" /> | ||||
|     <Platform Name="x64" /> | ||||
|     <Platform Name="x86" /> | ||||
|   </Configurations> | ||||
|   <Folder Name="/Build/"> | ||||
|     <File Path="../dotnet-tools.json" /> | ||||
|     <File Path="../global.json" /> | ||||
|     <File Path=".editorconfig" /> | ||||
|     <File Path="Directory.Build.props" /> | ||||
|     <File Path="Directory.Build.targets" /> | ||||
|     <File Path="Directory.Packages.props" /> | ||||
|     <File Path="stylecop.json" /> | ||||
|   </Folder> | ||||
|   <Folder Name="/Build/GitHub/"> | ||||
|     <File Path="../.github/workflows/ci.yaml" /> | ||||
|     <File Path="../.github/workflows/publish.yaml" /> | ||||
|   </Folder> | ||||
|   <Folder Name="/CLI/"> | ||||
|     <Project Path="Spectre.Console.Cli/Spectre.Console.Cli.csproj" /> | ||||
|     <Project Path="Tests/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj" /> | ||||
|   </Folder> | ||||
|   <Folder Name="/Extensions/"> | ||||
|     <Project Path="Extensions/Spectre.Console.ImageSharp/Spectre.Console.ImageSharp.csproj" /> | ||||
|     <Project Path="Extensions/Spectre.Console.Json/Spectre.Console.Json.csproj" /> | ||||
|   </Folder> | ||||
|   <Project Path="Spectre.Console.Testing/Spectre.Console.Testing.csproj" /> | ||||
|   <Project Path="Spectre.Console/Spectre.Console.csproj" /> | ||||
|   <Project Path="Tests/Spectre.Console.Tests/Spectre.Console.Tests.csproj" /> | ||||
| </Solution> | ||||
| @@ -45,7 +45,7 @@ public static class TableColumnExtensions | ||||
|             throw new ArgumentNullException(nameof(header)); | ||||
|         } | ||||
|  | ||||
|         column.Footer = header; | ||||
|         column.Header = header; | ||||
|         return column; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,26 @@ namespace Spectre.Console; | ||||
|  | ||||
| internal static class Cell | ||||
| { | ||||
|     private static readonly int?[] _runeWidthCache = new int?[char.MaxValue]; | ||||
|     private const sbyte Sentinel = -2; | ||||
|  | ||||
|     /// <summary> | ||||
|     /// UnicodeCalculator.GetWidth documents the width as (-1, 0, 1, 2). We only need space for these values and a sentinel for uninitialized values. | ||||
|     /// This is only five values in total so we are storing one byte per value. We could store 2 per byte but that would add more logic to the retrieval. | ||||
|     /// We should add one to char.MaxValue because the total number of characters includes \0 too so there are 65536 valid chars. | ||||
|     /// </summary> | ||||
|     private static readonly sbyte[] _runeWidthCache = new sbyte[char.MaxValue + 1]; | ||||
|  | ||||
|     static Cell() | ||||
|     { | ||||
|         #if !NETSTANDARD2_0 | ||||
|         Array.Fill(_runeWidthCache, Sentinel); | ||||
|         #else | ||||
|         for (var i = 0; i < _runeWidthCache.Length; i++) | ||||
|         { | ||||
|             _runeWidthCache[i] = Sentinel; | ||||
|         } | ||||
|         #endif | ||||
|     } | ||||
|  | ||||
|     public static int GetCellLength(string text) | ||||
|     { | ||||
| @@ -29,6 +48,13 @@ internal static class Cell | ||||
|             return 1; | ||||
|         } | ||||
|  | ||||
|         return _runeWidthCache[rune] ??= UnicodeCalculator.GetWidth(rune); | ||||
|         var width = _runeWidthCache[rune]; | ||||
|         if (width == Sentinel) | ||||
|         { | ||||
|             _runeWidthCache[rune] = (sbyte)UnicodeCalculator.GetWidth(rune); | ||||
|             return _runeWidthCache[rune]; | ||||
|         } | ||||
|  | ||||
|         return _runeWidthCache[rune]; | ||||
|     } | ||||
| } | ||||
| @@ -4,7 +4,6 @@ internal sealed class LiveDisplayRenderer : IRenderHook | ||||
| { | ||||
|     private readonly IAnsiConsole _console; | ||||
|     private readonly LiveDisplayContext _context; | ||||
|  | ||||
|     public LiveDisplayRenderer(IAnsiConsole console, LiveDisplayContext context) | ||||
|     { | ||||
|         _console = console; | ||||
| @@ -45,7 +44,7 @@ internal sealed class LiveDisplayRenderer : IRenderHook | ||||
|     { | ||||
|         lock (_context.Lock) | ||||
|         { | ||||
|             yield return _context.Live.PositionCursor(); | ||||
|             yield return _context.Live.PositionCursor(options); | ||||
|  | ||||
|             foreach (var renderable in renderables) | ||||
|             { | ||||
|   | ||||
| @@ -39,7 +39,7 @@ internal sealed class LiveRenderable : Renderable | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public IRenderable PositionCursor() | ||||
|     public IRenderable PositionCursor(RenderOptions options) | ||||
|     { | ||||
|         lock (_lock) | ||||
|         { | ||||
| @@ -48,6 +48,14 @@ internal sealed class LiveRenderable : Renderable | ||||
|                 return new ControlCode(string.Empty); | ||||
|             } | ||||
|  | ||||
|             // Check if the size have been reduced | ||||
|             if (_shape.Value.Height > options.ConsoleSize.Height || _shape.Value.Width > options.ConsoleSize.Width) | ||||
|             { | ||||
|                 // Important reset shape, so the size can shrink | ||||
|                 _shape = null; | ||||
|                 return new ControlCode(ED(2) + ED(3) + CUP(1, 1)); | ||||
|             } | ||||
|  | ||||
|             var linesToMoveUp = _shape.Value.Height - 1; | ||||
|             return new ControlCode("\r" + CUU(linesToMoveUp)); | ||||
|         } | ||||
|   | ||||
| @@ -118,7 +118,7 @@ internal sealed class DefaultProgressRenderer : ProgressRenderer | ||||
|     { | ||||
|         lock (_lock) | ||||
|         { | ||||
|             yield return _live.PositionCursor(); | ||||
|             yield return _live.PositionCursor(options); | ||||
|  | ||||
|             foreach (var renderable in renderables) | ||||
|             { | ||||
|   | ||||
| @@ -360,6 +360,42 @@ namespace Spectre.Console | ||||
|                     "⠀⡀", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class Dots13Spinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "⣼", | ||||
|                     "⣹", | ||||
|                     "⢻", | ||||
|                     "⠿", | ||||
|                     "⡟", | ||||
|                     "⣏", | ||||
|                     "⣧", | ||||
|                     "⣶", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class Dots14Spinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "⠉⠉", | ||||
|                     "⠈⠙", | ||||
|                     "⠀⠹", | ||||
|                     "⠀⢸", | ||||
|                     "⠀⣰", | ||||
|                     "⢀⣠", | ||||
|                     "⣀⣀", | ||||
|                     "⣄⡀", | ||||
|                     "⣆⠀", | ||||
|                     "⡇⠀", | ||||
|                     "⠏⠀", | ||||
|                     "⠋⠁", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class Dots8BitSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
| @@ -624,6 +660,65 @@ namespace Spectre.Console | ||||
|                     "⣿", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class DotsCircleSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "⢎ ", | ||||
|                     "⠎⠁", | ||||
|                     "⠊⠑", | ||||
|                     "⠈⠱", | ||||
|                     " ⡱", | ||||
|                     "⢀⡰", | ||||
|                     "⢄⡠", | ||||
|                     "⢆⡀", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class SandSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "⠁", | ||||
|                     "⠂", | ||||
|                     "⠄", | ||||
|                     "⡀", | ||||
|                     "⡈", | ||||
|                     "⡐", | ||||
|                     "⡠", | ||||
|                     "⣀", | ||||
|                     "⣁", | ||||
|                     "⣂", | ||||
|                     "⣄", | ||||
|                     "⣌", | ||||
|                     "⣔", | ||||
|                     "⣤", | ||||
|                     "⣥", | ||||
|                     "⣦", | ||||
|                     "⣮", | ||||
|                     "⣶", | ||||
|                     "⣷", | ||||
|                     "⣿", | ||||
|                     "⡿", | ||||
|                     "⠿", | ||||
|                     "⢟", | ||||
|                     "⠟", | ||||
|                     "⡛", | ||||
|                     "⠛", | ||||
|                     "⠫", | ||||
|                     "⢋", | ||||
|                     "⠋", | ||||
|                     "⠍", | ||||
|                     "⡉", | ||||
|                     "⠉", | ||||
|                     "⠑", | ||||
|                     "⠡", | ||||
|                     "⢁", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class LineSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(130); | ||||
| @@ -875,6 +970,24 @@ namespace Spectre.Console | ||||
|                     "◥", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class BinarySpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
|             public override bool IsUnicode => false; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "010010", | ||||
|                     "001100", | ||||
|                     "100101", | ||||
|                     "111010", | ||||
|                     "111101", | ||||
|                     "010111", | ||||
|                     "101011", | ||||
|                     "111000", | ||||
|                     "110011", | ||||
|                     "110101", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class ArcSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(100); | ||||
| @@ -1136,6 +1249,7 @@ namespace Spectre.Console | ||||
|                     "[=   ]", | ||||
|                     "[==  ]", | ||||
|                     "[=== ]", | ||||
|                     "[====]", | ||||
|                     "[ ===]", | ||||
|                     "[  ==]", | ||||
|                     "[   =]", | ||||
| @@ -1545,6 +1659,153 @@ namespace Spectre.Console | ||||
|                     "ββββββρ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class FingerDanceSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(160); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "🤘 ", | ||||
|                     "🤟 ", | ||||
|                     "🖖 ", | ||||
|                     "✋ ", | ||||
|                     "🤚 ", | ||||
|                     "👆 ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class FistBumpSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "🤜    🤛 ", | ||||
|                     "🤜    🤛 ", | ||||
|                     "🤜    🤛 ", | ||||
|                     " 🤜  🤛  ", | ||||
|                     "  🤜🤛   ", | ||||
|                     " 🤜✨🤛   ", | ||||
|                     "🤜 ✨ 🤛  ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class SoccerHeaderSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     " 🧑⚽️       🧑 ", | ||||
|                     "🧑  ⚽️      🧑 ", | ||||
|                     "🧑   ⚽️     🧑 ", | ||||
|                     "🧑    ⚽️    🧑 ", | ||||
|                     "🧑     ⚽️   🧑 ", | ||||
|                     "🧑      ⚽️  🧑 ", | ||||
|                     "🧑       ⚽️🧑  ", | ||||
|                     "🧑      ⚽️  🧑 ", | ||||
|                     "🧑     ⚽️   🧑 ", | ||||
|                     "🧑    ⚽️    🧑 ", | ||||
|                     "🧑   ⚽️     🧑 ", | ||||
|                     "🧑  ⚽️      🧑 ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class MindblownSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(160); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "😐 ", | ||||
|                     "😐 ", | ||||
|                     "😮 ", | ||||
|                     "😮 ", | ||||
|                     "😦 ", | ||||
|                     "😦 ", | ||||
|                     "😧 ", | ||||
|                     "😧 ", | ||||
|                     "🤯 ", | ||||
|                     "💥 ", | ||||
|                     "✨ ", | ||||
|                     "  ", | ||||
|                     "  ", | ||||
|                     "  ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class SpeakerSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(160); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "🔈 ", | ||||
|                     "🔉 ", | ||||
|                     "🔊 ", | ||||
|                     "🔉 ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class OrangePulseSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(100); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "🔸 ", | ||||
|                     "🔶 ", | ||||
|                     "🟠 ", | ||||
|                     "🟠 ", | ||||
|                     "🔶 ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class BluePulseSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(100); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "🔹 ", | ||||
|                     "🔷 ", | ||||
|                     "🔵 ", | ||||
|                     "🔵 ", | ||||
|                     "🔷 ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class OrangeBluePulseSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(100); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "🔸 ", | ||||
|                     "🔶 ", | ||||
|                     "🟠 ", | ||||
|                     "🟠 ", | ||||
|                     "🔶 ", | ||||
|                     "🔹 ", | ||||
|                     "🔷 ", | ||||
|                     "🔵 ", | ||||
|                     "🔵 ", | ||||
|                     "🔷 ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class TimeTravelSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(100); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     "🕛 ", | ||||
|                     "🕚 ", | ||||
|                     "🕙 ", | ||||
|                     "🕘 ", | ||||
|                     "🕗 ", | ||||
|                     "🕖 ", | ||||
|                     "🕕 ", | ||||
|                     "🕔 ", | ||||
|                     "🕓 ", | ||||
|                     "🕒 ", | ||||
|                     "🕑 ", | ||||
|                     "🕐 ", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class AestheticSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
| @@ -1561,6 +1822,147 @@ namespace Spectre.Console | ||||
|                     "▰▱▱▱▱▱▱", | ||||
|             }; | ||||
|         } | ||||
|         private sealed class DwarfFortressSpinner : Spinner | ||||
|         { | ||||
|             public override TimeSpan Interval => TimeSpan.FromMilliseconds(80); | ||||
|             public override bool IsUnicode => true; | ||||
|             public override IReadOnlyList<string> Frames => new List<string> | ||||
|             { | ||||
|                     " ██████£££  ", | ||||
|                     "☺██████£££  ", | ||||
|                     "☺██████£££  ", | ||||
|                     "☺▓█████£££  ", | ||||
|                     "☺▓█████£££  ", | ||||
|                     "☺▒█████£££  ", | ||||
|                     "☺▒█████£££  ", | ||||
|                     "☺░█████£££  ", | ||||
|                     "☺░█████£££  ", | ||||
|                     "☺ █████£££  ", | ||||
|                     " ☺█████£££  ", | ||||
|                     " ☺█████£££  ", | ||||
|                     " ☺▓████£££  ", | ||||
|                     " ☺▓████£££  ", | ||||
|                     " ☺▒████£££  ", | ||||
|                     " ☺▒████£££  ", | ||||
|                     " ☺░████£££  ", | ||||
|                     " ☺░████£££  ", | ||||
|                     " ☺ ████£££  ", | ||||
|                     "  ☺████£££  ", | ||||
|                     "  ☺████£££  ", | ||||
|                     "  ☺▓███£££  ", | ||||
|                     "  ☺▓███£££  ", | ||||
|                     "  ☺▒███£££  ", | ||||
|                     "  ☺▒███£££  ", | ||||
|                     "  ☺░███£££  ", | ||||
|                     "  ☺░███£££  ", | ||||
|                     "  ☺ ███£££  ", | ||||
|                     "   ☺███£££  ", | ||||
|                     "   ☺███£££  ", | ||||
|                     "   ☺▓██£££  ", | ||||
|                     "   ☺▓██£££  ", | ||||
|                     "   ☺▒██£££  ", | ||||
|                     "   ☺▒██£££  ", | ||||
|                     "   ☺░██£££  ", | ||||
|                     "   ☺░██£££  ", | ||||
|                     "   ☺ ██£££  ", | ||||
|                     "    ☺██£££  ", | ||||
|                     "    ☺██£££  ", | ||||
|                     "    ☺▓█£££  ", | ||||
|                     "    ☺▓█£££  ", | ||||
|                     "    ☺▒█£££  ", | ||||
|                     "    ☺▒█£££  ", | ||||
|                     "    ☺░█£££  ", | ||||
|                     "    ☺░█£££  ", | ||||
|                     "    ☺ █£££  ", | ||||
|                     "     ☺█£££  ", | ||||
|                     "     ☺█£££  ", | ||||
|                     "     ☺▓£££  ", | ||||
|                     "     ☺▓£££  ", | ||||
|                     "     ☺▒£££  ", | ||||
|                     "     ☺▒£££  ", | ||||
|                     "     ☺░£££  ", | ||||
|                     "     ☺░£££  ", | ||||
|                     "     ☺ £££  ", | ||||
|                     "      ☺£££  ", | ||||
|                     "      ☺£££  ", | ||||
|                     "      ☺▓££  ", | ||||
|                     "      ☺▓££  ", | ||||
|                     "      ☺▒££  ", | ||||
|                     "      ☺▒££  ", | ||||
|                     "      ☺░££  ", | ||||
|                     "      ☺░££  ", | ||||
|                     "      ☺ ££  ", | ||||
|                     "       ☺££  ", | ||||
|                     "       ☺££  ", | ||||
|                     "       ☺▓£  ", | ||||
|                     "       ☺▓£  ", | ||||
|                     "       ☺▒£  ", | ||||
|                     "       ☺▒£  ", | ||||
|                     "       ☺░£  ", | ||||
|                     "       ☺░£  ", | ||||
|                     "       ☺ £  ", | ||||
|                     "        ☺£  ", | ||||
|                     "        ☺£  ", | ||||
|                     "        ☺▓  ", | ||||
|                     "        ☺▓  ", | ||||
|                     "        ☺▒  ", | ||||
|                     "        ☺▒  ", | ||||
|                     "        ☺░  ", | ||||
|                     "        ☺░  ", | ||||
|                     "        ☺   ", | ||||
|                     "        ☺  &", | ||||
|                     "        ☺ ☼&", | ||||
|                     "       ☺ ☼ &", | ||||
|                     "       ☺☼  &", | ||||
|                     "      ☺☼  & ", | ||||
|                     "      ‼   & ", | ||||
|                     "     ☺   &  ", | ||||
|                     "    ‼    &  ", | ||||
|                     "   ☺    &   ", | ||||
|                     "  ‼     &   ", | ||||
|                     " ☺     &    ", | ||||
|                     "‼      &    ", | ||||
|                     "      &     ", | ||||
|                     "      &     ", | ||||
|                     "     &   ░  ", | ||||
|                     "     &   ▒  ", | ||||
|                     "    &    ▓  ", | ||||
|                     "    &    £  ", | ||||
|                     "   &    ░£  ", | ||||
|                     "   &    ▒£  ", | ||||
|                     "  &     ▓£  ", | ||||
|                     "  &     ££  ", | ||||
|                     " &     ░££  ", | ||||
|                     " &     ▒££  ", | ||||
|                     "&      ▓££  ", | ||||
|                     "&      £££  ", | ||||
|                     "      ░£££  ", | ||||
|                     "      ▒£££  ", | ||||
|                     "      ▓£££  ", | ||||
|                     "      █£££  ", | ||||
|                     "     ░█£££  ", | ||||
|                     "     ▒█£££  ", | ||||
|                     "     ▓█£££  ", | ||||
|                     "     ██£££  ", | ||||
|                     "    ░██£££  ", | ||||
|                     "    ▒██£££  ", | ||||
|                     "    ▓██£££  ", | ||||
|                     "    ███£££  ", | ||||
|                     "   ░███£££  ", | ||||
|                     "   ▒███£££  ", | ||||
|                     "   ▓███£££  ", | ||||
|                     "   ████£££  ", | ||||
|                     "  ░████£££  ", | ||||
|                     "  ▒████£££  ", | ||||
|                     "  ▓████£££  ", | ||||
|                     "  █████£££  ", | ||||
|                     " ░█████£££  ", | ||||
|                     " ▒█████£££  ", | ||||
|                     " ▓█████£££  ", | ||||
|                     " ██████£££  ", | ||||
|                     " ██████£££  ", | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Contains all predefined spinners. | ||||
| @@ -1624,10 +2026,26 @@ namespace Spectre.Console | ||||
|             /// </summary> | ||||
|             public static Spinner Dots12 { get; } = new Dots12Spinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "dots13" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Dots13 { get; } = new Dots13Spinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "dots14" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Dots14 { get; } = new Dots14Spinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "dots8Bit" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Dots8Bit { get; } = new Dots8BitSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "dotsCircle" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner DotsCircle { get; } = new DotsCircleSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "sand" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Sand { get; } = new SandSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "line" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Line { get; } = new LineSpinner(); | ||||
| @@ -1700,6 +2118,10 @@ namespace Spectre.Console | ||||
|             /// </summary> | ||||
|             public static Spinner Triangle { get; } = new TriangleSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "binary" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Binary { get; } = new BinarySpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "arc" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Arc { get; } = new ArcSpinner(); | ||||
| @@ -1864,9 +2286,49 @@ namespace Spectre.Console | ||||
|             /// </summary> | ||||
|             public static Spinner BetaWave { get; } = new BetaWaveSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "fingerDance" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner FingerDance { get; } = new FingerDanceSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "fistBump" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner FistBump { get; } = new FistBumpSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "soccerHeader" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner SoccerHeader { get; } = new SoccerHeaderSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "mindblown" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Mindblown { get; } = new MindblownSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "speaker" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Speaker { get; } = new SpeakerSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "orangePulse" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner OrangePulse { get; } = new OrangePulseSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "bluePulse" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner BluePulse { get; } = new BluePulseSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "orangeBluePulse" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner OrangeBluePulse { get; } = new OrangeBluePulseSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "timeTravel" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner TimeTravel { get; } = new TimeTravelSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "aesthetic" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner Aesthetic { get; } = new AestheticSpinner(); | ||||
|             /// <summary> | ||||
|             /// Gets the "dwarfFortress" spinner. | ||||
|             /// </summary> | ||||
|             public static Spinner DwarfFortress { get; } = new DwarfFortressSpinner(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -42,7 +42,7 @@ internal sealed class ListPromptRenderHook<T> : IRenderHook | ||||
|                 _dirty = false; | ||||
|             } | ||||
|  | ||||
|             yield return _live.PositionCursor(); | ||||
|             yield return _live.PositionCursor(options); | ||||
|  | ||||
|             foreach (var renderable in renderables) | ||||
|             { | ||||
|   | ||||
| @@ -63,6 +63,7 @@ internal sealed class ListPromptState<T> | ||||
|             switch (keyInfo.Key) | ||||
|             { | ||||
|                 case ConsoleKey.UpArrow: | ||||
|                 case ConsoleKey.K: | ||||
|                     if (currentLeafIndex > 0) | ||||
|                     { | ||||
|                         index = _leafIndexes[currentLeafIndex - 1]; | ||||
| @@ -75,6 +76,7 @@ internal sealed class ListPromptState<T> | ||||
|                     break; | ||||
|  | ||||
|                 case ConsoleKey.DownArrow: | ||||
|                 case ConsoleKey.J: | ||||
|                     if (currentLeafIndex < _leafIndexes.Count - 1) | ||||
|                     { | ||||
|                         index = _leafIndexes[currentLeafIndex + 1]; | ||||
| @@ -117,8 +119,8 @@ internal sealed class ListPromptState<T> | ||||
|         { | ||||
|             index = keyInfo.Key switch | ||||
|             { | ||||
|                 ConsoleKey.UpArrow => Index - 1, | ||||
|                 ConsoleKey.DownArrow => Index + 1, | ||||
|                 ConsoleKey.UpArrow or ConsoleKey.K => Index - 1, | ||||
|                 ConsoleKey.DownArrow or ConsoleKey.J => Index + 1, | ||||
|                 ConsoleKey.Home => 0, | ||||
|                 ConsoleKey.End => ItemCount - 1, | ||||
|                 ConsoleKey.PageUp => Index - PageSize, | ||||
|   | ||||
| @@ -109,7 +109,9 @@ public sealed class SelectionPrompt<T> : IPrompt<T>, IListPromptStrategy<T> | ||||
|     /// <inheritdoc/> | ||||
|     ListPromptInputResult IListPromptStrategy<T>.HandleInput(ConsoleKeyInfo key, ListPromptState<T> state) | ||||
|     { | ||||
|         if (key.Key == ConsoleKey.Enter || key.Key == ConsoleKey.Spacebar || key.Key == ConsoleKey.Packet) | ||||
|         if (key.Key == ConsoleKey.Enter | ||||
|          || key.Key == ConsoleKey.Packet | ||||
|          || (!state.SearchEnabled && key.Key == ConsoleKey.Spacebar)) | ||||
|         { | ||||
|             // Selecting a non leaf in "leaf mode" is not allowed | ||||
|             if (state.Current.IsGroup && Mode == SelectionMode.Leaf) | ||||
|   | ||||
| @@ -19,7 +19,6 @@ | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup Label="Dependencies"> | ||||
|     <PackageReference Include="System.Memory" /> | ||||
|     <PackageReference Include="Wcwidth.Sources"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|     </PackageReference> | ||||
| @@ -34,6 +33,7 @@ | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> | ||||
|     <PackageReference Include="System.Memory" /> | ||||
|     <PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" PrivateAssets="all"/> | ||||
|     <PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]"/> | ||||
|   </ItemGroup> | ||||
|   | ||||
| @@ -226,6 +226,11 @@ public sealed class Style : IEquatable<Style> | ||||
|             builder.Add("on " + Background.ToMarkup()); | ||||
|         } | ||||
|  | ||||
|         if (Link != null) | ||||
|         { | ||||
|             builder.Add($"link={Link}"); | ||||
|         } | ||||
|  | ||||
|         return string.Join(" ", builder); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| namespace Spectre.Console; | ||||
| 
 | ||||
| /// <summary> | ||||
| /// Represents a prompt validation result. | ||||
| /// Represents a validation result. | ||||
| /// </summary> | ||||
| public sealed class ValidationResult | ||||
| { | ||||
| @@ -65,7 +65,7 @@ internal static class ExceptionFormatter | ||||
|  | ||||
|         var stackTrace = new StackTrace(ex, fNeedFileInfo: true); | ||||
|         var allFrames = stackTrace.GetFrames(); | ||||
|         if (allFrames[0]?.GetMethod() == null) | ||||
|         if (allFrames.Length > 0 && allFrames[0]?.GetMethod() == null) | ||||
|         { | ||||
|             // if we can't easily get the method for the frame, then we are in AOT | ||||
|             // fallback to using ToString method of each frame. | ||||
|   | ||||
| @@ -3,16 +3,17 @@ namespace Spectre.Console.Tests; | ||||
| public static class Constants | ||||
| { | ||||
|     public static string[] VersionCommand { get; } = | ||||
|         new[] | ||||
|         { | ||||
|     [ | ||||
|         CliConstants.Commands.Branch, | ||||
|                 CliConstants.Commands.Version, | ||||
|         }; | ||||
|         CliConstants.Commands.Version | ||||
|     ]; | ||||
|  | ||||
|     public static string[] XmlDocCommand { get; } = | ||||
|         new[] | ||||
|         { | ||||
|     [ | ||||
|         CliConstants.Commands.Branch, | ||||
|                 CliConstants.Commands.XmlDoc, | ||||
|         }; | ||||
|         CliConstants.Commands.XmlDoc | ||||
|     ]; | ||||
|  | ||||
|     public static string[] OpenCliOption { get; } = | ||||
|         [CliConstants.DumpHelpOpenCliOption]; | ||||
| } | ||||
|   | ||||
| @@ -9,10 +9,10 @@ public sealed class AsynchronousCommand : AsyncCommand<AsynchronousCommandSettin | ||||
|         _console = console; | ||||
|     } | ||||
|  | ||||
|     public async override Task<int> ExecuteAsync(CommandContext context, AsynchronousCommandSettings settings) | ||||
|     public async override Task<int> ExecuteAsync(CommandContext context, AsynchronousCommandSettings settings, CancellationToken cancellationToken) | ||||
|     { | ||||
|         // Simulate a long running asynchronous task | ||||
|         await Task.Delay(200); | ||||
|         await Task.Delay(200, cancellationToken); | ||||
|  | ||||
|         if (settings.ThrowException) | ||||
|         { | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user