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