Compare commits

..

55 Commits

Author SHA1 Message Date
tk
37a04f3a74 * Support command description localization
eg.
 [CommandOption("-a|--args")]
 [Description(nameof(Str.GitArgs))]
 [Localization(typeof(Str))]
 public string Args { get; set; }
2025-07-03 11:00:59 +08:00
f32f80dc57 Fix resizing of Live views with reduced size. (#1840) 2025-06-21 12:30:18 +02:00
7f3ebe02c4 Reduce memory usage for rune width cache. (#1756) 2025-06-20 12:49:45 +02:00
d77bfb6391 chore: Update dependency AngleSharp to 1.3.0 (#1809)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 18:05:15 +02:00
7819f0693d chore: Update dependency SixLabors.ImageSharp to 3.1.10 (#1812)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 18:01:14 +02:00
465be9391b chore: Update dependency Microsoft.Playwright to 1.52.0 (#1816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 18:00:41 +02:00
7e5ddb1efe chore: Update dependency Verify.Xunit to v30 (#1818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 17:57:36 +02:00
aabe8eeaf8 chore: Update dependency Scriban to 6.2.1 (#1808)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 17:56:33 +02:00
108b23fca8 chore: Update dependency xunit.runner.visualstudio to 3.1.1 (#1820)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 17:55:15 +02:00
7051bc9e2d chore: Update dependency Microsoft.NET.Test.Sdk to 17.14.1 (#1822)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-06-18 17:52:31 +02:00
65bab890f2 Update System.Memory dependency 2025-06-05 10:34:24 +02:00
bd0e2d3e22 Fixed link not dispalyed in markup in Style.cs and added unit test cases (#1750) 2025-06-02 10:19:53 +02:00
9efc426eb9 Added documentation for align widget 2025-06-01 13:54:15 +02:00
2570202990 Fix build errors 2025-05-25 16:42:43 +02:00
e4b5b56d93 Update help output for required options 2025-05-25 16:42:43 +02:00
67c3909bbb Add support for required options 2025-05-25 16:42:43 +02:00
d836ad1805 Add ShowRowSeparators in Table Widget docs (#1807) 2025-04-14 18:34:09 +02:00
57dd8ee410 #1718 TestConsole can now be configured and accessed in CommandAppTester (#1803)
* TestConsole can now be configured and accessed in CommandAppTester
* Add test with mocked user inputs for interactive command
* Add documentation for using the CommandAppTester

Co-authored-by: Patrik Svensson <patriksvensson@users.noreply.github.com>
Co-authored-by: Marek Magath <Marek.Magath@solarwinds.com>
2025-04-14 10:38:03 +02:00
6105ee2a86 Fix IndexOutOfRangeException
Fix `IndexOutOfRangeException` if an exception does not have an associated stack trace.

Resolves #1798.
2025-04-09 12:36:53 +02:00
b5c839030c Blog post: Fix broken PR links 2025-04-08 20:45:50 +02:00
b08ca1c4d7 Add blog post 2025-04-08 18:29:25 +02:00
68fcfe0de4 Update dependencies and .NET SDK 2025-04-08 17:30:03 +02:00
b0f82d787d Documentation improvements for 1.0 release (#1620)
* Rewrote the Dependency Injection section for better clarity and readability.
* Syntax fix: Should reference interfaces, not implementation
* Renamed TypeRegistrar to MyTypeRegistrar, making it more obvious it is a custom class the user must provide.
2025-04-08 17:01:17 +02:00
1dabf25e1c Add testing documentation (#1631) 2025-04-08 16:58:25 +02:00
958820dd66 Tighten up when to show/hide the application "-v|--version" option. 2025-04-08 16:53:43 +02:00
c4a97f3c89 Improved unit test coverage using Spectre.Console.Tests.Data.VersionCommand 2025-04-08 16:53:43 +02:00
4ac88b5d3f Help writer unit tests; including coverage of when the -v|--version should (and shouldn't) appear in the help output 2025-04-08 16:53:43 +02:00
c937c8800a Updated test names for better self-documentation 2025-04-08 16:43:49 +02:00
349eac1e22 Bug fix: Tests with remaining args should not be expected to pass strict parsing 2025-04-08 16:43:49 +02:00
2f8a38f169 Add strict parsing to all version tests, an obvious omission in main 2025-04-08 16:43:49 +02:00
e9f9f56189 Check if the command has a version option on its setting class 2025-04-08 16:43:49 +02:00
cefb51df7b Unit tests to ensure VersionCommand executes when -v|--version is specified, rather than showing the ApplicationVersion number 2025-04-08 16:43:49 +02:00
75b3b83210 Removed unnecessary using directive 2025-04-08 16:43:49 +02:00
dfdd129dd0 Display the application version (if set) when a version flag is the first argument, even if a default command has been set. 2025-04-08 16:43:49 +02:00
17c7a4f7d6 Cover -v and --version options in unit tests 2025-04-08 16:43:49 +02:00
520efe07e2 Significant improvement to the command line parsing 2025-04-08 16:38:37 +02:00
c81bc5fe1d Fix tests with incorrect validation that were in the main branch
Tests had incorrect expectations for the parsing of branch + default command arguments
2025-04-08 16:38:37 +02:00
edf7f23957 Add strict parsing to almost all branches tests, an obvious omission 2025-04-08 16:38:37 +02:00
80a8b0e406 Cosmetic: Remove ugly looking variable naming 2025-04-08 16:38:37 +02:00
dca67da8cd New unit tests to ensure unknown flags are added to remaining args 2025-04-08 16:38:37 +02:00
78272e62e6 Merge pull request #1779 from Moustafaa91/bug/1776 2025-03-24 00:05:14 +01:00
540fb0195f Upgrade SixLabors.ImageSharp to 3.1.7 2025-03-23 13:44:18 +00:00
93668e92b6 Changed IConfigurator to return IConfigurator instead of void for (#1762) 2025-02-24 20:57:23 +00:00
11a320c7c9 Conditionally trim trailing periods of argument and option descriptions (#1740) 2025-02-11 21:13:30 +00:00
c1eb94c1db Merge pull request #1755 from 0xced/fix-generic-exception-formatting 2025-02-05 18:39:38 +01:00
9d8d3c1d6d Fix generic exception formatting with shortened types
Fixes #1754
2025-02-05 18:20:21 +01:00
7e1142df58 Add tests for generic exception formatting 2025-02-05 18:20:21 +01:00
a6b96e9297 Include resource files for additional cultures in HelpProvider. (#1717)
* Creation of Resource files for HelpProvider in the following cultures: it, ja, ko, pt, ru, and zh-Hans.
* Add unit test files and update InlineData for new cultures.
* Include --version option in CommandAppTests.Help.cs and update related output files
2025-01-31 21:23:40 +00:00
f1f633cc72 Merge pull request #1747 from phil-scott-78/spinner-extension 2025-01-28 18:41:34 +01:00
05ce33615e Add async spinner extension methods and related documentation 2025-01-28 10:20:24 -05:00
97715f2553 Merge pull request #1743 from spectreconsole/FrankRay78-disable-blank-issue 2025-01-25 20:20:59 +01:00
039553efbb Disable the GitHub 'Blank issue' option offered to users 2025-01-25 18:30:02 +00:00
f704f2a0e8 Merge pull request #1739 from FrankRay78/1738-CommandAppTester-is-trimming-TestConsole-output 2025-01-21 10:33:25 +01:00
8c5264d117 Trimming of TestConsole output by CommandAppTester is configurable 2025-01-21 08:39:31 +00:00
58bf89a56a Implement 3 digit hex parsing (#1708) 2024-12-04 16:31:21 +01:00
123 changed files with 4007 additions and 457 deletions

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1 @@
blank_issues_enabled: false

View File

@ -38,7 +38,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.Playwright" Version="1.49.0" /> <PackageReference Include="Microsoft.Playwright" Version="1.52.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Statiq.CodeAnalysis" Version="1.0.0-beta.72" /> <PackageReference Include="Statiq.CodeAnalysis" Version="1.0.0-beta.72" />
<PackageReference Include="Statiq.Common" Version="1.0.0-beta.72" /> <PackageReference Include="Statiq.Common" Version="1.0.0-beta.72" />

View File

@ -23,6 +23,7 @@ namespace Docs
{ {
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", "../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", "../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.Testing/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Extensions/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs", "../../src/Extensions/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Extensions/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs" "../../src/Extensions/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
}) })

View File

@ -1,6 +1,6 @@
{ {
"sdk": { "sdk": {
"version": "9.0.100", "version": "9.0.202",
"rollForward": "latestFeature" "rollForward": "latestFeature"
} }
} }

View 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"]

View File

@ -0,0 +1,332 @@
{"version": 2, "width": 42, "height": 4, "title": "await-spinner (plain)", "env": {"TERM": "Spectre.Console"}}
[0, "o", "Loading the rocket ship "]
[0, "o", "\u001B[?25l"]
[0, "o", "\u280B"]
[0, "o", "\u001B[1D"]
[0.094, "o", "\u001B[?25l"]
[0.094, "o", "\u2819"]
[0.094, "o", "\u001B[1D"]
[0.188, "o", "\u001B[?25l"]
[0.188, "o", "\u2839"]
[0.188, "o", "\u001B[1D"]
[0.266, "o", "\u001B[?25l"]
[0.266, "o", "\u2838"]
[0.266, "o", "\u001B[1D"]
[0.36, "o", "\u001B[?25l"]
[0.36, "o", "\u283C"]
[0.36, "o", "\u001B[1D"]
[0.453, "o", "\u001B[?25l"]
[0.453, "o", "\u2834"]
[0.453, "o", "\u001B[1D"]
[0.563, "o", "\u001B[?25l"]
[0.563, "o", "\u2826"]
[0.563, "o", "\u001B[1D"]
[0.656, "o", "\u001B[?25l"]
[0.656, "o", "\u2827"]
[0.656, "o", "\u001B[1D"]
[0.75, "o", "\u001B[?25l"]
[0.75, "o", "\u2807"]
[0.75, "o", "\u001B[1D"]
[0.844, "o", "\u001B[?25l"]
[0.844, "o", "\u280F"]
[0.844, "o", "\u001B[1D"]
[0.922, "o", "\u001B[?25l"]
[0.922, "o", "\u280B"]
[0.922, "o", "\u001B[1D"]
[1.016, "o", "\u001B[?25l"]
[1.016, "o", "\u2819"]
[1.016, "o", "\u001B[1D"]
[1.11, "o", "\u001B[?25l"]
[1.11, "o", "\u2839"]
[1.11, "o", "\u001B[1D"]
[1.203, "o", "\u001B[?25l"]
[1.203, "o", "\u2838"]
[1.203, "o", "\u001B[1D"]
[1.297, "o", "\u001B[?25l"]
[1.297, "o", "\u283C"]
[1.297, "o", "\u001B[1D"]
[1.391, "o", "\u001B[?25l"]
[1.391, "o", "\u2834"]
[1.391, "o", "\u001B[1D"]
[1.485, "o", "\u001B[?25l"]
[1.485, "o", "\u2826"]
[1.485, "o", "\u001B[1D"]
[1.578, "o", "\u001B[?25l"]
[1.578, "o", "\u2827"]
[1.578, "o", "\u001B[1D"]
[1.672, "o", "\u001B[?25l"]
[1.672, "o", "\u2807"]
[1.672, "o", "\u001B[1D"]
[1.75, "o", "\u001B[?25l"]
[1.75, "o", "\u280F"]
[1.75, "o", "\u001B[1D"]
[1.828, "o", "\u001B[?25l"]
[1.828, "o", "\u280B"]
[1.828, "o", "\u001B[1D"]
[1.906, "o", "\u001B[?25l"]
[1.906, "o", "\u2819"]
[1.906, "o", "\u001B[1D"]
[2, "o", "\u001B[?25l"]
[2, "o", "\u2839"]
[2, "o", "\u001B[1D"]
[2.11, "o", "\u001B[?25l"]
[2.11, "o", "\u2838"]
[2.11, "o", "\u001B[1D"]
[2.203, "o", "\u001B[?25l"]
[2.203, "o", "\u283C"]
[2.203, "o", "\u001B[1D"]
[2.297, "o", "\u001B[?25l"]
[2.297, "o", "\u2834"]
[2.297, "o", "\u001B[1D"]
[2.391, "o", "\u001B[?25l"]
[2.391, "o", "\u2826"]
[2.391, "o", "\u001B[1D"]
[2.485, "o", "\u001B[?25l"]
[2.485, "o", "\u2827"]
[2.485, "o", "\u001B[1D"]
[2.578, "o", "\u001B[?25l"]
[2.578, "o", "\u2807"]
[2.578, "o", "\u001B[1D"]
[2.656, "o", "\u001B[?25l"]
[2.656, "o", "\u280F"]
[2.656, "o", "\u001B[1D"]
[2.735, "o", "\u001B[?25l"]
[2.735, "o", "\u280B"]
[2.735, "o", "\u001B[1D"]
[2.828, "o", "\u001B[?25l"]
[2.828, "o", "\u2819"]
[2.828, "o", "\u001B[1D"]
[2.922, "o", "\u001B[?25l"]
[2.922, "o", "\u2839"]
[2.922, "o", "\u001B[1D"]
[3.016, "o", "\u001B[?25l"]
[3.016, "o", "\u2838"]
[3.016, "o", "\u001B[1D"]
[3.094, "o", "\u001B[?25l"]
[3.094, "o", "\u283C"]
[3.094, "o", "\u001B[1D"]
[3.188, "o", "\u001B[?25l"]
[3.188, "o", "\u2834"]
[3.188, "o", "\u001B[1D"]
[3.281, "o", "\u001B[?25l"]
[3.281, "o", "\u2826"]
[3.281, "o", "\u001B[1D"]
[3.375, "o", "\u001B[?25l"]
[3.375, "o", "\u2827"]
[3.375, "o", "\u001B[1D"]
[3.453, "o", "\u001B[?25l"]
[3.453, "o", "\u2807"]
[3.453, "o", "\u001B[1D"]
[3.516, "o", " "]
[3.516, "o", "\u001B[1D"]
[3.516, "o", "\u001B[?25h"]
[3.516, "o", "\u001B[32mDone\u001B[0m\r\n"]
[3.516, "o", "Firing up the engines "]
[3.516, "o", "\u001B[?25l"]
[3.516, "o", "[ ]"]
[3.516, "o", "\u001B[6D"]
[3.61, "o", "\u001B[?25l"]
[3.61, "o", "[= ]"]
[3.61, "o", "\u001B[6D"]
[3.703, "o", "\u001B[?25l"]
[3.703, "o", "[== ]"]
[3.703, "o", "\u001B[6D"]
[3.797, "o", "\u001B[?25l"]
[3.797, "o", "[=== ]"]
[3.797, "o", "\u001B[6D"]
[3.875, "o", "\u001B[?25l"]
[3.875, "o", "[ ===]"]
[3.875, "o", "\u001B[6D"]
[3.953, "o", "\u001B[?25l"]
[3.953, "o", "[ ==]"]
[3.953, "o", "\u001B[6D"]
[4.063, "o", "\u001B[?25l"]
[4.063, "o", "[ =]"]
[4.063, "o", "\u001B[6D"]
[4.156, "o", "\u001B[?25l"]
[4.156, "o", "[ ]"]
[4.156, "o", "\u001B[6D"]
[4.25, "o", "\u001B[?25l"]
[4.25, "o", "[ =]"]
[4.25, "o", "\u001B[6D"]
[4.328, "o", "\u001B[?25l"]
[4.328, "o", "[ ==]"]
[4.328, "o", "\u001B[6D"]
[4.406, "o", "\u001B[?25l"]
[4.406, "o", "[ ===]"]
[4.406, "o", "\u001B[6D"]
[4.5, "o", "\u001B[?25l"]
[4.5, "o", "[====]"]
[4.5, "o", "\u001B[6D"]
[4.594, "o", "\u001B[?25l"]
[4.594, "o", "[=== ]"]
[4.594, "o", "\u001B[6D"]
[4.688, "o", "\u001B[?25l"]
[4.688, "o", "[== ]"]
[4.688, "o", "\u001B[6D"]
[4.781, "o", "\u001B[?25l"]
[4.781, "o", "[= ]"]
[4.781, "o", "\u001B[6D"]
[4.86, "o", "\u001B[?25l"]
[4.86, "o", "[ ]"]
[4.86, "o", "\u001B[6D"]
[4.953, "o", "\u001B[?25l"]
[4.953, "o", "[= ]"]
[4.953, "o", "\u001B[6D"]
[5.031, "o", "\u001B[?25l"]
[5.031, "o", "[== ]"]
[5.031, "o", "\u001B[6D"]
[5.125, "o", "\u001B[?25l"]
[5.125, "o", "[=== ]"]
[5.125, "o", "\u001B[6D"]
[5.219, "o", "\u001B[?25l"]
[5.219, "o", "[ ===]"]
[5.219, "o", "\u001B[6D"]
[5.313, "o", "\u001B[?25l"]
[5.313, "o", "[ ==]"]
[5.313, "o", "\u001B[6D"]
[5.422, "o", "\u001B[?25l"]
[5.422, "o", "[ =]"]
[5.422, "o", "\u001B[6D"]
[5.5, "o", "\u001B[?25l"]
[5.5, "o", "[ ]"]
[5.5, "o", "\u001B[6D"]
[5.594, "o", "\u001B[?25l"]
[5.594, "o", "[ =]"]
[5.594, "o", "\u001B[6D"]
[5.672, "o", "\u001B[?25l"]
[5.672, "o", "[ ==]"]
[5.672, "o", "\u001B[6D"]
[5.766, "o", "\u001B[?25l"]
[5.766, "o", "[ ===]"]
[5.766, "o", "\u001B[6D"]
[5.86, "o", "\u001B[?25l"]
[5.86, "o", "[====]"]
[5.86, "o", "\u001B[6D"]
[5.953, "o", "\u001B[?25l"]
[5.969, "o", "[=== ]"]
[5.969, "o", "\u001B[6D"]
[6.063, "o", "\u001B[?25l"]
[6.063, "o", "[== ]"]
[6.063, "o", "\u001B[6D"]
[6.156, "o", "\u001B[?25l"]
[6.156, "o", "[= ]"]
[6.156, "o", "\u001B[6D"]
[6.25, "o", "\u001B[?25l"]
[6.25, "o", "[ ]"]
[6.25, "o", "\u001B[6D"]
[6.328, "o", "\u001B[?25l"]
[6.328, "o", "[= ]"]
[6.328, "o", "\u001B[6D"]
[6.422, "o", "\u001B[?25l"]
[6.422, "o", "[== ]"]
[6.422, "o", "\u001B[6D"]
[6.516, "o", "\u001B[?25l"]
[6.516, "o", "[=== ]"]
[6.516, "o", "\u001B[6D"]
[6.61, "o", "\u001B[?25l"]
[6.61, "o", "[ ===]"]
[6.61, "o", "\u001B[6D"]
[6.703, "o", "\u001B[?25l"]
[6.703, "o", "[ ==]"]
[6.703, "o", "\u001B[6D"]
[6.797, "o", "\u001B[?25l"]
[6.797, "o", "[ =]"]
[6.797, "o", "\u001B[6D"]
[6.891, "o", "\u001B[?25l"]
[6.891, "o", "[ ]"]
[6.891, "o", "\u001B[6D"]
[6.922, "o", " "]
[6.922, "o", "\u001B[6D"]
[6.922, "o", "\u001B[?25h"]
[6.922, "o", "\u001B[32mDone\u001B[0m\r\n"]
[6.922, "o", "Blasting into orbit "]
[6.922, "o", "\u001B[?25l"]
[6.922, "o", "\u2631"]
[6.922, "o", "\u001B[1D"]
[7.031, "o", "\u001B[?25l"]
[7.031, "o", "\u2632"]
[7.031, "o", "\u001B[1D"]
[7.141, "o", "\u001B[?25l"]
[7.141, "o", "\u2634"]
[7.141, "o", "\u001B[1D"]
[7.25, "o", "\u001B[?25l"]
[7.25, "o", "\u2631"]
[7.25, "o", "\u001B[1D"]
[7.36, "o", "\u001B[?25l"]
[7.36, "o", "\u2632"]
[7.36, "o", "\u001B[1D"]
[7.485, "o", "\u001B[?25l"]
[7.485, "o", "\u2634"]
[7.485, "o", "\u001B[1D"]
[7.594, "o", "\u001B[?25l"]
[7.594, "o", "\u2631"]
[7.594, "o", "\u001B[1D"]
[7.703, "o", "\u001B[?25l"]
[7.703, "o", "\u2632"]
[7.703, "o", "\u001B[1D"]
[7.813, "o", "\u001B[?25l"]
[7.813, "o", "\u2634"]
[7.813, "o", "\u001B[1D"]
[7.922, "o", "\u001B[?25l"]
[7.922, "o", "\u2631"]
[7.922, "o", "\u001B[1D"]
[8.031, "o", "\u001B[?25l"]
[8.031, "o", "\u2632"]
[8.031, "o", "\u001B[1D"]
[8.141, "o", "\u001B[?25l"]
[8.141, "o", "\u2634"]
[8.141, "o", "\u001B[1D"]
[8.25, "o", "\u001B[?25l"]
[8.25, "o", "\u2631"]
[8.25, "o", "\u001B[1D"]
[8.375, "o", "\u001B[?25l"]
[8.375, "o", "\u2632"]
[8.375, "o", "\u001B[1D"]
[8.485, "o", "\u001B[?25l"]
[8.485, "o", "\u2634"]
[8.485, "o", "\u001B[1D"]
[8.594, "o", "\u001B[?25l"]
[8.594, "o", "\u2631"]
[8.594, "o", "\u001B[1D"]
[8.703, "o", "\u001B[?25l"]
[8.703, "o", "\u2632"]
[8.703, "o", "\u001B[1D"]
[8.813, "o", "\u001B[?25l"]
[8.813, "o", "\u2634"]
[8.813, "o", "\u001B[1D"]
[8.938, "o", "\u001B[?25l"]
[8.938, "o", "\u2631"]
[8.938, "o", "\u001B[1D"]
[9.047, "o", "\u001B[?25l"]
[9.047, "o", "\u2632"]
[9.047, "o", "\u001B[1D"]
[9.156, "o", "\u001B[?25l"]
[9.156, "o", "\u2634"]
[9.156, "o", "\u001B[1D"]
[9.266, "o", "\u001B[?25l"]
[9.266, "o", "\u2631"]
[9.266, "o", "\u001B[1D"]
[9.375, "o", "\u001B[?25l"]
[9.375, "o", "\u2632"]
[9.375, "o", "\u001B[1D"]
[9.485, "o", "\u001B[?25l"]
[9.485, "o", "\u2634"]
[9.485, "o", "\u001B[1D"]
[9.594, "o", "\u001B[?25l"]
[9.594, "o", "\u2631"]
[9.594, "o", "\u001B[1D"]
[9.719, "o", "\u001B[?25l"]
[9.719, "o", "\u2632"]
[9.719, "o", "\u001B[1D"]
[9.828, "o", "\u001B[?25l"]
[9.828, "o", "\u2634"]
[9.828, "o", "\u001B[1D"]
[9.938, "o", "\u001B[?25l"]
[9.938, "o", "\u2631"]
[9.938, "o", "\u001B[1D"]
[9.953, "o", " "]
[9.953, "o", "\u001B[1D"]
[9.953, "o", "\u001B[?25h"]
[9.953, "o", "\u001B[31mOh no\u001B[0m\r\n"]

View File

@ -0,0 +1,326 @@
{"version": 2, "width": 42, "height": 4, "title": "await-spinner (rich)", "env": {"TERM": "Spectre.Console"}}
[0, "o", "Loading the rocket ship "]
[0, "o", "\u001B[?25l"]
[0, "o", "\u280B"]
[0, "o", "\u001B[1D"]
[0.094, "o", "\u001B[?25l"]
[0.094, "o", "\u2819"]
[0.094, "o", "\u001B[1D"]
[0.172, "o", "\u001B[?25l"]
[0.172, "o", "\u2839"]
[0.172, "o", "\u001B[1D"]
[0.266, "o", "\u001B[?25l"]
[0.266, "o", "\u2838"]
[0.266, "o", "\u001B[1D"]
[0.36, "o", "\u001B[?25l"]
[0.36, "o", "\u283C"]
[0.36, "o", "\u001B[1D"]
[0.453, "o", "\u001B[?25l"]
[0.453, "o", "\u2834"]
[0.453, "o", "\u001B[1D"]
[0.547, "o", "\u001B[?25l"]
[0.547, "o", "\u2826"]
[0.547, "o", "\u001B[1D"]
[0.641, "o", "\u001B[?25l"]
[0.641, "o", "\u2827"]
[0.641, "o", "\u001B[1D"]
[0.719, "o", "\u001B[?25l"]
[0.719, "o", "\u2807"]
[0.719, "o", "\u001B[1D"]
[0.797, "o", "\u001B[?25l"]
[0.797, "o", "\u280F"]
[0.797, "o", "\u001B[1D"]
[0.891, "o", "\u001B[?25l"]
[0.891, "o", "\u280B"]
[0.891, "o", "\u001B[1D"]
[0.969, "o", "\u001B[?25l"]
[0.969, "o", "\u2819"]
[0.969, "o", "\u001B[1D"]
[1.078, "o", "\u001B[?25l"]
[1.078, "o", "\u2839"]
[1.094, "o", "\u001B[1D"]
[1.25, "o", "\u001B[?25l"]
[1.25, "o", "\u2838"]
[1.266, "o", "\u001B[1D"]
[1.407, "o", "\u001B[?25l"]
[1.407, "o", "\u283C"]
[1.422, "o", "\u001B[1D"]
[1.532, "o", "\u001B[?25l"]
[1.532, "o", "\u2834"]
[1.532, "o", "\u001B[1D"]
[1.61, "o", "\u001B[?25l"]
[1.61, "o", "\u2826"]
[1.61, "o", "\u001B[1D"]
[1.703, "o", "\u001B[?25l"]
[1.703, "o", "\u2827"]
[1.703, "o", "\u001B[1D"]
[1.782, "o", "\u001B[?25l"]
[1.782, "o", "\u2807"]
[1.782, "o", "\u001B[1D"]
[1.86, "o", "\u001B[?25l"]
[1.86, "o", "\u280F"]
[1.86, "o", "\u001B[1D"]
[1.953, "o", "\u001B[?25l"]
[1.953, "o", "\u280B"]
[1.953, "o", "\u001B[1D"]
[2.047, "o", "\u001B[?25l"]
[2.047, "o", "\u2819"]
[2.047, "o", "\u001B[1D"]
[2.125, "o", "\u001B[?25l"]
[2.125, "o", "\u2839"]
[2.125, "o", "\u001B[1D"]
[2.219, "o", "\u001B[?25l"]
[2.219, "o", "\u2838"]
[2.219, "o", "\u001B[1D"]
[2.313, "o", "\u001B[?25l"]
[2.313, "o", "\u283C"]
[2.313, "o", "\u001B[1D"]
[2.407, "o", "\u001B[?25l"]
[2.407, "o", "\u2834"]
[2.407, "o", "\u001B[1D"]
[2.5, "o", "\u001B[?25l"]
[2.5, "o", "\u2826"]
[2.5, "o", "\u001B[1D"]
[2.594, "o", "\u001B[?25l"]
[2.594, "o", "\u2827"]
[2.594, "o", "\u001B[1D"]
[2.688, "o", "\u001B[?25l"]
[2.688, "o", "\u2807"]
[2.688, "o", "\u001B[1D"]
[2.782, "o", "\u001B[?25l"]
[2.782, "o", "\u280F"]
[2.782, "o", "\u001B[1D"]
[2.875, "o", "\u001B[?25l"]
[2.875, "o", "\u280B"]
[2.875, "o", "\u001B[1D"]
[2.969, "o", "\u001B[?25l"]
[2.969, "o", "\u2819"]
[2.969, "o", "\u001B[1D"]
[3.063, "o", "\u001B[?25l"]
[3.063, "o", "\u2839"]
[3.063, "o", "\u001B[1D"]
[3.157, "o", "\u001B[?25l"]
[3.157, "o", "\u2838"]
[3.157, "o", "\u001B[1D"]
[3.25, "o", "\u001B[?25l"]
[3.25, "o", "\u283C"]
[3.25, "o", "\u001B[1D"]
[3.344, "o", "\u001B[?25l"]
[3.344, "o", "\u2834"]
[3.344, "o", "\u001B[1D"]
[3.469, "o", "\u001B[?25l"]
[3.469, "o", "\u2826"]
[3.469, "o", "\u001B[1D"]
[3.563, "o", "\u001B[?25l"]
[3.563, "o", "\u2827"]
[3.563, "o", "\u001B[1D"]
[3.563, "o", " "]
[3.563, "o", "\u001B[1D"]
[3.563, "o", "\u001B[?25h"]
[3.563, "o", "\u001B[38;5;2mDone\u001B[0m\r\n"]
[3.563, "o", "Firing up the engines "]
[3.563, "o", "\u001B[?25l"]
[3.563, "o", "[ ]"]
[3.563, "o", "\u001B[6D"]
[3.672, "o", "\u001B[?25l"]
[3.672, "o", "[= ]"]
[3.672, "o", "\u001B[6D"]
[3.75, "o", "\u001B[?25l"]
[3.75, "o", "[== ]"]
[3.75, "o", "\u001B[6D"]
[3.844, "o", "\u001B[?25l"]
[3.844, "o", "[=== ]"]
[3.844, "o", "\u001B[6D"]
[3.953, "o", "\u001B[?25l"]
[3.953, "o", "[ ===]"]
[3.953, "o", "\u001B[6D"]
[4.047, "o", "\u001B[?25l"]
[4.047, "o", "[ ==]"]
[4.047, "o", "\u001B[6D"]
[4.157, "o", "\u001B[?25l"]
[4.157, "o", "[ =]"]
[4.157, "o", "\u001B[6D"]
[4.25, "o", "\u001B[?25l"]
[4.25, "o", "[ ]"]
[4.25, "o", "\u001B[6D"]
[4.344, "o", "\u001B[?25l"]
[4.344, "o", "[ =]"]
[4.344, "o", "\u001B[6D"]
[4.438, "o", "\u001B[?25l"]
[4.438, "o", "[ ==]"]
[4.438, "o", "\u001B[6D"]
[4.532, "o", "\u001B[?25l"]
[4.532, "o", "[ ===]"]
[4.532, "o", "\u001B[6D"]
[4.625, "o", "\u001B[?25l"]
[4.625, "o", "[====]"]
[4.625, "o", "\u001B[6D"]
[4.719, "o", "\u001B[?25l"]
[4.719, "o", "[=== ]"]
[4.719, "o", "\u001B[6D"]
[4.828, "o", "\u001B[?25l"]
[4.844, "o", "[== ]"]
[4.86, "o", "\u001B[6D"]
[4.953, "o", "\u001B[?25l"]
[4.953, "o", "[= ]"]
[4.953, "o", "\u001B[6D"]
[5.047, "o", "\u001B[?25l"]
[5.047, "o", "[ ]"]
[5.047, "o", "\u001B[6D"]
[5.141, "o", "\u001B[?25l"]
[5.141, "o", "[= ]"]
[5.141, "o", "\u001B[6D"]
[5.235, "o", "\u001B[?25l"]
[5.235, "o", "[== ]"]
[5.235, "o", "\u001B[6D"]
[5.328, "o", "\u001B[?25l"]
[5.328, "o", "[=== ]"]
[5.328, "o", "\u001B[6D"]
[5.422, "o", "\u001B[?25l"]
[5.422, "o", "[ ===]"]
[5.422, "o", "\u001B[6D"]
[5.532, "o", "\u001B[?25l"]
[5.532, "o", "[ ==]"]
[5.532, "o", "\u001B[6D"]
[5.625, "o", "\u001B[?25l"]
[5.625, "o", "[ =]"]
[5.625, "o", "\u001B[6D"]
[5.719, "o", "\u001B[?25l"]
[5.719, "o", "[ ]"]
[5.719, "o", "\u001B[6D"]
[5.813, "o", "\u001B[?25l"]
[5.813, "o", "[ =]"]
[5.813, "o", "\u001B[6D"]
[5.907, "o", "\u001B[?25l"]
[5.907, "o", "[ ==]"]
[5.907, "o", "\u001B[6D"]
[6, "o", "\u001B[?25l"]
[6, "o", "[ ===]"]
[6, "o", "\u001B[6D"]
[6.094, "o", "\u001B[?25l"]
[6.094, "o", "[====]"]
[6.094, "o", "\u001B[6D"]
[6.188, "o", "\u001B[?25l"]
[6.188, "o", "[=== ]"]
[6.188, "o", "\u001B[6D"]
[6.282, "o", "\u001B[?25l"]
[6.282, "o", "[== ]"]
[6.282, "o", "\u001B[6D"]
[6.375, "o", "\u001B[?25l"]
[6.375, "o", "[= ]"]
[6.375, "o", "\u001B[6D"]
[6.453, "o", "\u001B[?25l"]
[6.453, "o", "[ ]"]
[6.453, "o", "\u001B[6D"]
[6.547, "o", "\u001B[?25l"]
[6.547, "o", "[= ]"]
[6.547, "o", "\u001B[6D"]
[6.625, "o", "\u001B[?25l"]
[6.625, "o", "[== ]"]
[6.625, "o", "\u001B[6D"]
[6.703, "o", "\u001B[?25l"]
[6.703, "o", "[=== ]"]
[6.703, "o", "\u001B[6D"]
[6.797, "o", "\u001B[?25l"]
[6.797, "o", "[ ===]"]
[6.797, "o", "\u001B[6D"]
[6.891, "o", "\u001B[?25l"]
[6.891, "o", "[ ==]"]
[6.891, "o", "\u001B[6D"]
[6.969, "o", " "]
[6.969, "o", "\u001B[6D"]
[6.969, "o", "\u001B[?25h"]
[6.969, "o", "\u001B[38;5;2mDone\u001B[0m\r\n"]
[6.969, "o", "Blasting into orbit "]
[6.969, "o", "\u001B[?25l"]
[6.969, "o", "\u2631"]
[6.969, "o", "\u001B[1D"]
[7.078, "o", "\u001B[?25l"]
[7.078, "o", "\u2632"]
[7.078, "o", "\u001B[1D"]
[7.172, "o", "\u001B[?25l"]
[7.172, "o", "\u2634"]
[7.172, "o", "\u001B[1D"]
[7.282, "o", "\u001B[?25l"]
[7.282, "o", "\u2631"]
[7.282, "o", "\u001B[1D"]
[7.375, "o", "\u001B[?25l"]
[7.375, "o", "\u2632"]
[7.375, "o", "\u001B[1D"]
[7.485, "o", "\u001B[?25l"]
[7.485, "o", "\u2634"]
[7.485, "o", "\u001B[1D"]
[7.594, "o", "\u001B[?25l"]
[7.594, "o", "\u2631"]
[7.594, "o", "\u001B[1D"]
[7.703, "o", "\u001B[?25l"]
[7.703, "o", "\u2632"]
[7.703, "o", "\u001B[1D"]
[7.813, "o", "\u001B[?25l"]
[7.813, "o", "\u2634"]
[7.813, "o", "\u001B[1D"]
[7.922, "o", "\u001B[?25l"]
[7.922, "o", "\u2631"]
[7.922, "o", "\u001B[1D"]
[8.032, "o", "\u001B[?25l"]
[8.032, "o", "\u2632"]
[8.032, "o", "\u001B[1D"]
[8.125, "o", "\u001B[?25l"]
[8.125, "o", "\u2634"]
[8.125, "o", "\u001B[1D"]
[8.235, "o", "\u001B[?25l"]
[8.235, "o", "\u2631"]
[8.235, "o", "\u001B[1D"]
[8.344, "o", "\u001B[?25l"]
[8.344, "o", "\u2632"]
[8.344, "o", "\u001B[1D"]
[8.453, "o", "\u001B[?25l"]
[8.453, "o", "\u2634"]
[8.453, "o", "\u001B[1D"]
[8.563, "o", "\u001B[?25l"]
[8.563, "o", "\u2631"]
[8.563, "o", "\u001B[1D"]
[8.657, "o", "\u001B[?25l"]
[8.657, "o", "\u2632"]
[8.657, "o", "\u001B[1D"]
[8.766, "o", "\u001B[?25l"]
[8.766, "o", "\u2634"]
[8.766, "o", "\u001B[1D"]
[8.86, "o", "\u001B[?25l"]
[8.86, "o", "\u2631"]
[8.86, "o", "\u001B[1D"]
[8.969, "o", "\u001B[?25l"]
[8.969, "o", "\u2632"]
[8.969, "o", "\u001B[1D"]
[9.078, "o", "\u001B[?25l"]
[9.078, "o", "\u2634"]
[9.078, "o", "\u001B[1D"]
[9.203, "o", "\u001B[?25l"]
[9.203, "o", "\u2631"]
[9.203, "o", "\u001B[1D"]
[9.313, "o", "\u001B[?25l"]
[9.313, "o", "\u2632"]
[9.313, "o", "\u001B[1D"]
[9.407, "o", "\u001B[?25l"]
[9.407, "o", "\u2634"]
[9.407, "o", "\u001B[1D"]
[9.516, "o", "\u001B[?25l"]
[9.516, "o", "\u2631"]
[9.516, "o", "\u001B[1D"]
[9.625, "o", "\u001B[?25l"]
[9.625, "o", "\u2632"]
[9.625, "o", "\u001B[1D"]
[9.719, "o", "\u001B[?25l"]
[9.719, "o", "\u2634"]
[9.735, "o", "\u001B[1D"]
[9.828, "o", "\u001B[?25l"]
[9.828, "o", "\u2631"]
[9.828, "o", "\u001B[1D"]
[9.938, "o", "\u001B[?25l"]
[9.938, "o", "\u2632"]
[9.938, "o", "\u001B[1D"]
[10, "o", " "]
[10, "o", "\u001B[1D"]
[10, "o", "\u001B[?25h"]
[10, "o", "\u001B[38;5;9mOh no\u001B[0m\r\n"]

View File

@ -85,10 +85,8 @@ on the main thread.
### Unit Testing Best Practices ### Unit Testing Best Practices
For testing of console output, Spectre.Console has [`IAnsiConsole`](xref:T:Spectre.Console.IAnsiConsole) that can be For testing of console output, Spectre.Console has [`IAnsiConsole`](xref:T:Spectre.Console.IAnsiConsole) that can be
injected into your application. injected into your application. The [Spectre.Console.Test](https://www.nuget.org/packages/Spectre.Console.Testing/)
The [Spectre.Console.Test](https://www.nuget.org/packages/Spectre.Console.Testing/) contains a set of utilities for NuGet package contains utilities for capturing the console output for verification. See the [Unit Testing](cli/unit-testing) page for further guidance.
capturing the output for verification, either manually or via a tool such
as [Verify](https://github.com/VerifyTests/Verify).
### Analyzer for Best Practices ### Analyzer for Best Practices

View File

@ -26,9 +26,9 @@ New features have been added, such as the ability to show separators between tab
## Rendering ## Rendering
* Add .NET 8 support by [@patriksvensson](https://github.com/patriksvensson) in [#1367](https://github.com/spectreconsole/spectre.console/pull/1367) * Add .NET 8 support by [@patriksvensson](https://github.com/patriksvensson) in [#1367](https://github.com/spectreconsole/spectre.console/pull/1367)
* Fixed render issue where writeline inside status caused corrupt output #415 #694 by [@fredrikbentzen](https://github.com/fredrikbentzen) in [#1132](https://github.com/spectreconsole/spectre.console/pull/1132)) * Fixed render issue where writeline inside status caused corrupt output #415 #694 by [@fredrikbentzen](https://github.com/fredrikbentzen) in [#1132](https://github.com/spectreconsole/spectre.console/pull/1132)
* Relax the SDK requirements by rolling forward to the latest feature by [@0xced](https://github.com/0xced) in [#1237](https://github.com/spectreconsole/spectre.console/pull/1237)) * Relax the SDK requirements by rolling forward to the latest feature by [@0xced](https://github.com/0xced) in [#1237](https://github.com/spectreconsole/spectre.console/pull/1237)
* Add fix to avoid exception on rows with no children by [@jeppevammenkristensen](https://github.com/jeppevammenkristensen) in [#1241](https://github.com/spectreconsole/spectre.console/pull/1241)) * Add fix to avoid exception on rows with no children by [@jeppevammenkristensen](https://github.com/jeppevammenkristensen) in [#1241](https://github.com/spectreconsole/spectre.console/pull/1241)
* Set `end_of_line` to `LF` instead of `CRLF` by [@0xced](https://github.com/0xced) in [#1256](https://github.com/spectreconsole/spectre.console/pull/1256) * Set `end_of_line` to `LF` instead of `CRLF` by [@0xced](https://github.com/0xced) in [#1256](https://github.com/spectreconsole/spectre.console/pull/1256)
* Fix `Rule` widget docs by [@tomaszprasolek](https://github.com/tomaszprasolek) in [#1257](https://github.com/spectreconsole/spectre.console/pull/1257) * Fix `Rule` widget docs by [@tomaszprasolek](https://github.com/tomaszprasolek) in [#1257](https://github.com/spectreconsole/spectre.console/pull/1257)
* Added the missing columns-cast by [@nils](https://github.com/nils)-a in [#1294](https://github.com/spectreconsole/spectre.console/pull/1294) * Added the missing columns-cast by [@nils](https://github.com/nils)-a in [#1294](https://github.com/spectreconsole/spectre.console/pull/1294)
@ -46,7 +46,7 @@ New features have been added, such as the ability to show separators between tab
## CLI ## CLI
* Add async command unit tests by [@FrankRay78](https://github.com/FrankRay78) in [#1228](https://github.com/spectreconsole/spectre.console/pull/1228) * Add async command unit tests by [@FrankRay78](https://github.com/FrankRay78) in [#1228](https://github.com/spectreconsole/spectre.console/pull/1228)
* Add support for async delegate by [@icalvo](https://github.com/icalvo) in [#1215](https://github.com/spectreconsole/spectre.console/pull/1215)) * Add support for async delegate by [@icalvo](https://github.com/icalvo) in [#1215](https://github.com/spectreconsole/spectre.console/pull/1215)
* Remove unnecessary `[NotNull]` attributes by [@0xced](https://github.com/0xced) in [#1255](https://github.com/spectreconsole/spectre.console/pull/1255) * Remove unnecessary `[NotNull]` attributes by [@0xced](https://github.com/0xced) in [#1255](https://github.com/spectreconsole/spectre.console/pull/1255)
* Allow custom help providers by [@FrankRay78](https://github.com/FrankRay78) in [#1259](https://github.com/spectreconsole/spectre.console/pull/1259) * Allow custom help providers by [@FrankRay78](https://github.com/FrankRay78) in [#1259](https://github.com/spectreconsole/spectre.console/pull/1259)
* Specified details for settings for the argument vector by [@nils](https://github.com/nils)-a in [#1301](https://github.com/spectreconsole/spectre.console/pull/1301) * Specified details for settings for the argument vector by [@nils](https://github.com/nils)-a in [#1301](https://github.com/spectreconsole/spectre.console/pull/1301)

View File

@ -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)

View File

@ -67,7 +67,7 @@ registrations.AddSingleton<IGreeter, HelloWorldGreeter>();
// Create a type registrar and register any dependencies. // Create a type registrar and register any dependencies.
// A type registrar is an adapter for a DI framework. // A type registrar is an adapter for a DI framework.
var registrar = new TypeRegistrar(registrations); var registrar = new MyTypeRegistrar(registrations);
// Create a new command app with the registrar // Create a new command app with the registrar
// and run it with the provided arguments. // and run it with the provided arguments.
@ -75,10 +75,13 @@ var app = new CommandApp<DefaultCommand>(registrar);
return app.Run(args); return app.Run(args);
``` ```
`TypeRegistrar` is a custom class that must be created by the user. This [example using `Microsoft.Extensions.DependencyInjection` as the container](https://github.com/spectreconsole/examples/tree/main/examples/Cli/Injection) provides an example `TypeRegistrar` and `TypeResolver` that can be added to your application with small adjustments for your DI container. <?# Alert ?>
`MyTypeRegistrar` is a custom class that implements [ITypeRegistrar](xref:T:Spectre.Console.Cli.ITypeRegistrar) and must be provided by the user.
<?#/ Alert ?>
Hint: If you do write your own implementation of `TypeRegistrar` and `TypeResolver` and you have some form of unit tests in place for your project, There is a working [example of dependency injection](https://github.com/spectreconsole/examples/tree/main/examples/Cli/Injection) that uses `Microsoft.Extensions.DependencyInjection` as the container. Example implementations of `ITypeRegistrar` and `ITypeResolver` are provided, which you can copy and paste to your application for dependency injection.
there is a utility `TypeRegistrarBaseTests` available that can be used to ensure your implementations adhere to the required implementation. Simply call `TypeRegistrarBaseTests.RunAllTests()` and expect no `TypeRegistrarBaseTests.TestFailedException` to be thrown.
Unit testing your `ITypeRegistrar` and `ITypeResolver` implementations is done using the utility `TypeRegistrarBaseTests` included in `Spectre.Console.Testing`. Simply call `TypeRegistrarBaseTests.RunAllTests()` and expect no `TypeRegistrarBaseTests.TestFailedException` to be thrown.
## Interception ## Interception
Interceptors can be registered with the `TypeRegistrar` (or with a custom DI-Container). Alternatively, `CommandApp` also provides a `SetInterceptor` configuration. Interceptors can be registered with the `TypeRegistrar` (or with a custom DI-Container). Alternatively, `CommandApp` also provides a `SetInterceptor` configuration.
@ -89,4 +92,4 @@ This provides an opportunity to modify the result and also to tear down any infr
The `Intercept`-Method of each interceptor is run before the command is executed and the `InterceptResult`-Method is run after it. These are typically used for configuring logging or other infrastructure concerns. The `Intercept`-Method of each interceptor is run before the command is executed and the `InterceptResult`-Method is run after it. These are typically used for configuring logging or other infrastructure concerns.
For an example of using the interceptor to configure logging, see the [Serilog demo](https://github.com/spectreconsole/examples/tree/main/examples/Cli/Logging). For an example of using the interceptor to configure logging, see the [Serilog demo](https://github.com/spectreconsole/examples/tree/main/examples/Cli/Logging)

View File

@ -0,0 +1,199 @@
Title: Unit Testing
Order: 14
Description: Instructions for unit testing a Spectre.Console application.
Reference:
- T:Spectre.Console.Testing.CommandAppTester
- T:Spectre.Console.Testing.TestConsole
- T:Spectre.Console.Testing.TestConsoleInput
---
`Spectre.Console` has a separate project that contains test harnesses for unit testing your own console applications.
The fastest way of getting started is to install the `Spectre.Console.Testing` NuGet package.
```text
> dotnet add package Spectre.Console.Testing
```
`Spectre.Console.Testing` is also the namespace containing the test classes.
## Testing a CommandApp
The `CommandAppTester` is a test implementation of `CommandApp` that's configured in a similar manner but designed for unit testing.
The following example validates the exit code and terminal output of a `Spectre.Console` command:
```csharp
/// <summary>
/// A Spectre.Console Command
/// </summary>
public class HelloWorldCommand : Command
{
private readonly IAnsiConsole _console;
public HelloWorldCommand(IAnsiConsole console)
{
// nb. AnsiConsole should not be called directly by the command
// since this doesn't play well with testing. Instead,
// the command should inject a IAnsiConsole and use that.
_console = console;
}
public override int Execute(CommandContext context)
{
_console.WriteLine("Hello world.");
return 0;
}
}
[TestMethod]
public void Should_Output_Hello_World()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<HelloWorldCommand>();
// When
var result = app.Run();
// Then
Assert.AreEqual(result.ExitCode, 0);
Assert.AreEqual(result.Output, "Hello world.");
}
```
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)
{
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.
The following example renders some widgets before then validating the console output:
```csharp
[TestMethod]
public void Should_Render_Panel()
{
// Given
var console = new TestConsole();
// When
console.Write(new Panel(new Text("Hello World")));
// Then
Assert.AreEqual(console.Output, """"
┌─────────────┐
Hello World
└─────────────┘
"""");
}
```
While `Assert` is fine for validating simple output, more complex output may benefit from a tool like [Verify](https://github.com/VerifyTests/Verify).
The following example prompts the user for input before then validating the expected choice was made:
```csharp
[TestMethod]
public void Should_Select_Orange()
{
// Given
var console = new TestConsole();
console.Input.PushTextWithEnter("Orange");
// When
console.Prompt(
new TextPrompt<string>("Favorite fruit?")
.AddChoice("Banana")
.AddChoice("Orange"));
// Then
Assert.AreEqual(console.Output, "Favorite fruit? [Banana/Orange]: Orange\n");
}
```
`CommandAppTester` uses `TestConsole` internally, which in turn uses `TestConsoleInput`, offering a fully testable harness for `Spectre.Console` widgets, prompts and commands.

View File

@ -6,7 +6,7 @@ Order: 0
Spectre.Console is a `.NET` library that makes it easier Spectre.Console is a `.NET` library that makes it easier
to create beautiful console applications. to create beautiful console applications.
## Spectre.Console.AnsiConsole Features ## Spectre.Console.AnsiConsole
* Easily output text with different colors and even styles such as bold, italic and blinking with a Rich inspired [markup language](markup). * Easily output text with different colors and even styles such as bold, italic and blinking with a Rich inspired [markup language](markup).
* Supports `3`/`4`/`8`/`24`-bit colors in the terminal with auto-detection of the current terminal's capabilities. * Supports `3`/`4`/`8`/`24`-bit colors in the terminal with auto-detection of the current terminal's capabilities.
@ -14,16 +14,19 @@ to create beautiful console applications.
* Display progress for long running tasks with live displays of [progress](live/progress) and [status](live/status) controls. * Display progress for long running tasks with live displays of [progress](live/progress) and [status](live/status) controls.
* Prompt user input with strongly typed [text input](prompts/text) or via [single-item select](prompts/selection) and [multiple item select](prompts/multiselection) controls. * Prompt user input with strongly typed [text input](prompts/text) or via [single-item select](prompts/selection) and [multiple item select](prompts/multiselection) controls.
* Format .NET [exceptions](exceptions) with custom color coded themes and styles. * Format .NET [exceptions](exceptions) with custom color coded themes and styles.
* Written with unit testing in mind.
Spectre.Console.AnsiConsole has been heavily inspired Spectre.Console.AnsiConsole has been heavily inspired by the excellent [Rich](https://github.com/willmcgugan/rich) library for Python written by Will McGugan.
by the excellent [Rich](https://github.com/willmcgugan/rich) library
for Python written by Will McGugan.
## Spectre.Console.Cli ## Spectre.Console.Cli
* Create strongly typed settings and commands for parsing `args[]` to create complex command line applications like `git`, `gh`, or `dotnet` * Create strongly typed settings and commands for parsing `args[]` to create complex command line applications like `git`, `gh`, or `dotnet`
## Spectre.Console.Testing
* Spectre.Console has been developed with unit testing in mind. The Spectre.Console library itself is covered by an extensive test suite, project maintainers require test coverage for all new commits, and the same extension points and test harnesses used internally for testing are available to you.
* The [Unit Testing](cli/unit-testing) page provides instructions for testing a Spectre.Console application.
## Examples ## Examples
![Sample of Spectre.Console output](./assets/images/example.png) ![Sample of Spectre.Console output](./assets/images/example.png)
@ -36,3 +39,4 @@ for Python written by Will McGugan.
Sorry, your browser doesn't support embedded videos. Sorry, your browser doesn't support embedded videos.
</video> </video>
The Spectre.Console [examples repository](https://github.com/spectreconsole/examples) contains many other examples.

73
docs/input/live/async.md Normal file
View File

@ -0,0 +1,73 @@
Title: Async Extensions
Order: 11
Description: "Async Extensions provides extension methods for running tasks with an inline animations."
Highlights:
- Extension methods for running tasks with spinner animations
- Support for both void and generic Task types
- Customizable spinner styles and console output
Reference:
- T:Spectre.Console.Extensions.SpinnerExtensions
Xref: spinner-extensions
---
The Async Spinner Extension provides convenient extension methods for running tasks with an inline spinner animations in the console.
<?# AsciiCast cast="await-spinner" /?>
<?# Alert ?>
The spinner animation is not thread safe, and using it together with other interactive
components such as prompts, progress displays or other status displays is not supported.
<?#/ Alert ?>
## Usage
The extension methods allow you to easily add spinner animations to any Task execution:
```csharp
// Basic usage with void Task
await someTask.Spinner();
// With generic Task<T>
var result = await someTaskWithResult.Spinner(
Spinner.Known.Star,
new Style(foreground: Color.Green));
// With custom console
await someTask.Spinner(
Spinner.Known.Dots,
style: Style.Plain,
ansiConsole: customConsole);
```
## Features
The spinner extensions provide:
- Support for both void Tasks and Tasks with return values
- Customizable spinner animations using any Spectre.Console Spinner
- Optional styling for the spinner animation
- Ability to specify a custom IAnsiConsole instance
## Examples
Here's a more complete example showing different ways to use the spinner extensions:
```csharp
// Basic spinner with default settings
await Task.Delay(1000)
.Spinner(Spinner.Known.Dots);
// Customized spinner with style
var result = await CalculateSomething()
.Spinner(
Spinner.Known.Star,
new Style(foreground: Color.Green));
// Using with a custom console
await ProcessData()
.Spinner(
new Spinner(new[] { "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏" }, 80),
new Style(foreground: Color.Blue),
customConsole);
```

View File

@ -17,7 +17,7 @@ and the ascii terminal player.
/* temp styling for alerts */ /* temp styling for alerts */
.alert-warning { .alert-warning {
@apply p-4 border border-yellow-300 bg-yellow-100 text-yellow-800 dark:border-orange-700/50 dark:bg-orange-800/50 dark:text-orange-300/90 rounded shadow-sm text-sm; @apply p-4 border border-yellow-300 bg-yellow-100 text-yellow-800 dark:border-red-700/50 dark:bg-red-800/50 dark:text-red-100/90 rounded shadow-sm text-sm;
} }
.alert-warning p { .alert-warning p {

View 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));
```

View File

@ -137,4 +137,11 @@ table.Columns[0].NoWrap();
```csharp ```csharp
// Set the column width // Set the column width
table.Columns[0].Width(15); table.Columns[0].Width(15);
```
### Show row separators
```csharp
// Shows separator between each row
table.ShowRowSeparators();
``` ```

282
docs/package-lock.json generated
View File

@ -123,12 +123,13 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.17.0", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==", "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.14.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -315,12 +316,13 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -475,10 +477,11 @@
} }
}, },
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
@ -500,6 +503,13 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true,
"license": "MIT"
},
"node_modules/defined": { "node_modules/defined": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
@ -587,10 +597,11 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
}, },
@ -737,6 +748,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
} }
@ -802,13 +814,14 @@
} }
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.4", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"braces": "^3.0.1", "braces": "^3.0.3",
"picomatch": "^2.2.3" "picomatch": "^2.3.1"
}, },
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
@ -824,16 +837,27 @@
} }
}, },
"node_modules/minimist": { "node_modules/minimist": {
"version": "1.2.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.2.0", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true, "dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@ -931,10 +955,11 @@
} }
}, },
"node_modules/picocolors": { "node_modules/picocolors": {
"version": "1.0.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true "dev": true,
"license": "ISC"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.1",
@ -949,21 +974,32 @@
} }
}, },
"node_modules/postcss": { "node_modules/postcss": {
"version": "8.4.6", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"dev": true, "dev": true,
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": { "dependencies": {
"nanoid": "^3.2.0", "nanoid": "^3.3.8",
"picocolors": "^1.0.0", "picocolors": "^1.1.1",
"source-map-js": "^1.0.2" "source-map-js": "^1.2.1"
}, },
"engines": { "engines": {
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
} }
}, },
"node_modules/postcss-js": { "node_modules/postcss-js": {
@ -1093,10 +1129,11 @@
} }
}, },
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.13.9", "version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/resolve": { "node_modules/resolve": {
"version": "1.22.0", "version": "1.22.0",
@ -1157,6 +1194,29 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/seroval": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.2.1.tgz",
"integrity": "sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/seroval-plugins": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.2.1.tgz",
"integrity": "sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"seroval": "^1.0"
}
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -1179,16 +1239,23 @@
} }
}, },
"node_modules/solid-js": { "node_modules/solid-js": {
"version": "1.3.5", "version": "1.9.5",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.5.tgz",
"integrity": "sha512-PUom2cCARfvvgxI7cwOhfXMrZZZxjp+vIrb5fzVNBFyICy8A30wTqExwfUv457eJYgKpii2D3qStW9ILtKnShw==", "integrity": "sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==",
"dev": true "dev": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.1.0",
"seroval": "^1.1.0",
"seroval-plugins": "^1.1.0"
}
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.0.2", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true, "dev": true,
"license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -1274,6 +1341,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"
}, },
@ -1407,12 +1475,12 @@
} }
}, },
"@babel/runtime": { "@babel/runtime": {
"version": "7.17.0", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.0.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==", "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"dev": true, "dev": true,
"requires": { "requires": {
"regenerator-runtime": "^0.13.4" "regenerator-runtime": "^0.14.0"
} }
}, },
"@nodelib/fs.scandir": { "@nodelib/fs.scandir": {
@ -1547,12 +1615,12 @@
"dev": true "dev": true
}, },
"braces": { "braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"requires": { "requires": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
} }
}, },
"browserslist": { "browserslist": {
@ -1652,9 +1720,9 @@
} }
}, },
"cross-spawn": { "cross-spawn": {
"version": "7.0.3", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true, "dev": true,
"requires": { "requires": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
@ -1668,6 +1736,12 @@
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true "dev": true
}, },
"csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
},
"defined": { "defined": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz",
@ -1743,9 +1817,9 @@
} }
}, },
"fill-range": { "fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"requires": { "requires": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
@ -1905,13 +1979,13 @@
"dev": true "dev": true
}, },
"micromatch": { "micromatch": {
"version": "4.0.4", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true, "dev": true,
"requires": { "requires": {
"braces": "^3.0.1", "braces": "^3.0.3",
"picomatch": "^2.2.3" "picomatch": "^2.3.1"
} }
}, },
"mini-svg-data-uri": { "mini-svg-data-uri": {
@ -1921,15 +1995,15 @@
"dev": true "dev": true
}, },
"minimist": { "minimist": {
"version": "1.2.5", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true "dev": true
}, },
"nanoid": { "nanoid": {
"version": "3.2.0", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.2.0.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-fmsZYa9lpn69Ad5eDn7FMcnnSR+8R34W9qJEijxYhTbfOWzr22n1QxCMzXLK+ODyW2973V3Fux959iQoUxzUIA==", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true "dev": true
}, },
"node-releases": { "node-releases": {
@ -1998,9 +2072,9 @@
"dev": true "dev": true
}, },
"picocolors": { "picocolors": {
"version": "1.0.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true "dev": true
}, },
"picomatch": { "picomatch": {
@ -2010,14 +2084,14 @@
"dev": true "dev": true
}, },
"postcss": { "postcss": {
"version": "8.4.6", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"dev": true, "dev": true,
"requires": { "requires": {
"nanoid": "^3.2.0", "nanoid": "^3.3.8",
"picocolors": "^1.0.0", "picocolors": "^1.1.1",
"source-map-js": "^1.0.2" "source-map-js": "^1.2.1"
} }
}, },
"postcss-js": { "postcss-js": {
@ -2086,9 +2160,9 @@
} }
}, },
"regenerator-runtime": { "regenerator-runtime": {
"version": "0.13.9", "version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"dev": true "dev": true
}, },
"resolve": { "resolve": {
@ -2123,6 +2197,19 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"seroval": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/seroval/-/seroval-1.2.1.tgz",
"integrity": "sha512-yBxFFs3zmkvKNmR0pFSU//rIsYjuX418TnlDmc2weaq5XFDqDIV/NOMPBoLrbxjLH42p4UzRuXHryXh9dYcKcw==",
"dev": true
},
"seroval-plugins": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/seroval-plugins/-/seroval-plugins-1.2.1.tgz",
"integrity": "sha512-H5vs53+39+x4Udwp4J5rNZfgFuA+Lt+uU+09w1gYBVWomtAl98B+E9w7yC05Xc81/HgLvJdlyqJbU0fJCKCmdw==",
"dev": true,
"requires": {}
},
"shebang-command": { "shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -2139,15 +2226,20 @@
"dev": true "dev": true
}, },
"solid-js": { "solid-js": {
"version": "1.3.5", "version": "1.9.5",
"resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.3.5.tgz", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.5.tgz",
"integrity": "sha512-PUom2cCARfvvgxI7cwOhfXMrZZZxjp+vIrb5fzVNBFyICy8A30wTqExwfUv457eJYgKpii2D3qStW9ILtKnShw==", "integrity": "sha512-ogI3DaFcyn6UhYhrgcyRAMbu/buBJitYQASZz5WzfQVPP10RD2AbCoRZ517psnezrasyCbWzIxZ6kVqet768xw==",
"dev": true "dev": true,
"requires": {
"csstype": "^3.1.0",
"seroval": "^1.1.0",
"seroval-plugins": "^1.1.0"
}
}, },
"source-map-js": { "source-map-js": {
"version": "1.0.2", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true "dev": true
}, },
"supports-color": { "supports-color": {

View File

@ -99,8 +99,7 @@ public class Api : Pipeline
new ConcatDocuments(nameof(Code)), new ConcatDocuments(nameof(Code)),
new CacheDocuments( new CacheDocuments(
new AnalyzeCSharp() new AnalyzeCSharp()
.WhereNamespaces(ns => ns.StartsWith("Spectre.Console") && !ns.Contains("Analyzer") && .WhereNamespaces(ns => ns.StartsWith("Spectre.Console") && !ns.Contains("Analyzer") && !ns.Contains("Examples"))
!ns.Contains("Testing") && !ns.Contains("Examples"))
.WherePublic(true) .WherePublic(true)
.WithCssClasses("code", "cs") .WithCssClasses("code", "cs")
.WithDestinationPrefix("api") .WithDestinationPrefix("api")

View File

@ -1,7 +1,7 @@
{ {
"$schema": "http://json.schemastore.org/global", "$schema": "http://json.schemastore.org/global",
"sdk": { "sdk": {
"version": "9.0.100", "version": "9.0.202",
"rollForward": "latestFeature" "rollForward": "latestFeature"
} }
} }

View File

@ -0,0 +1,36 @@
using System.Threading.Tasks;
using Generator.Commands.Samples;
using Spectre.Console;
using Spectre.Console.Extensions;
namespace Generator.Commands.AsciiCast.Samples;
public class AwaitSpinnerSample : BaseSample
{
private static async Task DoSomethingAsync(int value)
{
await Task.Delay(value);
}
public override (int Cols, int Rows) ConsoleSize { get; } = (40, 4);
public override void Run(IAnsiConsole console)
{
Task.Run(async () =>
{
AnsiConsole.Write("Loading the rocket ship ");
await DoSomethingAsync(3500).Spinner(Spinner.Known.Dots);
AnsiConsole.MarkupLine("[green]Done[/]");
AnsiConsole.Write("Firing up the engines ");
await DoSomethingAsync(3400).Spinner(Spinner.Known.BouncingBar);
AnsiConsole.MarkupLine("[green]Done[/]");
AnsiConsole.Write("Blasting into orbit ");
await DoSomethingAsync(3025).Spinner(Spinner.Known.Hamburger);
AnsiConsole.MarkupLine("[red]Oh no[/]");
}).Wait();
}
}

View File

@ -43,10 +43,10 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AngleSharp" Version="1.1.2" /> <PackageReference Include="AngleSharp" Version="1.3.0" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" /> <PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Scriban" Version="5.12.0" /> <PackageReference Include="Scriban" Version="6.2.1" />
<PackageReference Include="Spectre.IO" Version="0.18.0" /> <PackageReference Include="Spectre.IO" Version="0.18.0" />
</ItemGroup> </ItemGroup>

View File

@ -11,7 +11,7 @@
<SignAssembly>true</SignAssembly> <SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\..\resources\spectre.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>$(MSBuildThisFileDirectory)\..\resources\spectre.snk</AssemblyOriginatorKeyFile>
<PublicKey>00240000048000009400000006020000002400005253413100040000010001006146d3789d31477cf4a3b508dcf772ff9ccad8613f6bd6b17b9c4a960a7a7b551ecd22e4f4119ced70ee8bbdf3ca0a117c99fd6248c16255ea9033110c2233d42e74e81bf4f3f7eb09bfe8b53ad399d957514f427171a86f5fe9fe0014be121d571c80c4a0cfc3531bdbf5a2900d936d93f2c94171b9134f7644a1ac3612a0d0</PublicKey> <PublicKey>00240000048000009400000006020000002400005253413100040000010001006146d3789d31477cf4a3b508dcf772ff9ccad8613f6bd6b17b9c4a960a7a7b551ecd22e4f4119ced70ee8bbdf3ca0a117c99fd6248c16255ea9033110c2233d42e74e81bf4f3f7eb09bfe8b53ad399d957514f427171a86f5fe9fe0014be121d571c80c4a0cfc3531bdbf5a2900d936d93f2c94171b9134f7644a1ac3612a0d0</PublicKey>
<Version>1.0.0</Version> <Version>1.0.3</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Deterministic Build" Condition="'$(GITHUB_ACTIONS)' == 'true'"> <PropertyGroup Label="Deterministic Build" Condition="'$(GITHUB_ACTIONS)' == 'true'">

View File

@ -1,25 +1,27 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally> <ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Dependencies">
<PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0" /> <ItemGroup>
<PackageVersion Include="Wcwidth.Sources" Version="2.0.0" /> <PackageVersion Include="IsExternalInit" Version="1.0.3"/>
<PackageVersion Include="IsExternalInit" Version="1.0.3" /> <PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6"/>
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" /> <PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1"/>
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> <PackageVersion Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0"/>
<PackageVersion Include="PolySharp" Version="1.15.0" /> <PackageVersion Include="MinVer" PrivateAssets="All" Version="6.0.0"/>
<PackageVersion Include="Shouldly" Version="4.2.1" /> <PackageVersion Include="PolySharp" Version="1.15.0"/>
<PackageVersion Include="Spectre.Verify.Extensions" Version="22.3.2-preview.0.1" /> <PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.13.1"/>
<PackageVersion Include="Verify.Xunit" Version="28.2.1" /> <PackageVersion Include="Shouldly" Version="4.3.0"/>
<PackageVersion Include="xunit" Version="2.9.2" /> <PackageVersion Include="SixLabors.ImageSharp" Version="3.1.10"/>
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" /> <PackageVersion Include="Spectre.Verify.Extensions" Version="28.16.0"/>
<PackageVersion Include="System.Memory" Version="4.6.0" /> <PackageVersion Include="StyleCop.Analyzers" PrivateAssets="All" Version="1.2.0-beta.556"/>
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.5" /> <PackageVersion Include="System.Memory" Version="4.6.3"/>
<PackageVersion Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160" /> <PackageVersion Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" Version="1.0.0-alpha.160"/>
</ItemGroup> <PackageVersion Include="Verify.Xunit" Version="30.4.0"/>
<ItemGroup Label="Static Analysis"> <PackageVersion Include="Wcwidth.Sources" Version="2.0.0"/>
<PackageVersion Include="StyleCop.Analyzers" PrivateAssets="All" Version="1.2.0-beta.556" /> <PackageVersion Include="xunit" Version="2.9.3"/>
<PackageVersion Include="Roslynator.Analyzers" PrivateAssets="All" Version="4.12.9" /> <PackageVersion Include="xunit.runner.visualstudio" Version="3.1.1"/>
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -45,6 +45,6 @@ public sealed class CommandArgumentAttribute : Attribute
// Assign the result. // Assign the result.
Position = position; Position = position;
ValueName = result.Value; ValueName = result.Value;
IsRequired = result.Required; IsRequired = result.IsRequired;
} }
} }

View File

@ -30,6 +30,11 @@ public sealed class CommandOptionAttribute : Attribute
/// </summary> /// </summary>
public bool ValueIsOptional { get; } public bool ValueIsOptional { get; }
/// <summary>
/// Gets a value indicating whether the value is required.
/// </summary>
public bool IsRequired { get; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether this option is hidden from the help text. /// Gets or sets a value indicating whether this option is hidden from the help text.
/// </summary> /// </summary>
@ -39,7 +44,8 @@ public sealed class CommandOptionAttribute : Attribute
/// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class. /// Initializes a new instance of the <see cref="CommandOptionAttribute"/> class.
/// </summary> /// </summary>
/// <param name="template">The option template.</param> /// <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) if (template == null)
{ {
@ -54,6 +60,7 @@ public sealed class CommandOptionAttribute : Attribute
ShortNames = result.ShortNames; ShortNames = result.ShortNames;
ValueName = result.Value; ValueName = result.Value;
ValueIsOptional = result.ValueIsOptional; ValueIsOptional = result.ValueIsOptional;
IsRequired = isRequired;
} }
internal bool IsMatch(string name) internal bool IsMatch(string name)

View File

@ -13,13 +13,13 @@ public abstract class AsyncCommand : ICommand<EmptyCommandSettings>
public abstract Task<int> ExecuteAsync(CommandContext context); public abstract Task<int> ExecuteAsync(CommandContext context);
/// <inheritdoc/> /// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings) Task<int> ICommand<EmptyCommandSettings>.ExecuteAsync(CommandContext context, EmptyCommandSettings settings)
{ {
return ExecuteAsync(context); return ExecuteAsync(context);
} }
/// <inheritdoc/> /// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings) Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings)
{ {
return ExecuteAsync(context); return ExecuteAsync(context);
} }

View File

@ -33,14 +33,14 @@ public abstract class AsyncCommand<TSettings> : ICommand<TSettings>
} }
/// <inheritdoc/> /// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings) Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings)
{ {
Debug.Assert(settings is TSettings, "Command settings is of unexpected type."); Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
return ExecuteAsync(context, (TSettings)settings); return ExecuteAsync(context, (TSettings)settings);
} }
/// <inheritdoc/> /// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings) Task<int> ICommand<TSettings>.ExecuteAsync(CommandContext context, TSettings settings)
{ {
return ExecuteAsync(context, settings); return ExecuteAsync(context, settings);
} }

View File

@ -14,13 +14,13 @@ public abstract class Command : ICommand<EmptyCommandSettings>
public abstract int Execute(CommandContext context); public abstract int Execute(CommandContext context);
/// <inheritdoc/> /// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings) Task<int> ICommand<EmptyCommandSettings>.ExecuteAsync(CommandContext context, EmptyCommandSettings settings)
{ {
return Task.FromResult(Execute(context)); return Task.FromResult(Execute(context));
} }
/// <inheritdoc/> /// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings) Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings)
{ {
return Task.FromResult(Execute(context)); return Task.FromResult(Execute(context));
} }

View File

@ -85,7 +85,7 @@ public sealed class CommandApp : ICommandApp
} }
return await _executor return await _executor
.Execute(_configurator, args) .ExecuteAsync(_configurator, args)
.ConfigureAwait(false); .ConfigureAwait(false);
} }
catch (Exception ex) catch (Exception ex)

View File

@ -34,14 +34,14 @@ public abstract class Command<TSettings> : ICommand<TSettings>
} }
/// <inheritdoc/> /// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings) Task<int> ICommand.ExecuteAsync(CommandContext context, CommandSettings settings)
{ {
Debug.Assert(settings is TSettings, "Command settings is of unexpected type."); 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));
} }
/// <inheritdoc/> /// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings) Task<int> ICommand<TSettings>.ExecuteAsync(CommandContext context, TSettings settings)
{ {
return Task.FromResult(Execute(context, settings)); return Task.FromResult(Execute(context, settings));
} }

View File

@ -113,4 +113,9 @@ public sealed class CommandParseException : CommandRuntimeException
var text = $"[red]Error:[/] The value '[white]{value}[/]' is not in a correct format"; var text = $"[red]Error:[/] The value '[white]{value}[/]' is not in a correct format";
return new CommandParseException("Could not parse value", new Markup(text)); return new CommandParseException("Could not parse value", new Markup(text));
} }
internal static CommandParseException UnknownParsingError()
{
return new CommandParseException("An unknown error occured when parsing the arguments.");
}
} }

View File

@ -37,6 +37,16 @@ public class CommandRuntimeException : CommandAppException
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{argument.Value}'."); 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) internal static CommandRuntimeException NoConverterFound(CommandParameter parameter)
{ {
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'."); return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");

View File

@ -168,7 +168,7 @@ public static class ConfiguratorExtensions
} }
/// <summary> /// <summary>
/// Tells the help writer whether or not to trim trailing period. /// Tells the help provider whether or not to trim trailing period.
/// </summary> /// </summary>
/// <param name="configurator">The configurator.</param> /// <param name="configurator">The configurator.</param>
/// <param name="trimTrailingPeriods">True to trim trailing period (default), false to not.</param> /// <param name="trimTrailingPeriods">True to trim trailing period (default), false to not.</param>

View File

@ -22,7 +22,7 @@ public class HelpProvider : IHelpProvider
protected virtual bool ShowOptionDefaultValues { get; } protected virtual bool ShowOptionDefaultValues { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether a trailing period of a command description is trimmed in the help text. /// Gets a value indicating whether a trailing period of a description is trimmed in the help text.
/// </summary> /// </summary>
protected virtual bool TrimTrailingPeriod { get; } protected virtual bool TrimTrailingPeriod { get; }
@ -53,8 +53,8 @@ public class HelpProvider : IHelpProvider
{ {
var arguments = new List<HelpArgument>(); var arguments = new List<HelpArgument>();
arguments.AddRange(command?.Parameters?.OfType<ICommandArgument>()?.Select( 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>()); ?? Array.Empty<HelpArgument>());
return arguments; return arguments;
} }
} }
@ -65,15 +65,20 @@ public class HelpProvider : IHelpProvider
public string? Long { get; } public string? Long { get; }
public string? Value { get; } public string? Value { get; }
public bool? ValueIsOptional { get; } public bool? ValueIsOptional { get; }
public bool IsRequired { get; }
public string? Description { get; } public string? Description { get; }
public object? DefaultValue { 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; Short = @short;
Long = @long; Long = @long;
Value = value; Value = value;
ValueIsOptional = valueIsOptional; ValueIsOptional = valueIsOptional;
IsRequired = isRequired;
Description = description; Description = description;
DefaultValue = defaultValue; DefaultValue = defaultValue;
} }
@ -85,28 +90,41 @@ public class HelpProvider : IHelpProvider
{ {
var parameters = new List<HelpOption> 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 application // Version information applies to the entire CLI application.
// Include the "-v" option in the help when at the root of the command line application // Whether to show the "-v|--version" option in the help is determined as per:
// Don't allow the "-v" option if users have specified one or more sub-commands // - If an application version has been set, and
if ((command?.Parent == null) && !(command?.IsBranch ?? false)) // -- When at the root of the application, or
// -- When at the root of the application with a default command, unless
// --- The default command has a version option in its settings
if ((command?.Parent == null) && !(command?.IsBranch ?? false) && (command?.IsDefaultCommand ?? true))
{ {
// Only show the version command if there is an // Check whether the default command has a version option in its settings.
// application version set. var versionCommandOption = command?.Parameters?.OfType<CommandOption>()?.FirstOrDefault(o =>
if (model.ApplicationVersion != null) (o.ShortNames.FirstOrDefault(v => v.Equals("v", StringComparison.OrdinalIgnoreCase)) != null) ||
(o.LongNames.FirstOrDefault(v => v.Equals("version", StringComparison.OrdinalIgnoreCase)) != null));
// Only show the version option if the default command doesn't have a version option in its settings.
if (versionCommandOption == null)
{ {
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null)); // Only show the version option if there is an application version set.
if (model.ApplicationVersion != null)
{
parameters.Add(new HelpOption("v", "version", null, null, false,
resources.PrintVersionDescription, null));
}
} }
} }
parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o => parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o =>
new HelpOption( new HelpOption(
o.ShortNames.FirstOrDefault(), o.LongNames.FirstOrDefault(), 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)) o.IsFlag && o.DefaultValue?.Value is false ? null : o.DefaultValue?.Value))
?? Array.Empty<HelpOption>()); ?? Array.Empty<HelpOption>());
return parameters; return parameters;
} }
} }
@ -171,7 +189,7 @@ public class HelpProvider : IHelpProvider
var composer = NewComposer(); var composer = NewComposer();
composer.Style(helpStyles?.Description?.Header ?? Style.Plain, $"{resources.Description}:").LineBreak(); composer.Style(helpStyles?.Description?.Header ?? Style.Plain, $"{resources.Description}:").LineBreak();
composer.Text(command.Description).LineBreak(); composer.Text(NormalizeDescription(command.Description)).LineBreak();
yield return composer.LineBreak(); yield return composer.LineBreak();
} }
@ -204,7 +222,9 @@ public class HelpProvider : IHelpProvider
{ {
if (isCurrent) 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 else
{ {
@ -217,38 +237,46 @@ public class HelpProvider : IHelpProvider
if (isCurrent) if (isCurrent)
{ {
foreach (var argument in current.Parameters.OfType<ICommandArgument>() 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) if (optionalArguments.Length > 0 || !isCurrent)
{ {
foreach (var optionalArgument in optionalArguments) 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) 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) if (command.IsBranch && command.DefaultCommand == null)
{ {
// The user must specify the command // 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) else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
{ {
// We are on a branch with a default command // We are on a branch with a default command
// The user can optionally specify the 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) else if (command.IsDefaultCommand)
{ {
@ -258,7 +286,8 @@ public class HelpProvider : IHelpProvider
{ {
// Commands other than the default are present // Commands other than the default are present
// So make these optional in the usage statement // 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}]"));
} }
} }
} }
@ -327,7 +356,8 @@ public class HelpProvider : IHelpProvider
for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++) for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++)
{ {
var args = string.Join(" ", examples[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(); composer.LineBreak();
} }
@ -353,7 +383,8 @@ public class HelpProvider : IHelpProvider
var result = new List<IRenderable> 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(); var grid = new Grid();
@ -364,14 +395,14 @@ public class HelpProvider : IHelpProvider
{ {
grid.AddRow( grid.AddRow(
NewComposer().Style(helpStyles?.Arguments?.RequiredArgument ?? Style.Plain, $"<{argument.Name}>"), NewComposer().Style(helpStyles?.Arguments?.RequiredArgument ?? Style.Plain, $"<{argument.Name}>"),
NewComposer().Text(argument.Description?.TrimEnd('.') ?? " ")); NewComposer().Text(NormalizeDescription(argument.Description)));
} }
foreach (var argument in arguments.Where(x => !x.Required).OrderBy(x => x.Position)) foreach (var argument in arguments.Where(x => !x.Required).OrderBy(x => x.Position))
{ {
grid.AddRow( grid.AddRow(
NewComposer().Style(helpStyles?.Arguments?.OptionalArgument ?? Style.Plain, $"[{argument.Name}]"), NewComposer().Style(helpStyles?.Arguments?.OptionalArgument ?? Style.Plain, $"[{argument.Name}]"),
NewComposer().Text(argument.Description?.TrimEnd('.') ?? " ")); NewComposer().Text(NormalizeDescription(argument.Description)));
} }
result.Add(grid); result.Add(grid);
@ -396,7 +427,8 @@ public class HelpProvider : IHelpProvider
var result = new List<IRenderable> 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(); var helpOptions = parameters.ToArray();
@ -428,7 +460,15 @@ public class HelpProvider : IHelpProvider
columns.Add(GetDefaultValueForOption(option.DefaultValue)); columns.Add(GetDefaultValueForOption(option.DefaultValue));
} }
columns.Add(NewComposer().Text(option.Description?.TrimEnd('.') ?? " ")); 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()); grid.AddRow(columns.ToArray());
} }
@ -459,7 +499,8 @@ public class HelpProvider : IHelpProvider
var result = new List<IRenderable> 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(); var grid = new Grid();
@ -478,18 +519,9 @@ public class HelpProvider : IHelpProvider
arguments.Space(); arguments.Space();
} }
if (TrimTrailingPeriod) grid.AddRow(
{ NewComposer().Text(arguments.ToString().TrimEnd()),
grid.AddRow( NewComposer().Text(NormalizeDescription(child.Description)));
NewComposer().Text(arguments.ToString().TrimEnd()),
NewComposer().Text(child.Description?.TrimEnd('.') ?? " "));
}
else
{
grid.AddRow(
NewComposer().Text(arguments.ToString().TrimEnd()),
NewComposer().Text(child.Description ?? " "));
}
} }
result.Add(grid); result.Add(grid);
@ -544,11 +576,11 @@ public class HelpProvider : IHelpProvider
composer.Text(" "); composer.Text(" ");
if (option.ValueIsOptional ?? false) if (option.ValueIsOptional ?? false)
{ {
composer.Style(helpStyles?.Options?.OptionalOption ?? Style.Plain, $"[{option.Value}]"); composer.Style(helpStyles?.Options?.OptionalOptionValue ?? Style.Plain, $"[{option.Value}]");
} }
else else
{ {
composer.Style(helpStyles?.Options?.RequiredOption ?? Style.Plain, $"<{option.Value}>"); composer.Style(helpStyles?.Options?.RequiredOptionValue ?? Style.Plain, $"<{option.Value}>");
} }
} }
@ -562,8 +594,27 @@ public class HelpProvider : IHelpProvider
null => NewComposer().Text(" "), null => NewComposer().Text(" "),
"" => NewComposer().Text(" "), "" => NewComposer().Text(" "),
Array { Length: 0 } => 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))), Array array => NewComposer().Join(
_ => NewComposer().Style(helpStyles?.Options?.DefaultValue ?? Style.Plain, defaultValue?.ToString() ?? string.Empty), ", ",
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),
}; };
} }
private string NormalizeDescription(string? description)
{
if (description == null)
{
return " ";
}
return TrimTrailingPeriod
? description.TrimEnd('.')
: description;
}
} }

View File

@ -76,8 +76,8 @@ public sealed class HelpProviderStyle
Header = "yellow", Header = "yellow",
DefaultValueHeader = "lime", DefaultValueHeader = "lime",
DefaultValue = "bold", DefaultValue = "bold",
RequiredOption = "silver", RequiredOptionValue = "silver",
OptionalOption = "grey", OptionalOptionValue = "grey",
}, },
}; };
} }
@ -212,8 +212,13 @@ public sealed class OptionStyle
/// </summary> /// </summary>
public Style? RequiredOption { get; set; } public Style? RequiredOption { get; set; }
/// <summary>
/// Gets or sets the style for required option values.
/// </summary>
public Style? RequiredOptionValue { get; set; }
/// <summary> /// <summary>
/// Gets or sets the style for optional options. /// Gets or sets the style for optional options.
/// </summary> /// </summary>
public Style? OptionalOption { get; set; } public Style? OptionalOptionValue { get; set; }
} }

View File

@ -13,7 +13,7 @@ public interface ICommandParameter
/// <summary> /// <summary>
/// Gets a value indicating whether the parameter is required. /// Gets a value indicating whether the parameter is required.
/// </summary> /// </summary>
bool Required { get; } bool IsRequired { get; }
/// <summary> /// <summary>
/// Gets the description of the parameter. /// Gets the description of the parameter.

View File

@ -19,5 +19,5 @@ public interface ICommand
/// <param name="context">The command context.</param> /// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param> /// <param name="settings">The settings.</param>
/// <returns>The validation result.</returns> /// <returns>The validation result.</returns>
Task<int> Execute(CommandContext context, CommandSettings settings); Task<int> ExecuteAsync(CommandContext context, CommandSettings settings);
} }

View File

@ -37,7 +37,7 @@ public interface ICommandAppSettings
bool ShowOptionDefaultValues { get; set; } bool ShowOptionDefaultValues { get; set; }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text. /// Gets or sets a value indicating whether a trailing period of a description is trimmed in the help text.
/// </summary> /// </summary>
bool TrimTrailingPeriod { get; set; } bool TrimTrailingPeriod { get; set; }

View File

@ -13,5 +13,5 @@ public interface ICommand<TSettings> : ICommandLimiter<TSettings>
/// <param name="context">The command context.</param> /// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param> /// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns> /// <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);
} }

View File

@ -9,13 +9,15 @@ public interface IConfigurator
/// Sets the help provider for the application. /// Sets the help provider for the application.
/// </summary> /// </summary>
/// <param name="helpProvider">The help provider to use.</param> /// <param name="helpProvider">The help provider to use.</param>
public void SetHelpProvider(IHelpProvider helpProvider); /// <returns>A configurator that can be used for further configuration.</returns>
public IConfigurator SetHelpProvider(IHelpProvider helpProvider);
/// <summary> /// <summary>
/// Sets the help provider for the application. /// Sets the help provider for the application.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam> /// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
public void SetHelpProvider<T>() /// <returns>A configurator that can be used for further configuration.</returns>
public IConfigurator SetHelpProvider<T>()
where T : IHelpProvider; where T : IHelpProvider;
/// <summary> /// <summary>
@ -27,7 +29,8 @@ public interface IConfigurator
/// Adds an example of how to use the application. /// Adds an example of how to use the application.
/// </summary> /// </summary>
/// <param name="args">The example arguments.</param> /// <param name="args">The example arguments.</param>
void AddExample(params string[] args); /// <returns>A configurator that can be used for further configuration.</returns>
IConfigurator AddExample(params string[] args);
/// <summary> /// <summary>
/// Adds a command. /// Adds a command.

View File

@ -1,3 +1,5 @@
using static Spectre.Console.Cli.CommandTreeTokenizer;
namespace Spectre.Console.Cli; namespace Spectre.Console.Cli;
internal sealed class CommandExecutor internal sealed class CommandExecutor
@ -10,8 +12,10 @@ internal sealed class CommandExecutor
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor)); _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)
{ {
CommandTreeParserResult parsedResult;
if (configuration == null) if (configuration == null)
{ {
throw new ArgumentNullException(nameof(configuration)); throw new ArgumentNullException(nameof(configuration));
@ -27,20 +31,37 @@ internal sealed class CommandExecutor
_registrar.RegisterInstance(typeof(CommandModel), model); _registrar.RegisterInstance(typeof(CommandModel), model);
_registrar.RegisterDependencies(model); _registrar.RegisterDependencies(model);
// No default command? // Got at least one argument?
if (model.DefaultCommand == null) var firstArgument = arguments.FirstOrDefault();
if (firstArgument != null)
{ {
// Got at least one argument? // Asking for version?
var firstArgument = arguments.FirstOrDefault(); if (firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase) ||
if (firstArgument != null) firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase))
{ {
// Asking for version? Kind of a hack, but it's alright. if (configuration.Settings.ApplicationVersion != null)
// We should probably make this a bit better in the future.
if (firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase) ||
firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase))
{ {
if (configuration.Settings.ApplicationVersion != null) // We need to check if the command has a version option on its setting class.
// Do this by first parsing the command line args and checking the remaining args.
try
{ {
// Parse and map the model against the arguments.
parsedResult = ParseCommandLineArguments(model, configuration.Settings, arguments);
}
catch (Exception)
{
// Something went wrong with parsing the command line arguments,
// however we know the first argument is a version option.
var console = configuration.Settings.Console.GetConsole();
console.MarkupLine(configuration.Settings.ApplicationVersion);
return 0;
}
// Check the parsed remaining args for the version options.
if ((firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase) && parsedResult.Remaining.Parsed.Contains("-v")) ||
(firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase) && parsedResult.Remaining.Parsed.Contains("--version")))
{
// The version option is not a member of the command settings.
var console = configuration.Settings.Console.GetConsole(); var console = configuration.Settings.Console.GetConsole();
console.MarkupLine(configuration.Settings.ApplicationVersion); console.MarkupLine(configuration.Settings.ApplicationVersion);
return 0; return 0;
@ -50,7 +71,7 @@ internal sealed class CommandExecutor
} }
// Parse and map the model against the arguments. // Parse and map the model against the arguments.
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, arguments); parsedResult = ParseCommandLineArguments(model, configuration.Settings, arguments);
// Register the arguments with the container. // Register the arguments with the container.
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult); _registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
@ -82,7 +103,7 @@ internal sealed class CommandExecutor
} }
// Is this the default and is it called without arguments when there are required arguments? // 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. // Display help for default command.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command)); configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
@ -97,11 +118,93 @@ internal sealed class CommandExecutor
leaf.Command.Data); leaf.Command.Data);
// Execute the command tree. // Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false); return await ExecuteAsync(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
} }
} }
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1513:Closing brace should be followed by blank line", Justification = "Improves code readability by grouping together related statements into a block")]
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IReadOnlyList<string> args) private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IReadOnlyList<string> args)
{
CommandTreeParserResult? parsedResult = null;
CommandTreeTokenizerResult tokenizerResult;
try
{
(parsedResult, tokenizerResult) = InternalParseCommandLineArguments(model, settings, args);
var lastParsedLeaf = parsedResult.Tree?.GetLeafCommand();
var lastParsedCommand = lastParsedLeaf?.Command;
if (lastParsedLeaf != null && lastParsedCommand != null &&
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
lastParsedCommand.DefaultCommand != null)
{
// Adjust for any parsed remaining arguments by
// inserting the the default command ahead of them.
var position = tokenizerResult.Tokens.Position;
foreach (var parsedRemaining in parsedResult.Remaining.Parsed)
{
position--;
position -= parsedRemaining.Count(value => value != null);
}
position = position < 0 ? 0 : position;
// Insert this branch's default command into the command line
// arguments and try again to see if it will parse.
var argsWithDefaultCommand = new List<string>(args);
argsWithDefaultCommand.Insert(position, lastParsedCommand.DefaultCommand.Name);
(parsedResult, tokenizerResult) = InternalParseCommandLineArguments(model, settings, argsWithDefaultCommand);
}
}
catch (CommandParseException) when (parsedResult == null && settings.ParsingMode == ParsingMode.Strict)
{
// The parsing exception might be resolved by adding in the default command,
// but we can't know for sure. Take a brute force approach and try this for
// every position between the arguments.
for (int i = 0; i < args.Count; i++)
{
var argsWithDefaultCommand = new List<string>(args);
argsWithDefaultCommand.Insert(args.Count - i, "__default_command");
try
{
(parsedResult, tokenizerResult) = InternalParseCommandLineArguments(model, settings, argsWithDefaultCommand);
break;
}
catch (CommandParseException)
{
// Continue.
}
}
if (parsedResult == null)
{
// Failed to parse having inserted the default command between each argument.
// Repeat the parsing of the original arguments to throw the correct exception.
InternalParseCommandLineArguments(model, settings, args);
}
}
if (parsedResult == null)
{
// The arguments failed to parse despite everything we tried above.
// Exceptions should be thrown above before ever getting this far,
// however the following is the ulimately backstop and avoids
// the compiler from complaining about returning null.
throw CommandParseException.UnknownParsingError();
}
return parsedResult;
}
/// <summary>
/// Parse the command line arguments using the specified <see cref="CommandModel"/> and <see cref="CommandAppSettings"/>,
/// returning the parser and tokenizer results.
/// </summary>
/// <returns>The parser and tokenizer results as a tuple.</returns>
private (CommandTreeParserResult ParserResult, CommandTreeTokenizerResult TokenizerResult) InternalParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IReadOnlyList<string> args)
{ {
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments); var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
@ -109,27 +212,10 @@ internal sealed class CommandExecutor
var tokenizerResult = CommandTreeTokenizer.Tokenize(args); var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
var parsedResult = parser.Parse(parserContext, tokenizerResult); var parsedResult = parser.Parse(parserContext, tokenizerResult);
var lastParsedLeaf = parsedResult.Tree?.GetLeafCommand(); return (parsedResult, tokenizerResult);
var lastParsedCommand = lastParsedLeaf?.Command;
if (lastParsedLeaf != null && lastParsedCommand != null &&
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
lastParsedCommand.DefaultCommand != null)
{
// Insert this branch's default command into the command line
// arguments and try again to see if it will parse.
var argsWithDefaultCommand = new List<string>(args);
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
parsedResult = parser.Parse(parserContext, tokenizerResult);
}
return parsedResult;
} }
private static async Task<int> Execute( private static async Task<int> ExecuteAsync(
CommandTree leaf, CommandTree leaf,
CommandTree tree, CommandTree tree,
CommandContext context, CommandContext context,
@ -163,7 +249,7 @@ internal sealed class CommandExecutor
} }
// Execute the command. // Execute the command.
var result = await command.Execute(context, settings); var result = await command.ExecuteAsync(context, settings);
foreach (var interceptor in interceptors) foreach (var interceptor in interceptors)
{ {
interceptor.InterceptResult(context, settings, ref result); interceptor.InterceptResult(context, settings, ref result);

View File

@ -9,12 +9,14 @@ internal static class CommandValidator
{ {
foreach (var parameter in node.Unmapped) foreach (var parameter in node.Unmapped)
{ {
if (parameter.Required) if (parameter.IsRequired)
{ {
switch (parameter) switch (parameter)
{ {
case CommandArgument argument: case CommandArgument argument:
throw CommandRuntimeException.MissingRequiredArgument(node, argument); throw CommandRuntimeException.MissingRequiredArgument(node, argument);
case CommandOption option:
throw CommandRuntimeException.MissingRequiredOption(node, option);
} }
} }
} }

View File

@ -212,7 +212,7 @@ internal sealed class ExplainCommand : Command<ExplainCommand.Settings>
parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value)); parameterNode.AddNode(ValueMarkup("Value", commandArgumentParameter.Value));
} }
parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString())); parameterNode.AddNode(ValueMarkup("Required", parameter.IsRequired.ToString()));
if (parameter.Converter != null) if (parameter.Converter != null)
{ {

View File

@ -142,7 +142,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
var node = document.CreateElement("Argument"); var node = document.CreateElement("Argument");
node.SetNullableAttribute("Name", argument.Value); node.SetNullableAttribute("Name", argument.Value);
node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture)); node.SetAttribute("Position", argument.Position.ToString(CultureInfo.InvariantCulture));
node.SetBooleanAttribute("Required", argument.Required); node.SetBooleanAttribute("Required", argument.IsRequired);
node.SetEnumAttribute("Kind", argument.ParameterKind); node.SetEnumAttribute("Kind", argument.ParameterKind);
node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName); node.SetNullableAttribute("ClrType", argument.ParameterType?.FullName);
@ -186,7 +186,7 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
node.SetNullableAttribute("Short", option.ShortNames); node.SetNullableAttribute("Short", option.ShortNames);
node.SetNullableAttribute("Long", option.LongNames); node.SetNullableAttribute("Long", option.LongNames);
node.SetNullableAttribute("Value", option.ValueName); node.SetNullableAttribute("Value", option.ValueName);
node.SetBooleanAttribute("Required", option.Required); node.SetBooleanAttribute("Required", option.IsRequired);
node.SetEnumAttribute("Kind", option.ParameterKind); node.SetEnumAttribute("Kind", option.ParameterKind);
node.SetNullableAttribute("ClrType", option.ParameterType?.FullName); node.SetNullableAttribute("ClrType", option.ParameterType?.FullName);

View File

@ -20,22 +20,25 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
Examples = new List<string[]>(); Examples = new List<string[]>();
} }
public void SetHelpProvider(IHelpProvider helpProvider) public IConfigurator SetHelpProvider(IHelpProvider helpProvider)
{ {
// Register the help provider // Register the help provider
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider); _registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
return this;
} }
public void SetHelpProvider<T>() public IConfigurator SetHelpProvider<T>()
where T : IHelpProvider where T : IHelpProvider
{ {
// Register the help provider // Register the help provider
_registrar.Register(typeof(IHelpProvider), typeof(T)); _registrar.Register(typeof(IHelpProvider), typeof(T));
return this;
} }
public void AddExample(params string[] args) public IConfigurator AddExample(params string[] args)
{ {
Examples.Add(args); Examples.Add(args);
return this;
} }
public ConfiguredCommand SetDefaultCommand<TDefaultCommand>() public ConfiguredCommand SetDefaultCommand<TDefaultCommand>()

View File

@ -5,12 +5,12 @@ internal static class TemplateParser
public sealed class ArgumentResult public sealed class ArgumentResult
{ {
public string Value { get; set; } 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; Value = value;
Required = required; IsRequired = isRequired;
} }
} }

View File

@ -9,7 +9,7 @@ internal sealed class DelegateCommand : ICommand
_func = func; _func = func;
} }
public Task<int> Execute(CommandContext context, CommandSettings settings) public Task<int> ExecuteAsync(CommandContext context, CommandSettings settings)
{ {
return _func(context, settings); return _func(context, settings);
} }

View File

@ -86,7 +86,7 @@ internal static class CommandModelValidator
// Arguments // Arguments
foreach (var argument in arguments) foreach (var argument in arguments)
{ {
if (argument.Required && argument.DefaultValue != null) if (argument.IsRequired && argument.DefaultValue != null)
{ {
throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument); throw CommandConfigurationException.RequiredArgumentsCannotHaveDefaultValue(argument);
} }

View File

@ -14,8 +14,9 @@ internal sealed class CommandOption : CommandParameter, ICommandOption
CommandOptionAttribute optionAttribute, ParameterValueProviderAttribute? valueProvider, CommandOptionAttribute optionAttribute, ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators, IEnumerable<ParameterValidationAttribute> validators,
DefaultValueAttribute? defaultValue, bool valueIsOptional) DefaultValueAttribute? defaultValue, bool valueIsOptional)
: base(parameterType, parameterKind, property, description, converter, : base(parameterType, parameterKind, property, description, converter,
defaultValue, deconstructor, valueProvider, validators, false, optionAttribute.IsHidden) defaultValue, deconstructor, valueProvider, validators,
optionAttribute.IsRequired, optionAttribute.IsHidden)
{ {
LongNames = optionAttribute.LongNames; LongNames = optionAttribute.LongNames;
ShortNames = optionAttribute.ShortNames; ShortNames = optionAttribute.ShortNames;

View File

@ -12,7 +12,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
public PairDeconstructorAttribute? PairDeconstructor { get; } public PairDeconstructorAttribute? PairDeconstructor { get; }
public List<ParameterValidationAttribute> Validators { get; } public List<ParameterValidationAttribute> Validators { get; }
public ParameterValueProviderAttribute? ValueProvider { get; } public ParameterValueProviderAttribute? ValueProvider { get; }
public bool Required { get; set; } public bool IsRequired { get; set; }
public bool IsHidden { get; } public bool IsHidden { get; }
public string PropertyName => Property.Name; public string PropertyName => Property.Name;
@ -39,7 +39,7 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
PairDeconstructor = deconstructor; PairDeconstructor = deconstructor;
ValueProvider = valueProvider; ValueProvider = valueProvider;
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>()); Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
Required = required; IsRequired = required;
IsHidden = isHidden; IsHidden = isHidden;
} }

View File

@ -1,4 +1,4 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// <auto-generated> // <auto-generated>
// This code was generated by a tool. // This code was generated by a tool.
// Runtime Version:4.0.30319.42000 // Runtime Version:4.0.30319.42000
@ -124,7 +124,7 @@ namespace Spectre.Console.Cli.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Prints help information. /// Looks up a localized string similar to Prints help information..
/// </summary> /// </summary>
internal static string PrintHelpDescription { internal static string PrintHelpDescription {
get { get {
@ -133,7 +133,7 @@ namespace Spectre.Console.Cli.Resources {
} }
/// <summary> /// <summary>
/// Looks up a localized string similar to Prints version information. /// Looks up a localized string similar to Prints version information..
/// </summary> /// </summary>
internal static string PrintVersionDescription { internal static string PrintVersionDescription {
get { get {

View File

@ -139,10 +139,10 @@
<value>OPTIONEN</value> <value>OPTIONEN</value>
</data> </data>
<data name="PrintHelpDescription" xml:space="preserve"> <data name="PrintHelpDescription" xml:space="preserve">
<value>Zeigt Hilfe an</value> <value>Zeigt Hilfe an.</value>
</data> </data>
<data name="PrintVersionDescription" xml:space="preserve"> <data name="PrintVersionDescription" xml:space="preserve">
<value>Zeigt Versionsinformationen an</value> <value>Zeigt Versionsinformationen an.</value>
</data> </data>
<data name="Usage" xml:space="preserve"> <data name="Usage" xml:space="preserve">
<value>VERWENDUNG</value> <value>VERWENDUNG</value>

View File

@ -139,10 +139,10 @@
<value>OPCIONES</value> <value>OPCIONES</value>
</data> </data>
<data name="PrintHelpDescription" xml:space="preserve"> <data name="PrintHelpDescription" xml:space="preserve">
<value>Imprime información de ayuda</value> <value>Imprime información de ayuda.</value>
</data> </data>
<data name="PrintVersionDescription" xml:space="preserve"> <data name="PrintVersionDescription" xml:space="preserve">
<value>Imprime información de versión</value> <value>Imprime información de versión.</value>
</data> </data>
<data name="Usage" xml:space="preserve"> <data name="Usage" xml:space="preserve">
<value>USO</value> <value>USO</value>

View File

@ -139,10 +139,10 @@
<value>OPTIONS</value> <value>OPTIONS</value>
</data> </data>
<data name="PrintHelpDescription" xml:space="preserve"> <data name="PrintHelpDescription" xml:space="preserve">
<value>Affiche l'aide</value> <value>Affiche l'aide.</value>
</data> </data>
<data name="PrintVersionDescription" xml:space="preserve"> <data name="PrintVersionDescription" xml:space="preserve">
<value>Affiche la version</value> <value>Affiche la version.</value>
</data> </data>
<data name="Usage" xml:space="preserve"> <data name="Usage" xml:space="preserve">
<value>UTILISATION</value> <value>UTILISATION</value>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>ARGOMENTI</value>
</data>
<data name="Command" xml:space="preserve">
<value>COMANDO</value>
</data>
<data name="Commands" xml:space="preserve">
<value>COMANDI</value>
</data>
<data name="Default" xml:space="preserve">
<value>PREDEFINITO</value>
</data>
<data name="Description" xml:space="preserve">
<value>DESCRIZIONE</value>
</data>
<data name="Examples" xml:space="preserve">
<value>ESEMPI</value>
</data>
<data name="Options" xml:space="preserve">
<value>OPZIONI</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Visualizza le informazioni di aiuto.</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Visualizza le informazioni sulla versione.</value>
</data>
<data name="Usage" xml:space="preserve">
<value>USO</value>
</data>
</root>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>引数</value>
</data>
<data name="Command" xml:space="preserve">
<value>コマンド</value>
</data>
<data name="Commands" xml:space="preserve">
<value>コマンド群</value>
</data>
<data name="Default" xml:space="preserve">
<value>デフォルト</value>
</data>
<data name="Description" xml:space="preserve">
<value>説明</value>
</data>
<data name="Examples" xml:space="preserve">
<value>例</value>
</data>
<data name="Options" xml:space="preserve">
<value>オプション</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>ヘルプ情報を表示</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>バージョン情報を表示</value>
</data>
<data name="Usage" xml:space="preserve">
<value>使用法</value>
</data>
</root>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>인수</value>
</data>
<data name="Command" xml:space="preserve">
<value>명령</value>
</data>
<data name="Commands" xml:space="preserve">
<value>명령어</value>
</data>
<data name="Default" xml:space="preserve">
<value>기본값</value>
</data>
<data name="Description" xml:space="preserve">
<value>설명</value>
</data>
<data name="Examples" xml:space="preserve">
<value>예시</value>
</data>
<data name="Options" xml:space="preserve">
<value>옵션</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>도움말 정보를 출력</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>버전 정보를 출력</value>
</data>
<data name="Usage" xml:space="preserve">
<value>사용법</value>
</data>
</root>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>ARGUMENTOS</value>
</data>
<data name="Command" xml:space="preserve">
<value>COMANDO</value>
</data>
<data name="Commands" xml:space="preserve">
<value>COMANDOS</value>
</data>
<data name="Default" xml:space="preserve">
<value>PADRÃO</value>
</data>
<data name="Description" xml:space="preserve">
<value>DESCRIÇÃO</value>
</data>
<data name="Examples" xml:space="preserve">
<value>EXEMPLOS</value>
</data>
<data name="Options" xml:space="preserve">
<value>OPÇÕES</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Exibe informações de ajuda.</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Exibe informações de versão.</value>
</data>
<data name="Usage" xml:space="preserve">
<value>USO</value>
</data>
</root>

View File

@ -139,10 +139,10 @@
<value>OPTIONS</value> <value>OPTIONS</value>
</data> </data>
<data name="PrintHelpDescription" xml:space="preserve"> <data name="PrintHelpDescription" xml:space="preserve">
<value>Prints help information</value> <value>Prints help information.</value>
</data> </data>
<data name="PrintVersionDescription" xml:space="preserve"> <data name="PrintVersionDescription" xml:space="preserve">
<value>Prints version information</value> <value>Prints version information.</value>
</data> </data>
<data name="Usage" xml:space="preserve"> <data name="Usage" xml:space="preserve">
<value>USAGE</value> <value>USAGE</value>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>АРГУМЕНТЫ</value>
</data>
<data name="Command" xml:space="preserve">
<value>КОМАНДА</value>
</data>
<data name="Commands" xml:space="preserve">
<value>КОМАНДЫ</value>
</data>
<data name="Default" xml:space="preserve">
<value>ПО УМОЛЧАНИЮ</value>
</data>
<data name="Description" xml:space="preserve">
<value>ОПИСАНИЕ</value>
</data>
<data name="Examples" xml:space="preserve">
<value>ПРИМЕРЫ</value>
</data>
<data name="Options" xml:space="preserve">
<value>ОПЦИИ</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Выводит информацию о помощи.</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Выводит информацию о версии.</value>
</data>
<data name="Usage" xml:space="preserve">
<value>ИСПОЛЬЗОВАНИЕ</value>
</data>
</root>

View File

@ -139,10 +139,10 @@
<value>VAL</value> <value>VAL</value>
</data> </data>
<data name="PrintHelpDescription" xml:space="preserve"> <data name="PrintHelpDescription" xml:space="preserve">
<value>Skriver ut hjälpinformation</value> <value>Skriver ut hjälpinformation.</value>
</data> </data>
<data name="PrintVersionDescription" xml:space="preserve"> <data name="PrintVersionDescription" xml:space="preserve">
<value>Skriver ut versionsnummer</value> <value>Skriver ut versionsnummer.</value>
</data> </data>
<data name="Usage" xml:space="preserve"> <data name="Usage" xml:space="preserve">
<value>ANVÄNDING</value> <value>ANVÄNDING</value>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Arguments" xml:space="preserve">
<value>参数</value>
</data>
<data name="Command" xml:space="preserve">
<value>命令</value>
</data>
<data name="Commands" xml:space="preserve">
<value>命令列表</value>
</data>
<data name="Default" xml:space="preserve">
<value>默认</value>
</data>
<data name="Description" xml:space="preserve">
<value>描述</value>
</data>
<data name="Examples" xml:space="preserve">
<value>示例</value>
</data>
<data name="Options" xml:space="preserve">
<value>选项</value>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>显示帮助信息</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>显示版本信息</value>
</data>
<data name="Usage" xml:space="preserve">
<value>用法</value>
</data>
</root>

View File

@ -31,10 +31,5 @@ public sealed class CommandAppResult
Output = output ?? string.Empty; Output = output ?? string.Empty;
Context = context; Context = context;
Settings = settings; Settings = settings;
Output = Output
.NormalizeLineEndings()
.TrimLines()
.Trim();
} }
} }

View File

@ -9,19 +9,46 @@ public sealed class CommandAppTester
private Action<IConfigurator>? _configuration; private Action<IConfigurator>? _configuration;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="CommandAppTester"/> class. /// Gets the test console used by both the CommandAppTester and CommandApp.
/// </summary> /// </summary>
/// <param name="registrar">The registrar.</param> public TestConsole Console { get; }
public CommandAppTester(ITypeRegistrar? registrar = null)
{
Registrar = registrar;
}
/// <summary> /// <summary>
/// Gets or sets the Registrar to use in the CommandApp. /// Gets or sets the Registrar to use in the CommandApp.
/// </summary> /// </summary>
public ITypeRegistrar? Registrar { get; set; } public ITypeRegistrar? Registrar { get; set; }
/// <summary>
/// Gets or sets the settings for the <see cref="CommandAppTester"/>.
/// </summary>
public CommandAppTesterSettings TestSettings { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="CommandAppTester"/> class.
/// </summary>
/// <param name="registrar">The registrar.</param>
/// <param name="settings">The settings.</param>
/// <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>
/// Initializes a new instance of the <see cref="CommandAppTester"/> class.
/// </summary>
/// <param name="settings">The settings.</param>
public CommandAppTester(CommandAppTesterSettings settings)
{
TestSettings = settings;
Console = new TestConsole().Width(int.MaxValue);
}
/// <summary> /// <summary>
/// Sets the default command. /// Sets the default command.
/// </summary> /// </summary>
@ -69,25 +96,23 @@ public sealed class CommandAppTester
public CommandAppFailure RunAndCatch<T>(params string[] args) public CommandAppFailure RunAndCatch<T>(params string[] args)
where T : Exception where T : Exception
{ {
var console = new TestConsole().Width(int.MaxValue);
try try
{ {
Run(args, console, c => c.PropagateExceptions()); Run(args, Console, c => c.PropagateExceptions());
throw new InvalidOperationException("Expected an exception to be thrown, but there was none."); throw new InvalidOperationException("Expected an exception to be thrown, but there was none.");
} }
catch (T ex) catch (T ex)
{ {
if (ex is CommandAppException commandAppException && commandAppException.Pretty != null) if (ex is CommandAppException commandAppException && commandAppException.Pretty != null)
{ {
console.Write(commandAppException.Pretty); Console.Write(commandAppException.Pretty);
} }
else else
{ {
console.WriteLine(ex.Message); Console.WriteLine(ex.Message);
} }
return new CommandAppFailure(ex, console.Output); return new CommandAppFailure(ex, Console.Output);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -104,8 +129,7 @@ public sealed class CommandAppTester
/// <returns>The result.</returns> /// <returns>The result.</returns>
public CommandAppResult Run(params string[] args) public CommandAppResult Run(params string[] args)
{ {
var console = new TestConsole().Width(int.MaxValue); return Run(args, Console);
return Run(args, console);
} }
private CommandAppResult Run(string[] args, TestConsole console, Action<IConfigurator>? config = null) private CommandAppResult Run(string[] args, TestConsole console, Action<IConfigurator>? config = null)
@ -135,10 +159,8 @@ public sealed class CommandAppTester
var result = app.Run(args); var result = app.Run(args);
var output = console.Output var output = console.Output.NormalizeLineEndings();
.NormalizeLineEndings() output = TestSettings.TrimConsoleOutput ? output.TrimLines().Trim() : output;
.TrimLines()
.Trim();
return new CommandAppResult(result, output, context, settings); return new CommandAppResult(result, output, context, settings);
} }
@ -150,8 +172,7 @@ public sealed class CommandAppTester
/// <returns>The result.</returns> /// <returns>The result.</returns>
public async Task<CommandAppResult> RunAsync(params string[] args) public async Task<CommandAppResult> RunAsync(params string[] args)
{ {
var console = new TestConsole().Width(int.MaxValue); return await RunAsync(args, Console);
return await RunAsync(args, console);
} }
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)
@ -181,10 +202,8 @@ public sealed class CommandAppTester
var result = await app.RunAsync(args); var result = await app.RunAsync(args);
var output = console.Output var output = console.Output.NormalizeLineEndings();
.NormalizeLineEndings() output = TestSettings.TrimConsoleOutput ? output.TrimLines().Trim() : output;
.TrimLines()
.Trim();
return new CommandAppResult(result, output, context, settings); return new CommandAppResult(result, output, context, settings);
} }

View File

@ -0,0 +1,15 @@
namespace Spectre.Console.Testing;
/// <summary>
/// Represents the configuration settings for the <see cref="CommandAppTester"/> class.
/// </summary>
public sealed class CommandAppTesterSettings
{
/// <summary>
/// Gets or sets a value indicating whether whitespace should be trimmed from the console output.
/// </summary>
/// <remarks>
/// When enabled, leading and trailing whitespace from the console output and trailing whitespace from each line will be trimmed.
/// </remarks>
public bool TrimConsoleOutput { get; set; } = true;
}

View File

@ -230,6 +230,13 @@ public partial struct Color : IEquatable<Color>
hex = hex.Substring(1); hex = hex.Substring(1);
} }
// 3 digit hex codes are expanded to 6 digits
// by doubling each digit, conform to CSS color codes
if (hex.Length == 3)
{
hex = string.Concat(hex.Select(c => new string(c, 2)));
}
var r = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber); var r = byte.Parse(hex.Substring(0, 2), NumberStyles.HexNumber);
var g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber); var g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber);
var b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber); var b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber);

View File

@ -0,0 +1,92 @@
namespace Spectre.Console.Extensions;
/// <summary>
/// Provides extension methods for running tasks with a spinner animation.
/// </summary>
public static class SpinnerExtensions
{
/// <summary>
/// Runs a task with a spinner animation.
/// </summary>
/// <param name="task">The task to run.</param>
/// <param name="spinner">The spinner to use.</param>
/// <param name="style">The style to apply to the spinner.</param>
/// <param name="ansiConsole">The console to write to.</param>
/// <returns>The result of the task.</returns>
public static async Task Spinner(this Task task, Spinner? spinner = null, Style? style = null, IAnsiConsole? ansiConsole = null)
{
await SpinnerInternal<object>(task, spinner ?? Console.Spinner.Known.Default, style, ansiConsole);
}
/// <summary>
/// Runs a task with a spinner animation.
/// </summary>
/// <typeparam name="T">The type of the task result.</typeparam>
/// <param name="task">The task to run.</param>
/// <param name="spinner">The spinner to use.</param>
/// <param name="style">The style to apply to the spinner.</param>
/// <param name="ansiConsole">The console to write to.</param>
/// <returns>The result of the task.</returns>
public static async Task<T> Spinner<T>(this Task<T> task, Spinner? spinner = null, Style? style = null, IAnsiConsole? ansiConsole = null)
{
return (await SpinnerInternal<T>(task, spinner ?? Console.Spinner.Known.Default, style, ansiConsole))!;
}
private static async Task<T?> SpinnerInternal<T>(Task task, Spinner spinner, Style? style = null, IAnsiConsole? ansiConsole = null)
{
ansiConsole ??= AnsiConsole.Console;
style ??= Style.Plain;
var currentFrame = 0;
var cancellationTokenSource = new CancellationTokenSource();
// Start spinner animation in background
var spinnerTask = Task.Run(
async () =>
{
while (!cancellationTokenSource.Token.IsCancellationRequested)
{
ansiConsole.Cursor.Show(false);
var spinnerFrame = spinner.Frames[currentFrame];
// Write the spinner frame
ansiConsole.Write(new Text(spinnerFrame, style));
ansiConsole.Write(new ControlCode(AnsiSequences.CUB(spinnerFrame.Length)));
currentFrame = (currentFrame + 1) % spinner.Frames.Count;
await Task.Delay(spinner.Interval, cancellationTokenSource.Token);
}
}, cancellationTokenSource.Token);
try
{
// Wait for the actual task to complete
if (task is Task<T> taskWithResult)
{
var result = await taskWithResult;
await cancellationTokenSource.CancelAsync();
await spinnerTask.ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnCanceled);
return result;
}
else
{
await task;
await cancellationTokenSource.CancelAsync();
await spinnerTask.ContinueWith(_ => { }, TaskContinuationOptions.OnlyOnCanceled);
return default;
}
}
finally
{
var spinnerFrame = spinner.Frames[currentFrame];
ansiConsole.Write(new string(' ', spinnerFrame.Length));
ansiConsole.Write(new ControlCode(AnsiSequences.CUB(spinnerFrame.Length)));
ansiConsole.Cursor.Show();
await cancellationTokenSource.CancelAsync();
}
}
}

View File

@ -2,7 +2,26 @@ namespace Spectre.Console;
internal static class Cell 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) public static int GetCellLength(string text)
{ {
@ -29,6 +48,13 @@ internal static class Cell
return 1; 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];
} }
} }

View File

@ -0,0 +1,13 @@
#if NETSTANDARD2_0
namespace Spectre.Console
{
internal static class CancellationTokenHelpers
{
public static Task CancelAsync(this CancellationTokenSource cts)
{
cts.Cancel();
return Task.CompletedTask;
}
}
}
#endif

View File

@ -4,7 +4,6 @@ internal sealed class LiveDisplayRenderer : IRenderHook
{ {
private readonly IAnsiConsole _console; private readonly IAnsiConsole _console;
private readonly LiveDisplayContext _context; private readonly LiveDisplayContext _context;
public LiveDisplayRenderer(IAnsiConsole console, LiveDisplayContext context) public LiveDisplayRenderer(IAnsiConsole console, LiveDisplayContext context)
{ {
_console = console; _console = console;
@ -45,7 +44,7 @@ internal sealed class LiveDisplayRenderer : IRenderHook
{ {
lock (_context.Lock) lock (_context.Lock)
{ {
yield return _context.Live.PositionCursor(); yield return _context.Live.PositionCursor(options);
foreach (var renderable in renderables) foreach (var renderable in renderables)
{ {

View File

@ -39,7 +39,7 @@ internal sealed class LiveRenderable : Renderable
} }
} }
public IRenderable PositionCursor() public IRenderable PositionCursor(RenderOptions options)
{ {
lock (_lock) lock (_lock)
{ {
@ -48,6 +48,14 @@ internal sealed class LiveRenderable : Renderable
return new ControlCode(string.Empty); 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; var linesToMoveUp = _shape.Value.Height - 1;
return new ControlCode("\r" + CUU(linesToMoveUp)); return new ControlCode("\r" + CUU(linesToMoveUp));
} }

View File

@ -118,7 +118,7 @@ internal sealed class DefaultProgressRenderer : ProgressRenderer
{ {
lock (_lock) lock (_lock)
{ {
yield return _live.PositionCursor(); yield return _live.PositionCursor(options);
foreach (var renderable in renderables) foreach (var renderable in renderables)
{ {

View File

@ -42,7 +42,7 @@ internal sealed class ListPromptRenderHook<T> : IRenderHook
_dirty = false; _dirty = false;
} }
yield return _live.PositionCursor(); yield return _live.PositionCursor(options);
foreach (var renderable in renderables) foreach (var renderable in renderables)
{ {

View File

@ -20,7 +20,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Dependencies"> <ItemGroup Label="Dependencies">
<PackageReference Condition="'$(TargetFramework)' == 'netstandard2.0'" Include="System.Memory"/>
<PackageReference Include="Wcwidth.Sources"> <PackageReference Include="Wcwidth.Sources">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>
@ -35,6 +34,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'"> <ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Memory" />
<PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" PrivateAssets="all"/> <PackageReference Include="TunnelVisionLabs.ReferenceAssemblyAnnotator" PrivateAssets="all"/>
<PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]"/> <PackageDownload Include="Microsoft.NETCore.App.Ref" Version="[$(AnnotatedReferenceAssemblyVersion)]"/>
</ItemGroup> </ItemGroup>

View File

@ -226,6 +226,11 @@ public sealed class Style : IEquatable<Style>
builder.Add("on " + Background.ToMarkup()); builder.Add("on " + Background.ToMarkup());
} }
if (Link != null)
{
builder.Add($"link={Link}");
}
return string.Join(" ", builder); return string.Join(" ", builder);
} }

View File

@ -33,11 +33,12 @@ internal static class ExceptionFormatter
{ {
var shortenTypes = (settings.Format & ExceptionFormats.ShortenTypes) != 0; var shortenTypes = (settings.Format & ExceptionFormats.ShortenTypes) != 0;
var exceptionType = ex.GetType(); var exceptionType = ex.GetType();
var exceptionTypeFullName = exceptionType.FullName ?? exceptionType.Name; var exceptionTypeName = TypeNameHelper.GetTypeDisplayName(exceptionType, fullName: !shortenTypes, includeSystemNamespace: true);
var type = Emphasize(exceptionTypeFullName, new[] { '.' }, settings.Style.Exception, shortenTypes, settings); var type = new StringBuilder();
Emphasize(type, exceptionTypeName, new[] { '.' }, settings.Style.Exception, shortenTypes, settings, limit: '<');
var message = $"[{settings.Style.Message.ToMarkup()}]{ex.Message.EscapeMarkup()}[/]"; var message = $"[{settings.Style.Message.ToMarkup()}]{ex.Message.EscapeMarkup()}[/]";
return new Markup(string.Concat(type, ": ", message)); return new Markup($"{type}: {message}");
} }
private static Grid GetStackFrames(Exception ex, ExceptionSettings settings) private static Grid GetStackFrames(Exception ex, ExceptionSettings settings)
@ -64,7 +65,7 @@ internal static class ExceptionFormatter
var stackTrace = new StackTrace(ex, fNeedFileInfo: true); var stackTrace = new StackTrace(ex, fNeedFileInfo: true);
var allFrames = stackTrace.GetFrames(); 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 // if we can't easily get the method for the frame, then we are in AOT
// fallback to using ToString method of each frame. // fallback to using ToString method of each frame.
@ -101,7 +102,7 @@ internal static class ExceptionFormatter
builder.Append(' '); builder.Append(' ');
} }
builder.Append(Emphasize(methodName, new[] { '.' }, styles.Method, shortenMethods, settings)); Emphasize(builder, methodName, new[] { '.' }, styles.Method, shortenMethods, settings);
builder.AppendWithStyle(styles.Parenthesis, "("); builder.AppendWithStyle(styles.Parenthesis, "(");
AppendParameters(builder, method, settings); AppendParameters(builder, method, settings);
builder.AppendWithStyle(styles.Parenthesis, ")"); builder.AppendWithStyle(styles.Parenthesis, ")");
@ -168,7 +169,7 @@ internal static class ExceptionFormatter
void AppendPath() void AppendPath()
{ {
var shortenPaths = (settings.Format & ExceptionFormats.ShortenPaths) != 0; var shortenPaths = (settings.Format & ExceptionFormats.ShortenPaths) != 0;
builder.Append(Emphasize(path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings)); Emphasize(builder, path, new[] { '/', '\\' }, settings.Style.Path, shortenPaths, settings);
} }
if ((settings.Format & ExceptionFormats.ShowLinks) != 0) if ((settings.Format & ExceptionFormats.ShowLinks) != 0)
@ -192,32 +193,25 @@ internal static class ExceptionFormatter
} }
} }
private static string Emphasize(string input, char[] separators, Style color, bool compact, private static void Emphasize(StringBuilder builder, string input, char[] separators, Style color, bool compact,
ExceptionSettings settings) ExceptionSettings settings, char? limit = null)
{ {
var builder = new StringBuilder(); var limitIndex = limit.HasValue ? input.IndexOf(limit.Value) : -1;
var type = input; var index = limitIndex != -1 ? input[..limitIndex].LastIndexOfAny(separators) : input.LastIndexOfAny(separators);
var index = type.LastIndexOfAny(separators);
if (index != -1) if (index != -1)
{ {
if (!compact) if (!compact)
{ {
builder.AppendWithStyle( builder.AppendWithStyle(settings.Style.NonEmphasized, input[..(index + 1)]);
settings.Style.NonEmphasized,
type.Substring(0, index + 1));
} }
builder.AppendWithStyle( builder.AppendWithStyle(color, input[(index + 1)..]);
color,
type.Substring(index + 1, type.Length - index - 1));
} }
else else
{ {
builder.Append(type.EscapeMarkup()); builder.AppendWithStyle(color, input);
} }
return builder.ToString();
} }
private static bool ShowInStackTrace(StackFrame frame) private static bool ShowInStackTrace(StackFrame frame)

View File

@ -39,11 +39,12 @@ internal static class TypeNameHelper
/// <param name="type">The <see cref="Type"/>.</param> /// <param name="type">The <see cref="Type"/>.</param>
/// <param name="fullName"><c>true</c> to print a fully qualified name.</param> /// <param name="fullName"><c>true</c> to print a fully qualified name.</param>
/// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param> /// <param name="includeGenericParameterNames"><c>true</c> to include generic parameter names.</param>
/// <param name="includeSystemNamespace"><c>true</c> to include the <c>System</c> namespace.</param>
/// <returns>The pretty printed type name.</returns> /// <returns>The pretty printed type name.</returns>
public static string GetTypeDisplayName(Type type, bool fullName = false, bool includeGenericParameterNames = true) public static string GetTypeDisplayName(Type type, bool fullName = false, bool includeGenericParameterNames = true, bool includeSystemNamespace = false)
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames)); ProcessType(builder, type, new DisplayNameOptions(fullName, includeGenericParameterNames, includeSystemNamespace));
return builder.ToString(); return builder.ToString();
} }
@ -71,7 +72,7 @@ internal static class TypeNameHelper
{ {
builder.Append(builtInName); builder.Append(builtInName);
} }
else if (type.Namespace == nameof(System)) else if (type.Namespace == nameof(System) && !options.IncludeSystemNamespace)
{ {
builder.Append(type.Name); builder.Append(type.Name);
} }
@ -181,14 +182,17 @@ internal static class TypeNameHelper
private struct DisplayNameOptions private struct DisplayNameOptions
{ {
public DisplayNameOptions(bool fullName, bool includeGenericParameterNames) public DisplayNameOptions(bool fullName, bool includeGenericParameterNames, bool includeSystemNamespace)
{ {
FullName = fullName; FullName = fullName;
IncludeGenericParameterNames = includeGenericParameterNames; IncludeGenericParameterNames = includeGenericParameterNames;
IncludeSystemNamespace = includeSystemNamespace;
} }
public bool FullName { get; } public bool FullName { get; }
public bool IncludeGenericParameterNames { get; } public bool IncludeGenericParameterNames { get; }
public bool IncludeSystemNamespace { get; }
} }
} }

View File

@ -20,7 +20,7 @@ public sealed class AsynchronousCommand : AsyncCommand<AsynchronousCommandSettin
} }
else else
{ {
_console.WriteLine($"Finished executing asynchronously"); _console.Write($"Finished executing asynchronously");
} }
return 0; return 0;

View File

@ -0,0 +1,14 @@
namespace Spectre.Console.Tests.Data;
public class RequiredOptionsSettings : CommandSettings
{
[CommandOption("--foo <VALUE>", true)]
[Description("Foos the bars")]
public string Foo { get; set; }
}
public class RequiredOptionsWithoutDescriptionSettings : CommandSettings
{
[CommandOption("--foo <VALUE>", true)]
public string Foo { get; set; }
}

View File

@ -3,5 +3,6 @@ namespace Spectre.Console.Tests.Data;
public sealed class VersionSettings : CommandSettings public sealed class VersionSettings : CommandSettings
{ {
[CommandOption("-v|--version")] [CommandOption("-v|--version")]
[Description("The command version")]
public string Version { get; set; } public string Version { get; set; }
} }

View File

@ -1,5 +1,5 @@
DESCRIPTION: DESCRIPTION:
Contains settings for a cat. Contains settings for a cat
USAGE: USAGE:
myapp cat [LEGS] [OPTIONS] <COMMAND> myapp cat [LEGS] [OPTIONS] <COMMAND>

View File

@ -1,5 +1,5 @@
DESCRIPTION: DESCRIPTION:
Contains settings for a cat. Contains settings for a cat
USAGE: USAGE:
myapp cat [LEGS] [OPTIONS] <COMMAND> myapp cat [LEGS] [OPTIONS] <COMMAND>

View File

@ -1,5 +1,5 @@
DESCRIPTION: DESCRIPTION:
The animal command. The animal command
USAGE: USAGE:
myapp animal [LEGS] [OPTIONS] <COMMAND> myapp animal [LEGS] [OPTIONS] <COMMAND>

View File

@ -1,5 +1,5 @@
DESCRIPTION: DESCRIPTION:
Contains settings for a cat. Contains settings for a cat
USAGE: USAGE:
myapp cat [LEGS] [OPTIONS] <COMMAND> myapp cat [LEGS] [OPTIONS] <COMMAND>

View File

@ -1,5 +1,5 @@
DESCRIPTION: DESCRIPTION:
The lion command. The lion command
USAGE: USAGE:
myapp <TEETH> [LEGS] [OPTIONS] myapp <TEETH> [LEGS] [OPTIONS]

View File

@ -1,5 +1,5 @@
DESCRIPTION: DESCRIPTION:
The dog command. The dog command
USAGE: USAGE:
myapp <AGE> [LEGS] [OPTIONS] myapp <AGE> [LEGS] [OPTIONS]

View File

@ -1,5 +1,5 @@
DESCRIPTION: DESCRIPTION:
The lion command. The lion command
USAGE: USAGE:
myapp <TEETH> [LEGS] [OPTIONS] myapp <TEETH> [LEGS] [OPTIONS]

View File

@ -1,5 +1,5 @@
BESCHREIBUNG: BESCHREIBUNG:
The lion command. The lion command
VERWENDUNG: VERWENDUNG:
myapp <TEETH> [LEGS] [OPTIONEN] [KOMMANDO] myapp <TEETH> [LEGS] [OPTIONEN] [KOMMANDO]
@ -14,6 +14,7 @@ ARGUMENTE:
OPTIONEN: OPTIONEN:
STANDARDWERT STANDARDWERT
-h, --help Zeigt Hilfe an -h, --help Zeigt Hilfe an
-v, --version Zeigt Versionsinformationen an
-a, --alive Indicates whether or not the animal is alive -a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE> -n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100 --agility <VALUE> 10 The agility between 0 and 100

View File

@ -1,5 +1,5 @@
DESCRIPTION: DESCRIPTION:
The lion command. The lion command
USAGE: USAGE:
myapp <TEETH> [LEGS] [OPTIONS] [COMMAND] myapp <TEETH> [LEGS] [OPTIONS] [COMMAND]
@ -14,6 +14,7 @@ ARGUMENTS:
OPTIONS: OPTIONS:
DEFAULT DEFAULT
-h, --help Prints help information -h, --help Prints help information
-v, --version Prints version information
-a, --alive Indicates whether or not the animal is alive -a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE> -n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100 --agility <VALUE> 10 The agility between 0 and 100

View File

@ -1,5 +1,5 @@
DESCRIPTION: DESCRIPTION:
The lion command. The lion command
UTILISATION: UTILISATION:
myapp <TEETH> [LEGS] [OPTIONS] [COMMANDE] myapp <TEETH> [LEGS] [OPTIONS] [COMMANDE]
@ -14,6 +14,7 @@ ARGUMENTS:
OPTIONS: OPTIONS:
DÉFAUT DÉFAUT
-h, --help Affiche l'aide -h, --help Affiche l'aide
-v, --version Affiche la version
-a, --alive Indicates whether or not the animal is alive -a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE> -n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100 --agility <VALUE> 10 The agility between 0 and 100

View File

@ -0,0 +1,26 @@
DESCRIZIONE:
The lion command
USO:
myapp <TEETH> [LEGS] [OPZIONI] [COMANDO]
ESEMPI:
myapp 20 --alive
ARGOMENTI:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
OPZIONI:
PREDEFINITO
-h, --help Visualizza le informazioni di aiuto
-v, --version Visualizza le informazioni sulla versione
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
-w, --weight [WEIGHT] The weight of the lion, in kgs
COMANDI:
giraffe <LENGTH> The giraffe command

View File

@ -0,0 +1,26 @@
説明:
The lion command
使用法:
myapp <TEETH> [LEGS] [オプション] [コマンド]
例:
myapp 20 --alive
引数:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
オプション:
デフォルト
-h, --help ヘルプ情報を表示
-v, --version バージョン情報を表示
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
-w, --weight [WEIGHT] The weight of the lion, in kgs
コマンド群:
giraffe <LENGTH> The giraffe command

View File

@ -0,0 +1,26 @@
설명:
The lion command
사용법:
myapp <TEETH> [LEGS] [옵션] [명령]
예시:
myapp 20 --alive
인수:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
옵션:
기본값
-h, --help 도움말 정보를 출력
-v, --version 버전 정보를 출력
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
-w, --weight [WEIGHT] The weight of the lion, in kgs
명령어:
giraffe <LENGTH> The giraffe command

View File

@ -0,0 +1,26 @@
DESCRIÇÃO:
The lion command
USO:
myapp <TEETH> [LEGS] [OPÇÕES] [COMANDO]
EXEMPLOS:
myapp 20 --alive
ARGUMENTOS:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
OPÇÕES:
PADRÃO
-h, --help Exibe informações de ajuda
-v, --version Exibe informações de versão
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
-w, --weight [WEIGHT] The weight of the lion, in kgs
COMANDOS:
giraffe <LENGTH> The giraffe command

View File

@ -0,0 +1,26 @@
ОПИСАНИЕ:
The lion command
ИСПОЛЬЗОВАНИЕ:
myapp <TEETH> [LEGS] [ОПЦИИ] [КОМАНДА]
ПРИМЕРЫ:
myapp 20 --alive
АРГУМЕНТЫ:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
ОПЦИИ:
ПО УМОЛЧАНИЮ
-h, --help Выводит информацию о помощи
-v, --version Выводит информацию о версии
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
-w, --weight [WEIGHT] The weight of the lion, in kgs
КОМАНДЫ:
giraffe <LENGTH> The giraffe command

View File

@ -1,5 +1,5 @@
BESKRIVNING: BESKRIVNING:
The lion command. The lion command
ANVÄNDING: ANVÄNDING:
myapp <TEETH> [LEGS] [VAL] [KOMMANDO] myapp <TEETH> [LEGS] [VAL] [KOMMANDO]
@ -14,6 +14,7 @@ ARGUMENT:
VAL: VAL:
STANDARD STANDARD
-h, --help Skriver ut hjälpinformation -h, --help Skriver ut hjälpinformation
-v, --version Skriver ut versionsnummer
-a, --alive Indicates whether or not the animal is alive -a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE> -n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100 --agility <VALUE> 10 The agility between 0 and 100

View File

@ -0,0 +1,26 @@
描述:
The lion command
用法:
myapp <TEETH> [LEGS] [选项] [命令]
示例:
myapp 20 --alive
参数:
<TEETH> The number of teeth the lion has
[LEGS] The number of legs
选项:
默认
-h, --help 显示帮助信息
-v, --version 显示版本信息
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100
-c <CHILDREN> The number of children the lion has
-d <DAY> Monday, Thursday The days the lion goes hunting
-w, --weight [WEIGHT] The weight of the lion, in kgs
命令列表:
giraffe <LENGTH> The giraffe command

View File

@ -1,5 +1,5 @@
[bold]DESCRIPTION:[/] [bold]DESCRIPTION:[/]
The lion command. The lion command
[bold]USAGE:[/] [bold]USAGE:[/]
myapp []<TEETH>[/] [][[LEGS]][/] [][[OPTIONS]][/] [][[COMMAND]][/] myapp []<TEETH>[/] [][[LEGS]][/] [][[OPTIONS]][/] [][[COMMAND]][/]

View File

@ -1,5 +1,5 @@
[yellow]DESCRIPTION:[/] [yellow]DESCRIPTION:[/]
The lion command. The lion command
[yellow]USAGE:[/] [yellow]USAGE:[/]
myapp [aqua]<TEETH>[/] [silver][[LEGS]][/] [grey][[OPTIONS]][/] [aqua][[COMMAND]][/] myapp [aqua]<TEETH>[/] [silver][[LEGS]][/] [grey][[OPTIONS]][/] [aqua][[COMMAND]][/]

Some files were not shown because too many files have changed in this diff Show More