Compare commits

..

1 Commits

Author SHA1 Message Date
a46321c06b Support command description localization
eg.
 [CommandOption("-a|--args")]
 [Description(nameof(Str.GitArgs))]
 [Localization(typeof(Str))]
 public string Args { get; set; }

The program will go to the autogenerated class "Str.designer.cs" of the Resx file,  to looking for local value of the the resource symbol "GitArgs" , instead of displaying the original: "GitArgs"
2024-11-29 14:52:42 +08:00
123 changed files with 456 additions and 4006 deletions

View File

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

View File

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

View File

@ -23,7 +23,6 @@ namespace Docs
{
"../../src/Spectre.Console/**/{!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.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
})

View File

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

View File

@ -1,2 +0,0 @@
{"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

@ -1,332 +0,0 @@
{"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

@ -1,326 +0,0 @@
{"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,8 +85,10 @@ on the main thread.
### Unit Testing Best Practices
For testing of console output, Spectre.Console has [`IAnsiConsole`](xref:T:Spectre.Console.IAnsiConsole) that can be
injected into your application. The [Spectre.Console.Test](https://www.nuget.org/packages/Spectre.Console.Testing/)
NuGet package contains utilities for capturing the console output for verification. See the [Unit Testing](cli/unit-testing) page for further guidance.
injected into your application.
The [Spectre.Console.Test](https://www.nuget.org/packages/Spectre.Console.Testing/) contains a set of utilities for
capturing the output for verification, either manually or via a tool such
as [Verify](https://github.com/VerifyTests/Verify).
### 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
* 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)
* 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)
* 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))
* 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)
* 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)
@ -46,7 +46,7 @@ New features have been added, such as the ability to show separators between tab
## CLI
* 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)
* 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)

View File

@ -1,71 +0,0 @@
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.
// A type registrar is an adapter for a DI framework.
var registrar = new MyTypeRegistrar(registrations);
var registrar = new TypeRegistrar(registrations);
// Create a new command app with the registrar
// and run it with the provided arguments.
@ -75,13 +75,10 @@ var app = new CommandApp<DefaultCommand>(registrar);
return app.Run(args);
```
<?# Alert ?>
`MyTypeRegistrar` is a custom class that implements [ITypeRegistrar](xref:T:Spectre.Console.Cli.ITypeRegistrar) and must be provided by the user.
<?#/ Alert ?>
`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.
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.
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.
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 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.
## Interception
Interceptors can be registered with the `TypeRegistrar` (or with a custom DI-Container). Alternatively, `CommandApp` also provides a `SetInterceptor` configuration.
@ -92,4 +89,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.
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

@ -1,199 +0,0 @@
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
to create beautiful console applications.
## Spectre.Console.AnsiConsole
## Spectre.Console.AnsiConsole Features
* 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.
@ -14,19 +14,16 @@ to create beautiful console applications.
* 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.
* Format .NET [exceptions](exceptions) with custom color coded themes and styles.
* Written with unit testing in mind.
Spectre.Console.AnsiConsole has been heavily inspired by the excellent [Rich](https://github.com/willmcgugan/rich) library for Python written by Will McGugan.
Spectre.Console.AnsiConsole has been heavily inspired
by the excellent [Rich](https://github.com/willmcgugan/rich) library
for Python written by Will McGugan.
## Spectre.Console.Cli
* 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
![Sample of Spectre.Console output](./assets/images/example.png)
@ -39,4 +36,3 @@ Spectre.Console.AnsiConsole has been heavily inspired by the excellent [Rich](ht
Sorry, your browser doesn't support embedded videos.
</video>
The Spectre.Console [examples repository](https://github.com/spectreconsole/examples) contains many other examples.

View File

@ -1,73 +0,0 @@
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 */
.alert-warning {
@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;
@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;
}
.alert-warning p {

View File

@ -1,66 +0,0 @@
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

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

View File

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

View File

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

View File

@ -1,36 +0,0 @@
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>
<PackageReference Include="AngleSharp" Version="1.3.0" />
<PackageReference Include="AngleSharp" Version="1.1.2" />
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Scriban" Version="6.2.1" />
<PackageReference Include="Scriban" Version="5.12.0" />
<PackageReference Include="Spectre.IO" Version="0.18.0" />
</ItemGroup>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -113,9 +113,4 @@ public sealed class CommandParseException : CommandRuntimeException
var text = $"[red]Error:[/] The value '[white]{value}[/]' is not in a correct format";
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,16 +37,6 @@ public class CommandRuntimeException : CommandAppException
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{argument.Value}'.");
}
internal static CommandRuntimeException MissingRequiredOption(CommandTree node, CommandOption option)
{
if (node.Command.Name == CliConstants.DefaultCommandName)
{
return new CommandRuntimeException($"Missing required option '{option.GetOptionName()}'.");
}
return new CommandRuntimeException($"Command '{node.Command.Name}' is missing required argument '{option.GetOptionName()}'.");
}
internal static CommandRuntimeException NoConverterFound(CommandParameter parameter)
{
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");

View File

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

View File

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

View File

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

View File

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

View File

@ -13,5 +13,5 @@ public interface ICommand<TSettings> : ICommandLimiter<TSettings>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
Task<int> ExecuteAsync(CommandContext context, TSettings settings);
Task<int> Execute(CommandContext context, TSettings settings);
}

View File

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

View File

@ -1,5 +1,3 @@
using static Spectre.Console.Cli.CommandTreeTokenizer;
namespace Spectre.Console.Cli;
internal sealed class CommandExecutor
@ -12,10 +10,8 @@ internal sealed class CommandExecutor
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
}
public async Task<int> ExecuteAsync(IConfiguration configuration, IEnumerable<string> args)
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
{
CommandTreeParserResult parsedResult;
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
@ -31,37 +27,20 @@ internal sealed class CommandExecutor
_registrar.RegisterInstance(typeof(CommandModel), model);
_registrar.RegisterDependencies(model);
// No default command?
if (model.DefaultCommand == null)
{
// Got at least one argument?
var firstArgument = arguments.FirstOrDefault();
if (firstArgument != null)
{
// Asking for version?
if (firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase) ||
firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase))
// Asking for version? Kind of a hack, but it's alright.
// 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();
console.MarkupLine(configuration.Settings.ApplicationVersion);
return 0;
@ -71,7 +50,7 @@ internal sealed class CommandExecutor
}
// Parse and map the model against the arguments.
parsedResult = ParseCommandLineArguments(model, configuration.Settings, arguments);
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, arguments);
// Register the arguments with the container.
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
@ -103,7 +82,7 @@ internal sealed class CommandExecutor
}
// Is this the default and is it called without arguments when there are required arguments?
if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.IsRequired))
if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.Required))
{
// Display help for default command.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
@ -118,93 +97,11 @@ internal sealed class CommandExecutor
leaf.Command.Data);
// Execute the command tree.
return await ExecuteAsync(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
return await Execute(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)
{
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);
@ -212,10 +109,27 @@ internal sealed class CommandExecutor
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
var parsedResult = parser.Parse(parserContext, tokenizerResult);
return (parsedResult, tokenizerResult);
var lastParsedLeaf = parsedResult.Tree?.GetLeafCommand();
var lastParsedCommand = lastParsedLeaf?.Command;
if (lastParsedLeaf != null && lastParsedCommand != null &&
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
lastParsedCommand.DefaultCommand != null)
{
// Insert this branch's default command into the command line
// arguments and try again to see if it will parse.
var argsWithDefaultCommand = new List<string>(args);
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
parsedResult = parser.Parse(parserContext, tokenizerResult);
}
private static async Task<int> ExecuteAsync(
return parsedResult;
}
private static async Task<int> Execute(
CommandTree leaf,
CommandTree tree,
CommandContext context,
@ -249,7 +163,7 @@ internal sealed class CommandExecutor
}
// Execute the command.
var result = await command.ExecuteAsync(context, settings);
var result = await command.Execute(context, settings);
foreach (var interceptor in interceptors)
{
interceptor.InterceptResult(context, settings, ref result);

View File

@ -9,14 +9,12 @@ internal static class CommandValidator
{
foreach (var parameter in node.Unmapped)
{
if (parameter.IsRequired)
if (parameter.Required)
{
switch (parameter)
{
case CommandArgument 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("Required", parameter.IsRequired.ToString()));
parameterNode.AddNode(ValueMarkup("Required", parameter.Required.ToString()));
if (parameter.Converter != null)
{

View File

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

View File

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

View File

@ -5,12 +5,12 @@ internal static class TemplateParser
public sealed class ArgumentResult
{
public string Value { get; set; }
public bool IsRequired { get; set; }
public bool Required { get; set; }
public ArgumentResult(string value, bool isRequired)
public ArgumentResult(string value, bool required)
{
Value = value;
IsRequired = isRequired;
Required = required;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,150 +0,0 @@
<?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

@ -1,150 +0,0 @@
<?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

@ -1,150 +0,0 @@
<?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

@ -1,150 +0,0 @@
<?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>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Prints help information.</value>
<value>Prints help information</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Prints version information.</value>
<value>Prints version information</value>
</data>
<data name="Usage" xml:space="preserve">
<value>USAGE</value>

View File

@ -1,150 +0,0 @@
<?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>
</data>
<data name="PrintHelpDescription" xml:space="preserve">
<value>Skriver ut hjälpinformation.</value>
<value>Skriver ut hjälpinformation</value>
</data>
<data name="PrintVersionDescription" xml:space="preserve">
<value>Skriver ut versionsnummer.</value>
<value>Skriver ut versionsnummer</value>
</data>
<data name="Usage" xml:space="preserve">
<value>ANVÄNDING</value>

View File

@ -1,150 +0,0 @@
<?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,5 +31,10 @@ public sealed class CommandAppResult
Output = output ?? string.Empty;
Context = context;
Settings = settings;
Output = Output
.NormalizeLineEndings()
.TrimLines()
.Trim();
}
}

View File

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

View File

@ -1,15 +0,0 @@
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,13 +230,6 @@ public partial struct Color : IEquatable<Color>
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 g = byte.Parse(hex.Substring(2, 2), NumberStyles.HexNumber);
var b = byte.Parse(hex.Substring(4, 2), NumberStyles.HexNumber);

View File

@ -1,92 +0,0 @@
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,26 +2,7 @@ namespace Spectre.Console;
internal static class Cell
{
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
}
private static readonly int?[] _runeWidthCache = new int?[char.MaxValue];
public static int GetCellLength(string text)
{
@ -48,13 +29,6 @@ internal static class Cell
return 1;
}
var width = _runeWidthCache[rune];
if (width == Sentinel)
{
_runeWidthCache[rune] = (sbyte)UnicodeCalculator.GetWidth(rune);
return _runeWidthCache[rune];
}
return _runeWidthCache[rune];
return _runeWidthCache[rune] ??= UnicodeCalculator.GetWidth(rune);
}
}

View File

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

View File

@ -39,7 +39,7 @@ internal sealed class LiveRenderable : Renderable
}
}
public IRenderable PositionCursor(RenderOptions options)
public IRenderable PositionCursor()
{
lock (_lock)
{
@ -48,14 +48,6 @@ internal sealed class LiveRenderable : Renderable
return new ControlCode(string.Empty);
}
// Check if the size have been reduced
if (_shape.Value.Height > options.ConsoleSize.Height || _shape.Value.Width > options.ConsoleSize.Width)
{
// Important reset shape, so the size can shrink
_shape = null;
return new ControlCode(ED(2) + ED(3) + CUP(1, 1));
}
var linesToMoveUp = _shape.Value.Height - 1;
return new ControlCode("\r" + CUU(linesToMoveUp));
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,26 +0,0 @@
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

@ -1,26 +0,0 @@
説明:
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,26 +0,0 @@
설명:
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,26 +0,0 @@
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

@ -1,26 +0,0 @@
ОПИСАНИЕ:
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:
The lion command
The lion command.
ANVÄNDING:
myapp <TEETH> [LEGS] [VAL] [KOMMANDO]
@ -14,7 +14,6 @@ ARGUMENT:
VAL:
STANDARD
-h, --help Skriver ut hjälpinformation
-v, --version Skriver ut versionsnummer
-a, --alive Indicates whether or not the animal is alive
-n, --name <VALUE>
--agility <VALUE> 10 The agility between 0 and 100

View File

@ -1,26 +0,0 @@
描述:
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:[/]
The lion command
The lion command.
[bold]USAGE:[/]
myapp []<TEETH>[/] [][[LEGS]][/] [][[OPTIONS]][/] [][[COMMAND]][/]

View File

@ -1,5 +1,5 @@
[yellow]DESCRIPTION:[/]
The lion command
The lion command.
[yellow]USAGE:[/]
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