mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
86 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7397169a27 | ||
|
|
ffdb47d77f | ||
|
|
383bee0e3e | ||
|
|
4d6541dd14 | ||
|
|
b1e0896a0d | ||
|
|
e07ccd9f66 | ||
|
|
250b1f4c9c | ||
|
|
023c77ff09 | ||
|
|
dc402220f2 | ||
|
|
c448d0d5f6 | ||
|
|
3da367f29f | ||
|
|
ead7115cbe | ||
|
|
1fd028942f | ||
|
|
4219bbbf61 | ||
|
|
29a43686d4 | ||
|
|
63b940cf0e | ||
|
|
bbf58ee814 | ||
|
|
e2a674815d | ||
|
|
343b98944d | ||
|
|
2bbb7c1ab6 | ||
|
|
5296e56b1c | ||
|
|
943c045fab | ||
|
|
2af3f7faeb | ||
|
|
ed9e198d60 | ||
|
|
3bee7212b7 | ||
|
|
c82d8c4523 | ||
|
|
bef21e8a21 | ||
|
|
037e109699 | ||
|
|
f7befacd79 | ||
|
|
cec5fb4595 | ||
|
|
9c86391fb6 | ||
|
|
a3dcb31729 | ||
|
|
1002c6fe27 | ||
|
|
c64797d681 | ||
|
|
131b37fff8 | ||
|
|
813a53cdfa | ||
|
|
2af901a814 | ||
|
|
83afa97017 | ||
|
|
e0ded712e8 | ||
|
|
d484e832f5 | ||
|
|
71a5d83067 | ||
|
|
35ce60b596 | ||
|
|
0ec70a44db | ||
|
|
1ee2653cf8 | ||
|
|
4c0178cf9a | ||
|
|
296bc61837 | ||
|
|
766ccb1f1b | ||
|
|
f7314dc2e8 | ||
|
|
f453890d13 | ||
|
|
2c8f459806 | ||
|
|
018f4ebd17 | ||
|
|
404b052a5f | ||
|
|
dac2097321 | ||
|
|
6acf9b8c63 | ||
|
|
3ec0fee795 | ||
|
|
5843a4545e | ||
|
|
d64d3ec770 | ||
|
|
4dcbd30285 | ||
|
|
1334319dd1 | ||
|
|
714cf179cb | ||
|
|
70da3f40ff | ||
|
|
5075732564 | ||
|
|
10467a2912 | ||
|
|
a7ab1b690e | ||
|
|
142942b8a2 | ||
|
|
0bab835293 | ||
|
|
a6af9a6842 | ||
|
|
d3f4f5f208 | ||
|
|
cbbdb3369c | ||
|
|
6740f0b02b | ||
|
|
f7f99ec899 | ||
|
|
955fe07bac | ||
|
|
819b948e78 | ||
|
|
baa8220a52 | ||
|
|
9cd7b24e65 | ||
|
|
04610cf492 | ||
|
|
f4183e0462 | ||
|
|
29846ba505 | ||
|
|
92318ce1fb | ||
|
|
720db951f3 | ||
|
|
e042fe15e4 | ||
|
|
62b30d6072 | ||
|
|
beca0b2419 | ||
|
|
de847b90e4 | ||
|
|
7b9553dd22 | ||
|
|
f223f6061c |
@@ -2,7 +2,7 @@ root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = CRLF
|
||||
end_of_line = LF
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
insert_final_newline = false
|
||||
@@ -26,9 +26,6 @@ indent_size = 2
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
|
||||
[*.cs]
|
||||
# Prefer file scoped namespace declarations
|
||||
csharp_style_namespace_declarations = file_scoped:warning
|
||||
|
||||
19
.github/pull_request_template.md
vendored
Normal file
19
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<!--
|
||||
Do NOT open a PR without discussing the changes on an open issue, first.
|
||||
|
||||
Add the issue number here. e.g. #123
|
||||
-->
|
||||
fixes #
|
||||
|
||||
<!-- formalities. These are not optional. -->
|
||||
|
||||
- [ ] I have read the [Contribution Guidelines](../CONTRIBUTING.md)
|
||||
- [ ] I have commented on the issue above and discussed the intended changes
|
||||
- [ ] A maintainer has signed off on the changes and the issue was assigned to me
|
||||
- [ ] All newly added code is adequately covered by tests
|
||||
- [ ] All existing tests are still running without errors
|
||||
- [ ] The documentation was modified to reflect the changes _OR_ no documentation changes are required.
|
||||
|
||||
## Changes
|
||||
|
||||
<!-- describe the changes you made. -->
|
||||
5
.github/workflows/ci.yaml
vendored
5
.github/workflows/ci.yaml
vendored
@@ -76,6 +76,11 @@ jobs:
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
7.0.x
|
||||
8.0.x
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
|
||||
5
.github/workflows/publish.yaml
vendored
5
.github/workflows/publish.yaml
vendored
@@ -107,6 +107,11 @@ jobs:
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
7.0.x
|
||||
8.0.x
|
||||
|
||||
- name: Publish
|
||||
shell: bash
|
||||
|
||||
60
build.cake
60
build.cake
@@ -14,8 +14,21 @@ Task("Build")
|
||||
.IsDependentOn("Clean")
|
||||
.Does(context =>
|
||||
{
|
||||
Information("Compiling generator...");
|
||||
DotNetBuild("./resources/scripts/Generator/Generator.sln", new DotNetBuildSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoIncremental = context.HasArgument("rebuild"),
|
||||
MSBuildSettings = new DotNetMSBuildSettings()
|
||||
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
|
||||
});
|
||||
|
||||
Information("\nCompiling Spectre.Console...");
|
||||
DotNetBuild("./src/Spectre.Console.sln", new DotNetBuildSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoIncremental = context.HasArgument("rebuild"),
|
||||
MSBuildSettings = new DotNetMSBuildSettings()
|
||||
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
|
||||
@@ -28,6 +41,8 @@ Task("Build-Analyzer")
|
||||
{
|
||||
DotNetBuild("./src/Spectre.Console.Analyzer.sln", new DotNetBuildSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoIncremental = context.HasArgument("rebuild"),
|
||||
MSBuildSettings = new DotNetMSBuildSettings()
|
||||
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
|
||||
@@ -40,6 +55,8 @@ Task("Build-Examples")
|
||||
{
|
||||
DotNetBuild("./examples/Examples.sln", new DotNetBuildSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoIncremental = context.HasArgument("rebuild"),
|
||||
MSBuildSettings = new DotNetMSBuildSettings()
|
||||
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
|
||||
@@ -54,18 +71,24 @@ Task("Test")
|
||||
{
|
||||
DotNetTest("./test/Spectre.Console.Tests/Spectre.Console.Tests.csproj", new DotNetTestSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoRestore = true,
|
||||
NoBuild = true,
|
||||
});
|
||||
|
||||
DotNetTest("./test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj", new DotNetTestSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoRestore = true,
|
||||
NoBuild = true,
|
||||
});
|
||||
|
||||
DotNetTest("./test/Spectre.Console.Analyzer.Tests/Spectre.Console.Analyzer.Tests.csproj", new DotNetTestSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoRestore = true,
|
||||
NoBuild = true,
|
||||
});
|
||||
@@ -77,6 +100,8 @@ Task("Package")
|
||||
{
|
||||
context.DotNetPack($"./src/Spectre.Console.sln", new DotNetPackSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoRestore = true,
|
||||
NoBuild = true,
|
||||
OutputDirectory = "./.artifacts",
|
||||
@@ -86,6 +111,8 @@ Task("Package")
|
||||
|
||||
context.DotNetPack($"./src/Spectre.Console.Analyzer.sln", new DotNetPackSettings {
|
||||
Configuration = configuration,
|
||||
Verbosity = DotNetVerbosity.Minimal,
|
||||
NoLogo = true,
|
||||
NoRestore = true,
|
||||
NoBuild = true,
|
||||
OutputDirectory = "./.artifacts",
|
||||
@@ -94,38 +121,6 @@ Task("Package")
|
||||
});
|
||||
});
|
||||
|
||||
Task("Publish-GitHub")
|
||||
.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
|
||||
.IsDependentOn("Package")
|
||||
.Does(context =>
|
||||
{
|
||||
var apiKey = Argument<string>("github-key", null);
|
||||
if(string.IsNullOrWhiteSpace(apiKey)) {
|
||||
throw new CakeException("No GitHub API key was provided.");
|
||||
}
|
||||
|
||||
// Publish to GitHub Packages
|
||||
var exitCode = 0;
|
||||
foreach(var file in context.GetFiles("./.artifacts/*.nupkg"))
|
||||
{
|
||||
context.Information("Publishing {0}...", file.GetFilename().FullPath);
|
||||
exitCode += StartProcess("dotnet",
|
||||
new ProcessSettings {
|
||||
Arguments = new ProcessArgumentBuilder()
|
||||
.Append("gpr")
|
||||
.Append("push")
|
||||
.AppendQuoted(file.FullPath)
|
||||
.AppendSwitchSecret("-k", " ", apiKey)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if(exitCode != 0)
|
||||
{
|
||||
throw new CakeException("Could not push GitHub packages.");
|
||||
}
|
||||
});
|
||||
|
||||
Task("Publish-NuGet")
|
||||
.WithCriteria(ctx => BuildSystem.IsRunningOnGitHubActions, "Not running on GitHub Actions")
|
||||
.IsDependentOn("Package")
|
||||
@@ -152,7 +147,6 @@ Task("Publish-NuGet")
|
||||
// Targets
|
||||
|
||||
Task("Publish")
|
||||
.IsDependentOn("Publish-GitHub")
|
||||
.IsDependentOn("Publish-NuGet");
|
||||
|
||||
Task("Default")
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||
<DefaultItemExcludes>$(DefaultItemExcludes);output\**;.gitignore</DefaultItemExcludes>
|
||||
<NoWarn>MVC1000</NoWarn>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "7.0.100",
|
||||
"version": "8.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{"version": 2, "width": 122, "height": 5, "title": "breakdown-chart (plain)", "env": {"TERM": "Spectre.Console"}}
|
||||
{"version": 2, "width": 84, "height": 5, "title": "breakdown-chart (plain)", "env": {"TERM": "Spectre.Console"}}
|
||||
[0, "o", "\u001B[31m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[34m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[32m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[37m\u2588\u2588\u001B[0m\u001B[37m\u2588\u2588\u001B[0m\r\n\u001B[31m\u25A0\u001B[0m SCSS \u001B[37m80\u001B[0m \u001B[34m\u25A0\u001B[0m HTML \u001B[37m28.3\u001B[0m \u001B[32m\u25A0\u001B[0m C# \u001B[37m22.6\u001B[0m \u001B[37m\u25A0\u001B[0m JavaScript \u001B[37m6\u001B[0m \r\n\u001B[37m\u25A0\u001B[0m Ruby \u001B[37m6\u001B[0m \u001B[36m\u25A0\u001B[0m Shell \u001B[37m0.1\u001B[0m \r\n"]
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{"version": 2, "width": 122, "height": 5, "title": "breakdown-chart (rich)", "env": {"TERM": "Spectre.Console"}}
|
||||
{"version": 2, "width": 84, "height": 5, "title": "breakdown-chart (rich)", "env": {"TERM": "Spectre.Console"}}
|
||||
[0, "o", "\u001B[38;5;9m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[38;5;12m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[38;5;2m\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u001B[0m\u001B[38;5;11m\u2588\u2588\u001B[0m\u001B[38;5;119m\u2588\u2588\u001B[0m\r\n\u001B[38;5;9m\u25A0\u001B[0m SCSS \u001B[38;5;8m80\u001B[0m \u001B[38;5;12m\u25A0\u001B[0m HTML \u001B[38;5;8m28.3\u001B[0m \u001B[38;5;2m\u25A0\u001B[0m C# \u001B[38;5;8m22.6\u001B[0m \u001B[38;5;11m\u25A0\u001B[0m JavaScript \u001B[38;5;8m6\u001B[0m \r\n\u001B[38;5;119m\u25A0\u001B[0m Ruby \u001B[38;5;8m6\u001B[0m \u001B[38;5;14m\u25A0\u001B[0m Shell \u001B[38;5;8m0.1\u001B[0m \r\n"]
|
||||
|
||||
|
||||
43
docs/input/assets/casts/columns-plain.cast
Normal file
43
docs/input/assets/casts/columns-plain.cast
Normal file
@@ -0,0 +1,43 @@
|
||||
{"version": 2, "width": 84, "height": 24, "title": "columns (plain)", "env": {"TERM": "Spectre.Console"}}
|
||||
[0, "o", "\u001B[?25l"]
|
||||
[0.094, "o", "\u001B[1;37mApple\u001B[0m "]
|
||||
[0.297, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m "]
|
||||
[0.5, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m "]
|
||||
[0.719, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m "]
|
||||
[0.922, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m "]
|
||||
[1.125, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m "]
|
||||
[1.344, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m "]
|
||||
[1.563, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m"]
|
||||
[1.766, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m"]
|
||||
[1.969, "o", "\r\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m\r\n\u001B[1;37mCherry\u001B[0m "]
|
||||
[2.172, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m\r\n\u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m "]
|
||||
[2.375, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m\r\n\u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m "]
|
||||
[2.594, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m "]
|
||||
[2.797, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m "]
|
||||
[3, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \u001B[1;37mGrape\u001B[0m "]
|
||||
[3.219, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m"]
|
||||
[3.422, "o", "\r\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m \u001B[1;37mBreadfruit\u001B[0m\r\n\u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m\r\n\u001B[1;37mJackfruit\u001B[0m "]
|
||||
[3.625, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m "]
|
||||
[3.828, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m "]
|
||||
[4.047, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m "]
|
||||
[4.25, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m "]
|
||||
[4.453, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m "]
|
||||
[4.672, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m "]
|
||||
[4.875, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m "]
|
||||
[5.094, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m "]
|
||||
[5.297, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m "]
|
||||
[5.516, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m "]
|
||||
[5.734, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m "]
|
||||
[5.953, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m "]
|
||||
[6.172, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m "]
|
||||
[6.375, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m "]
|
||||
[6.594, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m "]
|
||||
[6.797, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m "]
|
||||
[7.016, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m "]
|
||||
[7.234, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m \u001B[1;37mStrawberry\u001B[0m "]
|
||||
[7.438, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m \u001B[1;37mStrawberry\u001B[0m \r\n\u001B[1;37mXimenia\u001B[0m "]
|
||||
[7.641, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m \u001B[1;37mStrawberry\u001B[0m \r\n\u001B[1;37mXimenia\u001B[0m "]
|
||||
[7.641, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K"]
|
||||
[7.641, "o", "\u001B[?25h"]
|
||||
[7.656, "o", "\u001B[1;37mApple\u001B[0m \u001B[1;37mApricot\u001B[0m \u001B[1;37mAvocado\u001B[0m \u001B[1;37mBanana\u001B[0m \u001B[1;37mBlackberry\u001B[0m \u001B[1;37mBlueberry\u001B[0m \u001B[1;37mBoysenberry\u001B[0m\r\n\u001B[1;37mBreadfruit\u001B[0m \u001B[1;37mCacao\u001B[0m \u001B[1;37mCherry\u001B[0m \u001B[1;37mCloudberry\u001B[0m \u001B[1;37mCoconut\u001B[0m \u001B[1;37mDragonfruit\u001B[0m \u001B[1;37mElderberry\u001B[0m \r\n\u001B[1;37mGrape\u001B[0m \u001B[1;37mGrapefruit\u001B[0m \u001B[1;37mJackfruit\u001B[0m \u001B[1;37mKiwifruit\u001B[0m \u001B[1;37mLemon\u001B[0m \u001B[1;37mLime\u001B[0m \u001B[1;37mMango\u001B[0m \r\n\u001B[1;37mMelon\u001B[0m \u001B[1;37mOrange\u001B[0m \u001B[1;37mBlood orange\u001B[0m \u001B[1;37mClementine\u001B[0m \u001B[1;37mMandarine\u001B[0m \u001B[1;37mTangerine\u001B[0m \u001B[1;37mPapaya\u001B[0m \r\n\u001B[1;37mPassionfruit\u001B[0m \u001B[1;37mPlum\u001B[0m \u001B[1;37mPineapple\u001B[0m \u001B[1;37mPomelo\u001B[0m \u001B[1;37mRaspberry\u001B[0m \u001B[1;37mSalmonberry\u001B[0m \u001B[1;37mStrawberry\u001B[0m \r\n\u001B[1;37mXimenia\u001B[0m \u001B[1;37mYuzu\u001B[0m \r\n"]
|
||||
|
||||
43
docs/input/assets/casts/columns-rich.cast
Normal file
43
docs/input/assets/casts/columns-rich.cast
Normal file
@@ -0,0 +1,43 @@
|
||||
{"version": 2, "width": 84, "height": 24, "title": "columns (rich)", "env": {"TERM": "Spectre.Console"}}
|
||||
[0, "o", "\u001B[?25l"]
|
||||
[0, "o", "\u001B[1;38;5;11mApple\u001B[0m "]
|
||||
[0.219, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m "]
|
||||
[0.422, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m "]
|
||||
[0.625, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m "]
|
||||
[0.828, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m "]
|
||||
[1.032, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m "]
|
||||
[1.235, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m "]
|
||||
[1.438, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m"]
|
||||
[1.641, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m"]
|
||||
[1.844, "o", "\r\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m\r\n\u001B[1;38;5;11mCherry\u001B[0m "]
|
||||
[2.047, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m\r\n\u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m "]
|
||||
[2.266, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m\r\n\u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m "]
|
||||
[2.485, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m "]
|
||||
[2.703, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m "]
|
||||
[2.907, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \u001B[1;38;5;11mGrape\u001B[0m "]
|
||||
[3.11, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m"]
|
||||
[3.313, "o", "\r\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m \u001B[1;38;5;11mBreadfruit\u001B[0m\r\n\u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m\r\n\u001B[1;38;5;11mJackfruit\u001B[0m "]
|
||||
[3.532, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m "]
|
||||
[3.735, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m "]
|
||||
[3.953, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m "]
|
||||
[4.157, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m "]
|
||||
[4.36, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m "]
|
||||
[4.578, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m "]
|
||||
[4.782, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m "]
|
||||
[4.985, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m "]
|
||||
[5.188, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m "]
|
||||
[5.407, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m "]
|
||||
[5.61, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m "]
|
||||
[5.813, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m "]
|
||||
[6.016, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m "]
|
||||
[6.219, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m "]
|
||||
[6.438, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m "]
|
||||
[6.657, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m "]
|
||||
[6.86, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m "]
|
||||
[7.063, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m \u001B[1;38;5;11mStrawberry\u001B[0m "]
|
||||
[7.266, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m \u001B[1;38;5;11mStrawberry\u001B[0m \r\n\u001B[1;38;5;11mXimenia\u001B[0m "]
|
||||
[7.485, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m \u001B[1;38;5;11mStrawberry\u001B[0m \r\n\u001B[1;38;5;11mXimenia\u001B[0m "]
|
||||
[7.485, "o", "\r\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K\u001B[1A\u001B[2K"]
|
||||
[7.485, "o", "\u001B[?25h"]
|
||||
[7.485, "o", "\u001B[1;38;5;11mApple\u001B[0m \u001B[1;38;5;11mApricot\u001B[0m \u001B[1;38;5;11mAvocado\u001B[0m \u001B[1;38;5;11mBanana\u001B[0m \u001B[1;38;5;11mBlackberry\u001B[0m \u001B[1;38;5;11mBlueberry\u001B[0m \u001B[1;38;5;11mBoysenberry\u001B[0m\r\n\u001B[1;38;5;11mBreadfruit\u001B[0m \u001B[1;38;5;11mCacao\u001B[0m \u001B[1;38;5;11mCherry\u001B[0m \u001B[1;38;5;11mCloudberry\u001B[0m \u001B[1;38;5;11mCoconut\u001B[0m \u001B[1;38;5;11mDragonfruit\u001B[0m \u001B[1;38;5;11mElderberry\u001B[0m \r\n\u001B[1;38;5;11mGrape\u001B[0m \u001B[1;38;5;11mGrapefruit\u001B[0m \u001B[1;38;5;11mJackfruit\u001B[0m \u001B[1;38;5;11mKiwifruit\u001B[0m \u001B[1;38;5;11mLemon\u001B[0m \u001B[1;38;5;11mLime\u001B[0m \u001B[1;38;5;11mMango\u001B[0m \r\n\u001B[1;38;5;11mMelon\u001B[0m \u001B[1;38;5;11mOrange\u001B[0m \u001B[1;38;5;11mBlood orange\u001B[0m \u001B[1;38;5;11mClementine\u001B[0m \u001B[1;38;5;11mMandarine\u001B[0m \u001B[1;38;5;11mTangerine\u001B[0m \u001B[1;38;5;11mPapaya\u001B[0m \r\n\u001B[1;38;5;11mPassionfruit\u001B[0m \u001B[1;38;5;11mPlum\u001B[0m \u001B[1;38;5;11mPineapple\u001B[0m \u001B[1;38;5;11mPomelo\u001B[0m \u001B[1;38;5;11mRaspberry\u001B[0m \u001B[1;38;5;11mSalmonberry\u001B[0m \u001B[1;38;5;11mStrawberry\u001B[0m \r\n\u001B[1;38;5;11mXimenia\u001B[0m \u001B[1;38;5;11mYuzu\u001B[0m \r\n"]
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{"version": 2, "width": 122, "height": 24, "title": "panel (plain)", "env": {"TERM": "Spectre.Console"}}
|
||||
{"version": 2, "width": 84, "height": 24, "title": "panel (plain)", "env": {"TERM": "Spectre.Console"}}
|
||||
[0, "o", "\u2554\u2550\u2550\u001B[4mPasta Menu\u001B[0m\u2550\u2550\u2557\r\n\u2551 \u2551\r\n\u2551 \u2551\r\n\u2551 \u001B[31mSpaghetti\u001B[0m \u2551\r\n\u2551 \u001B[31mLinguini\u001B[0m \u2551\r\n\u2551 \u001B[31mFettucine\u001B[0m \u2551\r\n\u2551 \u001B[31mTortellini\u001B[0m \u2551\r\n\u2551 \u001B[31mCapellini\u001B[0m \u2551\r\n\u2551 \u001B[31mLasagna\u001B[0m \u2551\r\n\u2551 \u2551\r\n\u2551 \u2551\r\n\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\r\n"]
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{"version": 2, "width": 122, "height": 24, "title": "panel (rich)", "env": {"TERM": "Spectre.Console"}}
|
||||
{"version": 2, "width": 84, "height": 24, "title": "panel (rich)", "env": {"TERM": "Spectre.Console"}}
|
||||
[0, "o", "\u2554\u2550\u2550\u001B[4mPasta Menu\u001B[0m\u2550\u2550\u2557\r\n\u2551 \u2551\r\n\u2551 \u2551\r\n\u2551 \u001B[38;5;9mSpaghetti\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mLinguini\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mFettucine\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mTortellini\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mCapellini\u001B[0m \u2551\r\n\u2551 \u001B[38;5;9mLasagna\u001B[0m \u2551\r\n\u2551 \u2551\r\n\u2551 \u2551\r\n\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\r\n"]
|
||||
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
Title: Spectre.Console 0.46 released!
|
||||
Description: .NET 7 support, Layout Widget, JSON rendering
|
||||
Published: 2023-01-10
|
||||
Category: Release Notes
|
||||
Excluded: false
|
||||
---
|
||||
|
||||
Happy new year! 🎉
|
||||
Version 0.46 of Spectre.Console has been released!
|
||||
|
||||
A lot has happened since the last release, but the most notable additions
|
||||
and changes are support for [.NET 7](https://devblogs.microsoft.com/dotnet/announcing-dotnet-7/),
|
||||
the new [Layout](https://spectreconsole.net/widgets/layout) widget, and
|
||||
[rendering of JSON](https://spectreconsole.net/widgets/json). There has also been a lot of long overdue work
|
||||
on the command line argument parser.
|
||||
|
||||
## New Contributors
|
||||
* [@GaryMcD](https://github.com/GaryMcD) made their first contribution in [#961](https://github.com/spectreconsole/spectre.console/pull/961)
|
||||
* [@eduherminio](https://github.com/eduherminio) made their first contribution in [#964](https://github.com/spectreconsole/spectre.console/pull/964)
|
||||
* [@Saalvage](https://github.com/Saalvage) made their first contribution in [#976](https://github.com/spectreconsole/spectre.console/pull/976)
|
||||
* [@BenjaminMichaelis](https://github.com/BenjaminMichaelis) made their first contribution in [#1000](https://github.com/spectreconsole/spectre.console/pull/1000)
|
||||
* [@nilaoda](https://github.com/nilaoda) made their first contribution in [#1012](https://github.com/spectreconsole/spectre.console/pull/1012)
|
||||
* [@picture](https://github.com/picture)-vision made their first contribution in [#1013](https://github.com/spectreconsole/spectre.console/pull/1013)
|
||||
* [@patrickfreilinger](https://github.com/patrickfreilinger) made their first contribution in [#1016](https://github.com/spectreconsole/spectre.console/pull/1016)
|
||||
* [@sowa](https://github.com/sowa)705 made their first contribution in [#1014](https://github.com/spectreconsole/spectre.console/pull/1014)
|
||||
* [@ardalis](https://github.com/ardalis) made their first contribution in [#1021](https://github.com/spectreconsole/spectre.console/pull/1021)
|
||||
* [@Elisha](https://github.com/Elisha)-Aguilera made their first contribution in [#1038](https://github.com/spectreconsole/spectre.console/pull/1038)
|
||||
* [@wguner](https://github.com/wguner) made their first contribution in [#1044](https://github.com/spectreconsole/spectre.console/pull/1044)
|
||||
* [@bcwood](https://github.com/bcwood) made their first contribution in [#1068](https://github.com/spectreconsole/spectre.console/pull/1068)
|
||||
* [@FrankRay](https://github.com/FrankRay)78 made their first contribution in [#1073](https://github.com/spectreconsole/spectre.console/pull/1073)
|
||||
* [@tomkerkhove](https://github.com/tomkerkhove) made their first contribution in [#1089](https://github.com/spectreconsole/spectre.console/pull/1089)
|
||||
* [@ArveSystad](https://github.com/ArveSystad) made their first contribution in [#1090](https://github.com/spectreconsole/spectre.console/pull/1090)
|
||||
* [@maije](https://github.com/maije) made their first contribution in [#1096](https://github.com/spectreconsole/spectre.console/pull/1096)
|
||||
* [@krisrok](https://github.com/krisrok) made their first contribution in [#953](https://github.com/spectreconsole/spectre.console/pull/953)
|
||||
|
||||
## What's changed?
|
||||
* Add support for .NET 7.0 by [@patriksvensson](https://github.com/patriksvensson) in [#1056](https://github.com/spectreconsole/spectre.console/pull/1056)
|
||||
* Add `Layout` widget by [@patriksvensson](https://github.com/patriksvensson) in [#1041](https://github.com/spectreconsole/spectre.console/pull/1041)
|
||||
* Add JSON text renderer by [@patriksvensson](https://github.com/patriksvensson) in [#1086](https://github.com/spectreconsole/spectre.console/pull/1086)
|
||||
* Backward direction of text prompt autocomplete by [@nkochnev](https://github.com/nkochnev) in [#921](https://github.com/spectreconsole/spectre.console/pull/921)
|
||||
* Custom mask for secret by [@GaryMcD](https://github.com/GaryMcD) in [#970](https://github.com/spectreconsole/spectre.console/pull/970)
|
||||
* Allow selections to wrap around by [@Saalvage](https://github.com/Saalvage) in [#976](https://github.com/spectreconsole/spectre.console/pull/976)
|
||||
* Join .NET Foundation by [@patriksvensson](https://github.com/patriksvensson) in [#978](https://github.com/spectreconsole/spectre.console/pull/978)
|
||||
* Adding value: a single semi-colon! by [@johanlindfors](https://github.com/johanlindfors) in [#986](https://github.com/spectreconsole/spectre.console/pull/986)
|
||||
* Fix `@` being used in Figlet font by [@Saalvage](https://github.com/Saalvage) in [#972](https://github.com/spectreconsole/spectre.console/pull/972)
|
||||
* Add new and transferred issues to backlog project by [@patriksvensson](https://github.com/patriksvensson) in [#995](https://github.com/spectreconsole/spectre.console/pull/995)
|
||||
* Pin SDK due to a bug in .NET 6.0.401 by [@patriksvensson](https://github.com/patriksvensson) in [#1011](https://github.com/spectreconsole/spectre.console/pull/1011)
|
||||
* Remove period trimming by [@BenjaminMichaelis](https://github.com/BenjaminMichaelis) in [#1008](https://github.com/spectreconsole/spectre.console/pull/1008)
|
||||
* Allow `PACKET` key on MultiSelectionPrompt by [@nilaoda](https://github.com/nilaoda) in [#1012](https://github.com/spectreconsole/spectre.console/pull/1012)
|
||||
* Added Suckless Simple Terminal to list of ANSI terminals by [@picture](https://github.com/picture)-vision in [#1013](https://github.com/spectreconsole/spectre.console/pull/1013)
|
||||
* Add culture option to `TypeConverterHelper`, `TextPrompt` and `AnsiConsole` by [@sowa](https://github.com/sowa)705 in [#1014](https://github.com/spectreconsole/spectre.console/pull/1014)
|
||||
* Minor typo fixes by [@ardalis](https://github.com/ardalis) in [#1021](https://github.com/spectreconsole/spectre.console/pull/1021)
|
||||
* Alignment fixes by [@patriksvensson](https://github.com/patriksvensson) in [#1066](https://github.com/spectreconsole/spectre.console/pull/1066)
|
||||
* `IndexOf` replaced by Count at Add method - Performance issue #975 fixed by [@maije](https://github.com/maije) in [#1096](https://github.com/spectreconsole/spectre.console/pull/1096)
|
||||
* Modified tokenizer not to break on on `]]]` at the end of a style by [@nils](https://github.com/nils)-a in [#1027](https://github.com/spectreconsole/spectre.console/pull/1027)
|
||||
* Command line argument parsing improvements by [@FrankRay](https://github.com/FrankRay)78 in [#1048](https://github.com/spectreconsole/spectre.console/pull/1048)
|
||||
* Show help for default command by [@krisrok](https://github.com/krisrok) in [#953](https://github.com/spectreconsole/spectre.console/pull/953)
|
||||
* Automatically display default values of options in the help page by @0xced in [#1032](https://github.com/spectreconsole/spectre.console/pull/1032)
|
||||
|
||||
## Documentation updates
|
||||
* Add link to documentation in README by [@ardalis](https://github.com/ardalis) in [#1030](https://github.com/spectreconsole/spectre.console/pull/1030)
|
||||
* Update `.NET 5` references in docs by [@eduherminio](https://github.com/eduherminio) in [#964](https://github.com/spectreconsole/spectre.console/pull/964)
|
||||
* Blog date fix by [@phil](https://github.com/phil)-scott-78 in [#963](https://github.com/spectreconsole/spectre.console/pull/963)
|
||||
* Update sponsors by [@tomkerkhove](https://github.com/tomkerkhove) in [#1089](https://github.com/spectreconsole/spectre.console/pull/1089)
|
||||
* Inline `CommandArgument` required/optional style in template parameter docs by [@ArveSystad](https://github.com/ArveSystad) in [#1090](https://github.com/spectreconsole/spectre.console/pull/1090)
|
||||
* Add documentation for `BreakdownChart` by [@BenjaminMichaelis](https://github.com/BenjaminMichaelis) in [#1000](https://github.com/spectreconsole/spectre.console/pull/1000)
|
||||
* Create `Panel` documentation by [@patrickfreilinger](https://github.com/patrickfreilinger) in [#1016](https://github.com/spectreconsole/spectre.console/pull/1016)
|
||||
* Added details for using links within markup. by [@GaryMcD](https://github.com/GaryMcD) in [#961](https://github.com/spectreconsole/spectre.console/pull/961)
|
||||
* Added documentation for `Rows` widget by [@Elisha](https://github.com/Elisha)-Aguilera in [#1038](https://github.com/spectreconsole/spectre.console/pull/1038)
|
||||
* Added documentation guide for `Grid` widget by [@Elisha](https://github.com/Elisha)-Aguilera in [#1043](https://github.com/spectreconsole/spectre.console/pull/1043)
|
||||
* Added documentation guide for the `Padder` widget by [@Elisha](https://github.com/Elisha)-Aguilera in [#1046](https://github.com/spectreconsole/spectre.console/pull/1046)
|
||||
* Created a `Columns` widget documentation by [@wguner](https://github.com/wguner) in [#1044](https://github.com/spectreconsole/spectre.console/pull/1044)
|
||||
* Fixed typo in `Panel` documentation [@bcwood](https://github.com/bcwood) in [#1068](https://github.com/spectreconsole/spectre.console/pull/1068)
|
||||
* Clarified the license for `SixLabors.ImageSharp` by [@FrankRay](https://github.com/FrankRay)78 in [#1073](https://github.com/spectreconsole/spectre.console/pull/1073)
|
||||
* Add documentation for `Layout` by [@patriksvensson](https://github.com/patriksvensson) in [#1127](https://github.com/spectreconsole/spectre.console/pull/1127)
|
||||
|
||||
## Dependencies
|
||||
* Update dependency `Wcwidth.Sources` to `v1` by [@renovate](https://github.com/renovate) in [#969](https://github.com/spectreconsole/spectre.console/pull/969)
|
||||
* Update `actions/setup-dotnet` action to `v3` by [@renovate](https://github.com/renovate) in [#982](https://github.com/spectreconsole/spectre.console/pull/982)
|
||||
* Update dependency `Microsoft.NET.Test.Sdk` to `v17.3.2` by [@renovate](https://github.com/renovate) in [#977](https://github.com/spectreconsole/spectre.console/pull/977)
|
||||
* Update dependency `cake.tool` to `v2.3.0` by [@renovate](https://github.com/renovate) in [#1015](https://github.com/spectreconsole/spectre.console/pull/1015)
|
||||
@@ -0,0 +1,48 @@
|
||||
Title: Spectre.Console 0.47 released!
|
||||
Description: Alacritty terminal support, command line improvements
|
||||
Published: 2023-05-19
|
||||
Category: Release Notes
|
||||
Excluded: false
|
||||
---
|
||||
|
||||
Version 0.47 of Spectre.Console has been released!
|
||||
|
||||
There are a lot of fixes and improvements in this release, the most noteworthy changes being support for the [Alacritty](https://github.com/alacritty/alacritty) terminal and continued improvements to command line parsing.
|
||||
|
||||
Thank you to all contributers.
|
||||
|
||||
## New Contributors
|
||||
* [@wbaldoumas](https://github.com/wbaldoumas) made their first contribution in [#1143](https://github.com/spectreconsole/spectre.console/pull/1143)
|
||||
* [@MartinZikmund](https://github.com/MartinZikmund) made their first contribution in [#1151](https://github.com/spectreconsole/spectre.console/pull/1151)
|
||||
* [@ilyahryapko](https://github.com/ilyahryapko) made their first contribution in [#1131](https://github.com/spectreconsole/spectre.console/pull/1131)
|
||||
* [@meziantou](https://github.com/meziantou) made their first contribution in [#1174](https://github.com/spectreconsole/spectre.console/pull/1174)
|
||||
* [@MaxAtoms](https://github.com/MaxAtoms) made their first contribution in [#1211](https://github.com/spectreconsole/spectre.console/pull/1211)
|
||||
* [@phillip-haydon](https://github.com/phillip-haydon) made their first contribution in [#1218](https://github.com/spectreconsole/spectre.console/pull/1218)
|
||||
|
||||
## What's Changed
|
||||
* Add Alacritty to the supported terminals in AnsiDetector by [@MaxAtoms](https://github.com/MaxAtoms) in [#1211](https://github.com/spectreconsole/spectre.console/pull/1211)
|
||||
* Add an implicit operator to convert from Color to Style by [@0xced](https://github.com/0xced) in [#1160](https://github.com/spectreconsole/spectre.console/pull/1160)
|
||||
* Allow case-insensitive confirmation prompt by [@MartinZikmund](https://github.com/MartinZikmund) in [#1151](https://github.com/spectreconsole/spectre.console/pull/1151)
|
||||
* Allow configuration of confirmation prompt comparison via `StringComparer` by [@MartinZikmund](https://github.com/MartinZikmund) in [#1161](https://github.com/spectreconsole/spectre.console/pull/1161)
|
||||
* Do not register analyzer if SpectreConsole is not available in the current compilation by [@meziantou](https://github.com/meziantou) in [#1172](https://github.com/spectreconsole/spectre.console/pull/1172)
|
||||
* Ensure correct comparer is used for `TextPrompt` by [@MartinZikmund](https://github.com/MartinZikmund) in [#1152](https://github.com/spectreconsole/spectre.console/pull/1152)
|
||||
* Forward CancellationToken to GetOperation by [@meziantou](https://github.com/meziantou) in [#1173](https://github.com/spectreconsole/spectre.console/pull/1173)
|
||||
* Fix minor typo in Prompt example by [@Frassle](https://github.com/Frassle) in [#1183](https://github.com/spectreconsole/spectre.console/pull/1183)
|
||||
* Fix coconut spelling by [@phillip-haydon](https://github.com/phillip-haydon) in [#1218](https://github.com/spectreconsole/spectre.console/pull/1218)
|
||||
* Improve conversion error messages by [@0xced](https://github.com/0xced) in [#1141](https://github.com/spectreconsole/spectre.console/pull/1141)
|
||||
* Make the code fix more robust and detect more symbols of type IAnsiConsole by [@meziantou](https://github.com/meziantou) in [#1169](https://github.com/spectreconsole/spectre.console/pull/1169)
|
||||
* Minor Refactorings by [@Elisha-Aguilera](https://github.com/Elisha-Aguilera) in [#1081](https://github.com/spectreconsole/spectre.console/pull/1081)
|
||||
* Simplify access to the SemanticModel in analyzers by [@meziantou](https://github.com/meziantou) in [#1167](https://github.com/spectreconsole/spectre.console/pull/1167)
|
||||
* Use SymbolEqualityComparer.Default when possible by [@meziantou](https://github.com/meziantou) in [#1171](https://github.com/spectreconsole/spectre.console/pull/1171)
|
||||
* Use StringComparison.Ordinal instead of culture-sensitive comparisons by [@meziantou](https://github.com/meziantou) in [#1174](https://github.com/spectreconsole/spectre.console/pull/1174)
|
||||
|
||||
## Command line updates
|
||||
* Add possibility to set description and/or data for the default command by [@0xced](https://github.com/0xced) in [#1091](https://github.com/spectreconsole/spectre.console/pull/1091)
|
||||
* Add support for converting command parameters into FileInfo and DirectoryInfo by [@0xced](https://github.com/0xced) in [#1145](https://github.com/spectreconsole/spectre.console/pull/1145)
|
||||
* Add support for arrays in \[DefaultValue\] attributes by [@0xced](https://github.com/0xced) in [#1164](https://github.com/spectreconsole/spectre.console/pull/1164)
|
||||
* Add ability to pass example args using `params` syntax by [@seclerp](https://github.com/seclerp) in [#1166](https://github.com/spectreconsole/spectre.console/pull/1166)
|
||||
* Alias for branches by [@ilyahryapko](https://github.com/ilyahryapko) in [#1131](https://github.com/spectreconsole/spectre.console/pull/1131)
|
||||
* Command line improvements by [@FrankRay78](https://github.com/FrankRay78) in [#1103](https://github.com/spectreconsole/spectre.console/pull/1103)
|
||||
|
||||
## Documentation updates
|
||||
* Alignment => Justification Docs Fixes by [@wbaldoumas](https://github.com/wbaldoumas) in [#1143](https://github.com/spectreconsole/spectre.console/pull/1143)
|
||||
47
docs/input/cli/command-help.md
Normal file
47
docs/input/cli/command-help.md
Normal file
@@ -0,0 +1,47 @@
|
||||
Title: Command Help
|
||||
Order: 13
|
||||
Description: "Console applications built with *Spectre.Console.Cli* include automatically generated help command line help."
|
||||
---
|
||||
|
||||
Console applications built with `Spectre.Console.Cli` include automatically generated help which is displayed when `-h` or `--help` has been specified on the command line.
|
||||
|
||||
The automatically generated help is derived from the configured commands and their command settings.
|
||||
|
||||
The help is also context aware and tailored depending on what has been specified on the command line before it. For example,
|
||||
|
||||
1. When `-h` or `--help` appears immediately after the application name (eg. `application.exe --help`), then the help displayed is a high-level summary of the application, including any command line examples and a listing of all possible commands the user can execute.
|
||||
|
||||
2. When `-h` or `--help` appears immediately after a command has been specified (eg. `application.exe command --help`), then the help displayed is specific to the command and includes information about command specific switches and any default values.
|
||||
|
||||
`HelpProvider` is the `Spectre.Console` class responsible for determining context and preparing the help text to write to the console. It is an implementation of the public interface `IHelpProvider`.
|
||||
|
||||
## Custom help providers
|
||||
|
||||
Whilst it shouldn't be common place to implement your own help provider, it is however possible.
|
||||
|
||||
You are able to implement your own `IHelpProvider` and configure a `CommandApp` to use that instead of the Spectre.Console help provider.
|
||||
|
||||
```csharp
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Help;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
var app = new CommandApp<DefaultCommand>();
|
||||
|
||||
app.Configure(config =>
|
||||
{
|
||||
// Register the custom help provider
|
||||
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
There is a working [example of a custom help provider](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Help) demonstrating this.
|
||||
|
||||
@@ -43,7 +43,7 @@ For more complex command hierarchical configurations, they can also be composed
|
||||
|
||||
## Customizing Command Configurations
|
||||
|
||||
The `Configure` method is also used to change how help for the commands is generated. This configuration will give our command an additional alias of `file-size` and a description to be used when displaying the help. Additional, an example is specified that will be parsed and displayed for users asking for help. Multiple examples can be provided. Commands can also be marked as hidden. With this option they are still executable, but will not be displayed in help screens.
|
||||
The `Configure` method is also used to change how help for the commands is generated. This configuration will give our command an additional alias of `file-size` and a description to be used when displaying the help. Additionally, an example is specified that will be parsed and displayed for users asking for help. Multiple examples can be provided. Commands can also be marked as hidden. With this option they are still executable, but will not be displayed in help screens.
|
||||
|
||||
``` csharp
|
||||
var app = new CommandApp();
|
||||
|
||||
@@ -37,8 +37,8 @@ app.Configure(config =>
|
||||
config.AddCommand<HelloCommand>("hello")
|
||||
.WithAlias("hola")
|
||||
.WithDescription("Say hello")
|
||||
.WithExample(new []{"hello", "Phil"})
|
||||
.WithExample(new []{"hello", "Phil", "--count", "4"});
|
||||
.WithExample("hello", "Phil")
|
||||
.WithExample("hello", "Phil", "--count", "4");
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ Order: 12
|
||||
Description: "Handling exceptions in *Spectre.Console.Cli*"
|
||||
---
|
||||
|
||||
Exceptions happen.
|
||||
Exceptions happen.
|
||||
|
||||
`Spectre.Console.Cli` handles exceptions, writes a user friendly message to the console and sets the exitCode
|
||||
of the application to `-1`.
|
||||
@@ -49,11 +49,11 @@ namespace MyApp
|
||||
|
||||
## Using a custom ExceptionHandler
|
||||
|
||||
Using the `SetErrorHandler()` during configuration it is possible to handle exceptions in a defined way.
|
||||
Using the `SetExceptionHandler()` during configuration it is possible to handle exceptions in a defined way.
|
||||
This method comes in two flavours: One that uses the default exitCode (or `return` value) of `-1` and one
|
||||
where the exitCode needs to be supplied.
|
||||
|
||||
### Using `SetErrorHandler(Func<Exception, int> handler)`
|
||||
### Using `SetExceptionHandler(Func<Exception, int> handler)`
|
||||
|
||||
Using this method exceptions can be handled in a custom way. The return value of the handler is used as
|
||||
the exitCode for the application.
|
||||
@@ -84,9 +84,9 @@ namespace MyApp
|
||||
}
|
||||
```
|
||||
|
||||
### Using `SetErrorHandler(Action<Exception> handler)`
|
||||
### Using `SetExceptionHandler(Action<Exception> handler)`
|
||||
|
||||
Using this method exceptions can be handled in a custom way, much the same as with the `SetErrorHandler(Func<Exception, int> handler)`.
|
||||
Using this method exceptions can be handled in a custom way, much the same as with the `SetExceptionHandler(Func<Exception, int> handler)`.
|
||||
Using the `Action` as the handler however, it is not possible (or required) to supply a return value.
|
||||
The exitCode for the application will be `-1`.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Title: Specifying Settings
|
||||
Order: 5
|
||||
Description: "How to define command line argument settings for your *Spectre.Console.Cli* Commands"
|
||||
Reference:
|
||||
Reference:
|
||||
- T:Spectre.Console.Cli.CommandSettings
|
||||
- T:Spectre.Console.Cli.CommandArgumentAttribute
|
||||
- T:Spectre.Console.Cli.CommandOptionAttribute
|
||||
@@ -26,7 +26,7 @@ This setting file tells `Spectre.Console.Cli` that our command has two parameter
|
||||
|
||||
## CommandArgument
|
||||
|
||||
Arguments have a position and a name. The name is not only used for generating help, but its formatting is used to determine whether or not the argument is optional. The name must either be surrounded by square brackets (e.g. `[name]`) or angle brackets (e.g. `<name>`). Angle brackets denote required whereas square brackets denote optional. If neither are specified an exception will be thrown.
|
||||
Arguments have a position and a name. The name is not only used for generating help, but its formatting is used to determine whether or not the argument is optional. Angle brackets denote a required argument (e.g. `<name>`) whereas square brackets denote an optional argument (e.g. `[name]`). If neither are specified an exception will be thrown.
|
||||
|
||||
The position is used for scenarios where there could be more than one argument.
|
||||
|
||||
@@ -86,7 +86,9 @@ public int Count { get; set; }
|
||||
|
||||
## Arrays
|
||||
|
||||
`CommandArgument` can be defined as arrays and any additional parameters will be included in the value. For example
|
||||
### Argument Vector
|
||||
|
||||
One (exactly one) `CommandArgument` can be defined as an array, and any additional parameters will be included in the value. For example:
|
||||
|
||||
```csharp
|
||||
[CommandArgument(0, "[name]")]
|
||||
@@ -95,6 +97,19 @@ public string[] Name { get; set; }
|
||||
|
||||
Would allow the user to run `app.exe Dwayne Elizondo "Mountain Dew" Herbert Camacho`. The settings passed to the command would have a 5 element array consisting of Dwayne, Elizondo, Mountain Dew, Herbert and Camacho.
|
||||
|
||||
A command can have only one argument vector, and it needs to be the last argument. (I.e. there can be no `CommandArgument` whose position is higher than that of the argument vector.)
|
||||
|
||||
### Option Arrays
|
||||
|
||||
A `CommandOption` can be defined as an array like the following:
|
||||
|
||||
```csharp
|
||||
[CommandOption("-n|--name <VALUES>")]
|
||||
public string[] Names { get; set; },
|
||||
```
|
||||
|
||||
This would allow the user to run `app.exe --name Dwayne --name Elizondo --name "Mountain Dew" --name Herbert --name Camacho` and would result in a 5 element array consisting of Dwayne, Elizondo, Mountain Dew, Herbert and Camacho.
|
||||
|
||||
## Constructors
|
||||
|
||||
`Spectre.Console.Cli` supports constructor initialization and init only initialization. For constructor initialization, the parameter name of the constructor must match the name of the property name of the settings class. Order does not matter.
|
||||
|
||||
@@ -29,13 +29,13 @@ var fruits = AnsiConsole.Prompt(
|
||||
"[grey](Press [blue]<space>[/] to toggle a fruit, " +
|
||||
"[green]<enter>[/] to accept)[/]")
|
||||
.AddChoices(new[] {
|
||||
"Apple", "Apricot", "Avocado",
|
||||
"Apple", "Apricot", "Avocado",
|
||||
"Banana", "Blackcurrant", "Blueberry",
|
||||
"Cherry", "Cloudberry", "Cocunut",
|
||||
"Cherry", "Cloudberry", "Coconut",
|
||||
}));
|
||||
|
||||
// Write the selected fruits to the terminal
|
||||
foreach (string fruit in fruits)
|
||||
foreach (string fruit in fruits)
|
||||
{
|
||||
AnsiConsole.WriteLine(fruit);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ Spectre.Console can render [FIGlet](http://www.figlet.org/) text by using the `F
|
||||
```csharp
|
||||
AnsiConsole.Write(
|
||||
new FigletText("Hello")
|
||||
.LeftAligned()
|
||||
.LeftJustified()
|
||||
.Color(Color.Red));
|
||||
```
|
||||
|
||||
@@ -26,6 +26,6 @@ var font = FigletFont.Load("starwars.flf");
|
||||
|
||||
AnsiConsole.Write(
|
||||
new FigletText(font, "Hello")
|
||||
.LeftAligned()
|
||||
.LeftJustified()
|
||||
.Color(Color.Red));
|
||||
```
|
||||
|
||||
@@ -45,16 +45,16 @@ grid.AddColumn();
|
||||
|
||||
// Add header row
|
||||
grid.AddRow(new Text[]{
|
||||
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftAligned(),
|
||||
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftJustified(),
|
||||
new Text("Header 2", new Style(Color.Green, Color.Black)).Centered(),
|
||||
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightAligned()
|
||||
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightJustified()
|
||||
});
|
||||
|
||||
// Add content row
|
||||
grid.AddRow(new Text[]{
|
||||
new Text("Row 1").LeftAligned(),
|
||||
new Text("Row 1").LeftJustified(),
|
||||
new Text("Row 2").Centered(),
|
||||
new Text("Row 3").RightAligned()
|
||||
new Text("Row 3").RightJustified()
|
||||
});
|
||||
|
||||
// Write centered cell grid contents to Console
|
||||
@@ -73,9 +73,9 @@ grid.AddColumn();
|
||||
|
||||
// Add header row
|
||||
grid.AddRow(new Text[]{
|
||||
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftAligned(),
|
||||
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftJustified(),
|
||||
new Text("Header 2", new Style(Color.Green, Color.Black)).Centered(),
|
||||
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightAligned()
|
||||
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightJustified()
|
||||
});
|
||||
|
||||
var embedded = new Grid();
|
||||
@@ -88,7 +88,7 @@ embedded.AddRow(new Text("Embedded III"), new Text("Embedded IV"));
|
||||
|
||||
// Add content row
|
||||
grid.AddRow(
|
||||
new Text("Row 1").LeftAligned(),
|
||||
new Text("Row 1").LeftJustified(),
|
||||
new Text("Row 2").Centered(),
|
||||
embedded
|
||||
);
|
||||
|
||||
@@ -41,7 +41,7 @@ You can set the rule's title alignment.
|
||||
|
||||
```csharp
|
||||
var rule = new Rule("[red]Hello[/]");
|
||||
rule.Alignment = Justify.Left;
|
||||
rule.Justification = Justify.Left;
|
||||
AnsiConsole.Write(rule);
|
||||
```
|
||||
|
||||
@@ -53,7 +53,7 @@ You can also specify it via an extension method:
|
||||
|
||||
```csharp
|
||||
var rule = new Rule("[red]Hello[/]");
|
||||
rule.LeftAligned();
|
||||
rule.LeftJustified();
|
||||
AnsiConsole.Write(rule);
|
||||
```
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ You can also specify styles via extension methods:
|
||||
|
||||
```csharp
|
||||
var path = new TextPath("C:/This/Path/Is/Too/Long/To/Fit/In/The/Area.txt")
|
||||
.RightAligned();
|
||||
.RightJustified();
|
||||
```
|
||||
|
||||
## Styling
|
||||
|
||||
@@ -3,17 +3,11 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"cake.tool": {
|
||||
"version": "3.0.0",
|
||||
"version": "4.0.0",
|
||||
"commands": [
|
||||
"dotnet-cake"
|
||||
]
|
||||
},
|
||||
"gpr": {
|
||||
"version": "0.1.281",
|
||||
"commands": [
|
||||
"gpr"
|
||||
]
|
||||
},
|
||||
"dotnet-example": {
|
||||
"version": "2.0.0",
|
||||
"commands": [
|
||||
|
||||
@@ -9,7 +9,7 @@ public static partial class Program
|
||||
{
|
||||
[CommandOption("--count")]
|
||||
[Description("The number of bars to print")]
|
||||
[DefaultValue(1)]
|
||||
[DefaultValue(3)]
|
||||
public int Count { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<ExampleName>Delegates</ExampleName>
|
||||
<ExampleDescription>Demonstrates how to specify commands as delegates.</ExampleDescription>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Threading.Tasks;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
@@ -14,7 +15,13 @@ public static partial class Program
|
||||
.WithDescription("Foos the bars");
|
||||
|
||||
config.AddDelegate<BarSettings>("bar", Bar)
|
||||
.WithDescription("Bars the foos"); ;
|
||||
.WithDescription("Bars the foos");
|
||||
|
||||
config.AddAsyncDelegate("fooAsync", FooAsync)
|
||||
.WithDescription("Foos the bars asynchronously");
|
||||
|
||||
config.AddAsyncDelegate<BarSettings>("barAsync", BarAsync)
|
||||
.WithDescription("Bars the foos asynchronously");
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
@@ -35,4 +42,20 @@ public static partial class Program
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static Task<int> FooAsync(CommandContext context)
|
||||
{
|
||||
AnsiConsole.WriteLine("Foo");
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
|
||||
private static Task<int> BarAsync(CommandContext context, BarSettings settings)
|
||||
{
|
||||
for (var index = 0; index < settings.Count; index++)
|
||||
{
|
||||
AnsiConsole.WriteLine("Bar");
|
||||
}
|
||||
|
||||
return Task.FromResult(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<ExampleName>Demo</ExampleName>
|
||||
<ExampleDescription>Demonstrates the most common use cases of Spectre.Cli.</ExampleDescription>
|
||||
|
||||
@@ -15,25 +15,25 @@ public static class Program
|
||||
{
|
||||
config.SetApplicationName("fake-dotnet");
|
||||
config.ValidateExamples();
|
||||
config.AddExample(new[] { "run", "--no-build" });
|
||||
|
||||
// Run
|
||||
config.AddCommand<RunCommand>("run");
|
||||
|
||||
// Add
|
||||
config.AddBranch<AddSettings>("add", add =>
|
||||
{
|
||||
add.SetDescription("Add a package or reference to a .NET project");
|
||||
add.AddCommand<AddPackageCommand>("package");
|
||||
add.AddCommand<AddReferenceCommand>("reference");
|
||||
config.AddExample("run", "--no-build");
|
||||
|
||||
// Run
|
||||
config.AddCommand<RunCommand>("run");
|
||||
|
||||
// Add
|
||||
config.AddBranch<AddSettings>("add", add =>
|
||||
{
|
||||
add.SetDescription("Add a package or reference to a .NET project");
|
||||
add.AddCommand<AddPackageCommand>("package");
|
||||
add.AddCommand<AddReferenceCommand>("reference");
|
||||
});
|
||||
|
||||
// Serve
|
||||
config.AddCommand<ServeCommand>("serve")
|
||||
.WithExample("serve", "-o", "firefox")
|
||||
.WithExample("serve", "--port", "80", "-o", "firefox");
|
||||
});
|
||||
|
||||
// Serve
|
||||
config.AddCommand<ServeCommand>("serve")
|
||||
.WithExample(new[] { "serve", "-o", "firefox" })
|
||||
.WithExample(new[] { "serve", "--port", "80", "-o", "firefox" });
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<ExampleName>Dynamic</ExampleName>
|
||||
<ExampleDescription>Demonstrates how to define dynamic commands.</ExampleDescription>
|
||||
|
||||
30
examples/Cli/Help/CustomHelpProvider.cs
Normal file
30
examples/Cli/Help/CustomHelpProvider.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Linq;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Cli;
|
||||
using Spectre.Console.Cli.Help;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Help;
|
||||
|
||||
/// <summary>
|
||||
/// Example showing how to extend the built-in Spectre.Console help provider
|
||||
/// by rendering a custom banner at the top of the help information
|
||||
/// </summary>
|
||||
internal class CustomHelpProvider : HelpProvider
|
||||
{
|
||||
public CustomHelpProvider(ICommandAppSettings settings)
|
||||
: base(settings)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new Text("--------------------------------------"), Text.NewLine,
|
||||
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
|
||||
new Text("--------------------------------------"), Text.NewLine,
|
||||
Text.NewLine,
|
||||
};
|
||||
}
|
||||
}
|
||||
20
examples/Cli/Help/DefaultCommand.cs
Normal file
20
examples/Cli/Help/DefaultCommand.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Help;
|
||||
|
||||
public sealed class DefaultCommand : Command
|
||||
{
|
||||
private IAnsiConsole _console;
|
||||
|
||||
public DefaultCommand(IAnsiConsole console)
|
||||
{
|
||||
_console = console;
|
||||
}
|
||||
|
||||
public override int Execute(CommandContext context)
|
||||
{
|
||||
_console.WriteLine("Hello world");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
18
examples/Cli/Help/Help.csproj
Normal file
18
examples/Cli/Help/Help.csproj
Normal file
@@ -0,0 +1,18 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<ExampleName>Help</ExampleName>
|
||||
<ExampleDescription>Demonstrates how to extend the built-in Spectre.Console help provider to render a custom banner at the top of the help information.</ExampleDescription>
|
||||
<ExampleGroup>Cli</ExampleGroup>
|
||||
<ExampleVisible>false</ExampleVisible>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
19
examples/Cli/Help/Program.cs
Normal file
19
examples/Cli/Help/Program.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Help;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
var app = new CommandApp<DefaultCommand>();
|
||||
|
||||
app.Configure(config =>
|
||||
{
|
||||
// Register the custom help provider
|
||||
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<ExampleName>Injection</ExampleName>
|
||||
<ExampleDescription>Demonstrates how to use dependency injection with Spectre.Cli.</ExampleDescription>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<ExampleName>Logging</ExampleName>
|
||||
<ExampleDescription>Demonstrates how to dynamically configure Serilog for logging using parameters from a command.</ExampleDescription>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Screens</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to use alternate screens.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Borders</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates the different kind of borders.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -20,21 +20,22 @@ public static class Program
|
||||
{
|
||||
static IRenderable CreatePanel(string name, BoxBorder border)
|
||||
{
|
||||
return new Panel($"This is a panel with\nthe [yellow]{name}[/] border.")
|
||||
.Header($" [blue]{name}[/] ", Justify.Center)
|
||||
.Border(border)
|
||||
.BorderStyle(Style.Parse("grey"));
|
||||
return
|
||||
new Panel($"This is a panel with\nthe [yellow]{name}[/] border.")
|
||||
.Header($" [blue]{name}[/] ", Justify.Center)
|
||||
.Border(border)
|
||||
.BorderStyle(Style.Parse("grey"));
|
||||
}
|
||||
|
||||
var items = new[]
|
||||
{
|
||||
CreatePanel("Ascii", BoxBorder.Ascii),
|
||||
CreatePanel("Square", BoxBorder.Square),
|
||||
CreatePanel("Rounded", BoxBorder.Rounded),
|
||||
CreatePanel("Heavy", BoxBorder.Heavy),
|
||||
CreatePanel("Double", BoxBorder.Double),
|
||||
CreatePanel("None", BoxBorder.None),
|
||||
};
|
||||
CreatePanel("Ascii", BoxBorder.Ascii),
|
||||
CreatePanel("Square", BoxBorder.Square),
|
||||
CreatePanel("Rounded", BoxBorder.Rounded),
|
||||
CreatePanel("Heavy", BoxBorder.Heavy),
|
||||
CreatePanel("Double", BoxBorder.Double),
|
||||
CreatePanel("None", BoxBorder.None),
|
||||
};
|
||||
|
||||
AnsiConsole.Write(
|
||||
new Padder(
|
||||
@@ -47,6 +48,7 @@ public static class Program
|
||||
static IRenderable CreateTable(string name, TableBorder border)
|
||||
{
|
||||
var table = new Table().Border(border);
|
||||
table.ShowRowSeparators();
|
||||
table.AddColumn("[yellow]Header 1[/]", c => c.Footer("[grey]Footer 1[/]"));
|
||||
table.AddColumn("[yellow]Header 2[/]", col => col.Footer("[grey]Footer 2[/]").RightAligned());
|
||||
table.AddRow("Cell", "Cell");
|
||||
@@ -54,29 +56,23 @@ public static class Program
|
||||
|
||||
return new Panel(table)
|
||||
.Header($" [blue]{name}[/] ", Justify.Center)
|
||||
.PadBottom(1)
|
||||
.NoBorder();
|
||||
}
|
||||
|
||||
var items = new[]
|
||||
{
|
||||
CreateTable("Ascii", TableBorder.Ascii),
|
||||
CreateTable("Ascii2", TableBorder.Ascii2),
|
||||
CreateTable("AsciiDoubleHead", TableBorder.AsciiDoubleHead),
|
||||
CreateTable("Horizontal", TableBorder.Horizontal),
|
||||
CreateTable("Simple", TableBorder.Simple),
|
||||
CreateTable("SimpleHeavy", TableBorder.SimpleHeavy),
|
||||
CreateTable("Minimal", TableBorder.Minimal),
|
||||
CreateTable("MinimalHeavyHead", TableBorder.MinimalHeavyHead),
|
||||
CreateTable("MinimalDoubleHead", TableBorder.MinimalDoubleHead),
|
||||
CreateTable("Square", TableBorder.Square),
|
||||
CreateTable("Rounded", TableBorder.Rounded),
|
||||
CreateTable("Heavy", TableBorder.Heavy),
|
||||
CreateTable("HeavyEdge", TableBorder.HeavyEdge),
|
||||
CreateTable("HeavyHead", TableBorder.HeavyHead),
|
||||
CreateTable("Double", TableBorder.Double),
|
||||
CreateTable("DoubleEdge", TableBorder.DoubleEdge),
|
||||
CreateTable("Markdown", TableBorder.Markdown),
|
||||
};
|
||||
CreateTable("Ascii", TableBorder.Ascii), CreateTable("Ascii2", TableBorder.Ascii2),
|
||||
CreateTable("AsciiDoubleHead", TableBorder.AsciiDoubleHead),
|
||||
CreateTable("Horizontal", TableBorder.Horizontal), CreateTable("Simple", TableBorder.Simple),
|
||||
CreateTable("SimpleHeavy", TableBorder.SimpleHeavy), CreateTable("Minimal", TableBorder.Minimal),
|
||||
CreateTable("MinimalHeavyHead", TableBorder.MinimalHeavyHead),
|
||||
CreateTable("MinimalDoubleHead", TableBorder.MinimalDoubleHead),
|
||||
CreateTable("Square", TableBorder.Square), CreateTable("Rounded", TableBorder.Rounded),
|
||||
CreateTable("Heavy", TableBorder.Heavy), CreateTable("HeavyEdge", TableBorder.HeavyEdge),
|
||||
CreateTable("HeavyHead", TableBorder.HeavyHead), CreateTable("Double", TableBorder.Double),
|
||||
CreateTable("DoubleEdge", TableBorder.DoubleEdge), CreateTable("Markdown", TableBorder.Markdown),
|
||||
};
|
||||
|
||||
AnsiConsole.Write(new Columns(items).Collapse());
|
||||
}
|
||||
@@ -87,4 +83,4 @@ public static class Program
|
||||
AnsiConsole.Write(new Rule($"[white bold]{title}[/]").RuleStyle("grey").LeftJustified());
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Calendars</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render calendars.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Canvas</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render pixels and images.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Charts</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render charts in a console.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -23,6 +23,7 @@ public static class Program
|
||||
.FullSize()
|
||||
.Width(60)
|
||||
.ShowPercentage()
|
||||
.WithValueColor(Color.Orange1)
|
||||
.AddItem("SCSS", 37, Color.Red)
|
||||
.AddItem("HTML", 28.3, Color.Blue)
|
||||
.AddItem("C#", 22.6, Color.Green)
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Colors</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to use [yellow]c[/][red]o[/][green]l[/][blue]o[/][aqua]r[/][lime]s[/] in the console.</ExampleDescription>
|
||||
<ExampleGroup>Misc</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Columns</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render data into columns.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Cursor</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to move the cursor.</ExampleDescription>
|
||||
<ExampleGroup>Misc</ExampleGroup>
|
||||
|
||||
15
examples/Console/Decorations/Decorations.csproj
Normal file
15
examples/Console/Decorations/Decorations.csproj
Normal file
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Decorations</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to [italic]use[/] [bold]decorations[/] [dim]in[/] the console.</ExampleDescription>
|
||||
<ExampleGroup>Misc</ExampleGroup>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
29
examples/Console/Decorations/Program.cs
Normal file
29
examples/Console/Decorations/Program.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Colors;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
AnsiConsole.ResetDecoration();
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
if (AnsiConsole.Profile.Capabilities.Ansi)
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[bold green]ANSI Decorations[/]"));
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiConsole.Write(new Rule("[bold red]Legacy Decorations (unsupported)[/]"));
|
||||
}
|
||||
|
||||
var decorations = System.Enum.GetValues(typeof(Decoration));
|
||||
foreach (var decoration in decorations)
|
||||
{
|
||||
var name = System.Enum.GetName(typeof(Decoration),decoration);
|
||||
AnsiConsole.Write(name + ": ");
|
||||
AnsiConsole.Write(new Markup(name+"\n", new Style(decoration: (Decoration)decoration)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Emojis</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render emojis.</ExampleDescription>
|
||||
<ExampleGroup>Misc</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Exceptions</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render formatted exceptions.</ExampleDescription>
|
||||
<ExampleGroup>Misc</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Figlet</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render FIGlet text.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Grids</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render grids in a console.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Info</ExampleTitle>
|
||||
<ExampleDescription>Displays the capabilities of the current console.</ExampleDescription>
|
||||
<ExampleGroup>Misc</ExampleGroup>
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Json</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to print syntax highlighted JSON.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Shared\Shared.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\Spectre.Console.Json\Spectre.Console.Json.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Shared.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Layout</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to use layouts.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Links</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render links in a console.</ExampleDescription>
|
||||
<ExampleGroup>Misc</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Live</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to do live updates.</ExampleDescription>
|
||||
<ExampleGroup>Live</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>LiveTable</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to do live updates in a table.</ExampleDescription>
|
||||
<ExampleGroup>Live</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<ExampleTitle>Minimal</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates a minimal console application.</ExampleDescription>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Panels</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render items in panels.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Paths</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render paths.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Rendering;
|
||||
|
||||
namespace Progress;
|
||||
|
||||
@@ -22,35 +24,36 @@ public static class Program
|
||||
new RemainingTimeColumn(), // Remaining time
|
||||
new SpinnerColumn(), // Spinner
|
||||
})
|
||||
.UseRenderHook((renderable, tasks) => RenderHook(tasks, renderable))
|
||||
.Start(ctx =>
|
||||
{
|
||||
var random = new Random(DateTime.Now.Millisecond);
|
||||
|
||||
// Create some tasks
|
||||
var tasks = CreateTasks(ctx, random);
|
||||
// Create some tasks
|
||||
var tasks = CreateTasks(ctx, random);
|
||||
var warpTask = ctx.AddTask("Going to warp", autoStart: false).IsIndeterminate();
|
||||
|
||||
// Wait for all tasks (except the indeterminate one) to complete
|
||||
while (!ctx.IsFinished)
|
||||
while (!ctx.IsFinished)
|
||||
{
|
||||
// Increment progress
|
||||
foreach (var (task, increment) in tasks)
|
||||
// Increment progress
|
||||
foreach (var (task, increment) in tasks)
|
||||
{
|
||||
task.Increment(random.NextDouble() * increment);
|
||||
}
|
||||
|
||||
// Write some random things to the terminal
|
||||
if (random.NextDouble() < 0.1)
|
||||
// Write some random things to the terminal
|
||||
if (random.NextDouble() < 0.1)
|
||||
{
|
||||
WriteLogMessage();
|
||||
}
|
||||
|
||||
// Simulate some delay
|
||||
Thread.Sleep(100);
|
||||
// Simulate some delay
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
|
||||
// Now start the "warp" task
|
||||
warpTask.StartTask();
|
||||
warpTask.StartTask();
|
||||
warpTask.IsIndeterminate(false);
|
||||
while (!ctx.IsFinished)
|
||||
{
|
||||
@@ -65,6 +68,35 @@ public static class Program
|
||||
AnsiConsole.MarkupLine("[green]Done![/]");
|
||||
}
|
||||
|
||||
private static IRenderable RenderHook(IReadOnlyList<ProgressTask> tasks, IRenderable renderable)
|
||||
{
|
||||
var header = new Panel("Going on a :rocket:, we're going to the :crescent_moon:").Expand().RoundedBorder();
|
||||
var footer = new Rows(
|
||||
new Rule(),
|
||||
new Markup(
|
||||
$"[blue]{tasks.Count}[/] total tasks. [green]{tasks.Count(i => i.IsFinished)}[/] complete.")
|
||||
);
|
||||
|
||||
const string ESC = "\u001b";
|
||||
string escapeSequence;
|
||||
if (tasks.All(i => i.IsFinished))
|
||||
{
|
||||
escapeSequence = $"{ESC}]]9;4;0;100{ESC}\\";
|
||||
}
|
||||
else
|
||||
{
|
||||
var total = tasks.Sum(i => i.MaxValue);
|
||||
var done = tasks.Sum(i => i.Value);
|
||||
var percent = (int)(done / total * 100);
|
||||
escapeSequence = $"{ESC}]]9;4;1;{percent}{ESC}\\";
|
||||
}
|
||||
|
||||
var middleContent = new Grid().AddColumns(new GridColumn(), new GridColumn().Width(20));
|
||||
middleContent.AddRow(renderable, new FigletText(tasks.Count(i => i.IsFinished == false).ToString()));
|
||||
|
||||
return new Rows(header, middleContent, footer, new ControlCode(escapeSequence));
|
||||
}
|
||||
|
||||
private static List<(ProgressTask Task, int Delay)> CreateTasks(ProgressContext progress, Random random)
|
||||
{
|
||||
var tasks = new List<(ProgressTask, int)>();
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Progress</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to show progress bars.</ExampleDescription>
|
||||
<ExampleGroup>Status</ExampleGroup>
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace Prompt
|
||||
.AddChoices(favorites));
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine("Your selected: [yellow]{0}[/]", fruit);
|
||||
AnsiConsole.MarkupLine("You selected: [yellow]{0}[/]", fruit);
|
||||
return fruit;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>9</LangVersion>
|
||||
<ExampleTitle>Prompt</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to get input from a user.</ExampleDescription>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Rules</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render horizontal rules (lines).</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Showcase</ExampleTitle>
|
||||
<ExampleDescription>Demonstation of Spectre.Console.</ExampleDescription>
|
||||
<ExampleDescription>Demonstration of Spectre.Console.</ExampleDescription>
|
||||
<ExampleGroup>Misc</ExampleGroup>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Status</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to show status updates.</ExampleDescription>
|
||||
<ExampleGroup>Status</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Tables</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render tables in a console.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleTitle>Trees</ExampleTitle>
|
||||
<ExampleDescription>Demonstrates how to render trees in a console.</ExampleDescription>
|
||||
<ExampleGroup>Widgets</ExampleGroup>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ExampleVisible>false</ExampleVisible>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
12
global.json
12
global.json
@@ -1,5 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "7.0.100"
|
||||
}
|
||||
}
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/global",
|
||||
"sdk": {
|
||||
"version": "8.0.100",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Generator.Commands.Samples
|
||||
{
|
||||
public abstract void Run(IAnsiConsole console);
|
||||
public virtual string Name() => PascalToKebab(GetType().Name.Replace("Sample",""));
|
||||
public virtual (int Cols, int Rows) ConsoleSize => (120, 24);
|
||||
public virtual (int Cols, int Rows) ConsoleSize => (82, 24);
|
||||
public virtual IEnumerable<(string Name, Action<Capabilities> CapabilitiesAction)> GetCapabilities()
|
||||
{
|
||||
return new (string Name, Action<Capabilities> CapabilitiesAction)[]
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Spectre.Console;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Generator.Commands.Samples;
|
||||
|
||||
// ReSharper disable once UnusedType.Global
|
||||
public class ColumnsSample : BaseSample
|
||||
{
|
||||
public override void Run(IAnsiConsole console)
|
||||
{
|
||||
var cards = Fruit
|
||||
.LoadFriuts()
|
||||
.Select(GetContent)
|
||||
.ToList();
|
||||
|
||||
// Animate
|
||||
console.Live(new Text(""))
|
||||
.AutoClear(true)
|
||||
.Overflow(VerticalOverflow.Ellipsis)
|
||||
.Cropping(VerticalOverflowCropping.Top)
|
||||
.Start(ctx =>
|
||||
{
|
||||
for (var i = 1; i < cards.Count; i++)
|
||||
{
|
||||
var toShow = cards.Take(i);
|
||||
ctx.UpdateTarget(new Columns(toShow));
|
||||
//ctx.Refresh();
|
||||
Thread.Sleep(200);
|
||||
}
|
||||
});
|
||||
|
||||
// Render all cards in columns
|
||||
AnsiConsole.Write(new Spectre.Console.Columns(cards));
|
||||
}
|
||||
|
||||
private static string GetContent(Fruit fruit)
|
||||
{
|
||||
return $"[b][yellow]{fruit.Name}[/][/]";
|
||||
}
|
||||
|
||||
private sealed class Fruit
|
||||
{
|
||||
public string Name { get; init; }
|
||||
|
||||
public static List<Fruit> LoadFriuts()
|
||||
{
|
||||
return new []
|
||||
{
|
||||
"Apple",
|
||||
"Apricot",
|
||||
"Avocado",
|
||||
"Banana",
|
||||
"Blackberry",
|
||||
"Blueberry",
|
||||
"Boysenberry",
|
||||
"Breadfruit",
|
||||
"Cacao",
|
||||
"Cherry",
|
||||
"Cloudberry",
|
||||
"Coconut",
|
||||
"Dragonfruit",
|
||||
"Elderberry",
|
||||
"Grape",
|
||||
"Grapefruit",
|
||||
"Jackfruit",
|
||||
"Kiwifruit",
|
||||
"Lemon",
|
||||
"Lime",
|
||||
"Mango",
|
||||
"Melon",
|
||||
"Orange",
|
||||
"Blood orange",
|
||||
"Clementine",
|
||||
"Mandarine",
|
||||
"Tangerine",
|
||||
"Papaya",
|
||||
"Passionfruit",
|
||||
"Plum",
|
||||
"Pineapple",
|
||||
"Pomelo",
|
||||
"Raspberry",
|
||||
"Salmonberry",
|
||||
"Strawberry",
|
||||
"Ximenia",
|
||||
"Yuzu",
|
||||
}
|
||||
.Select(x => new Fruit{Name = x})
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<LangVersion>default</LangVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AngleSharp" Version="0.14.0" />
|
||||
<PackageReference Include="Humanizer.Core" Version="2.8.26" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Scriban" Version="2.1.3" />
|
||||
<PackageReference Include="Spectre.IO" Version="0.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Library", "Library", "{CFE7
|
||||
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\Spectre.Console.Json\Spectre.Console.Json.csproj", "{6C96C268-CEEE-478A-A36F-E1450AC33B73}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -71,6 +73,18 @@ Global
|
||||
{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
|
||||
@@ -79,6 +93,7 @@ Global
|
||||
{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}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
|
||||
|
||||
@@ -16,11 +16,15 @@ public class FavorInstanceAnsiConsoleOverStaticAnalyzer : SpectreAnalyzer
|
||||
/// <inheritdoc />
|
||||
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
||||
{
|
||||
var ansiConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||
if (ansiConsoleType == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
compilationStartContext.RegisterOperationAction(
|
||||
context =>
|
||||
{
|
||||
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||
|
||||
// if this operation isn't an invocation against one of the System.Console methods
|
||||
// defined in _methods then we can safely stop analyzing and return;
|
||||
var invocationOperation = (IInvocationOperation)context.Operation;
|
||||
|
||||
@@ -17,6 +17,16 @@ public class NoConcurrentLiveRenderablesAnalyzer : SpectreAnalyzer
|
||||
/// <inheritdoc />
|
||||
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
||||
{
|
||||
var liveTypes = Constants.LiveRenderables
|
||||
.Select(i => compilationStartContext.Compilation.GetTypeByMetadataName(i))
|
||||
.Where(i => i != null)
|
||||
.ToImmutableArray();
|
||||
|
||||
if (liveTypes.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
compilationStartContext.RegisterOperationAction(
|
||||
context =>
|
||||
{
|
||||
@@ -29,27 +39,21 @@ public class NoConcurrentLiveRenderablesAnalyzer : SpectreAnalyzer
|
||||
return;
|
||||
}
|
||||
|
||||
var liveTypes = Constants.LiveRenderables
|
||||
.Select(i => context.Compilation.GetTypeByMetadataName(i))
|
||||
.ToImmutableArray();
|
||||
|
||||
if (liveTypes.All(i => !SymbolEqualityComparer.Default.Equals(i, methodSymbol.ContainingType)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#pragma warning disable RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
|
||||
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
|
||||
#pragma warning restore RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
|
||||
var model = context.Operation.SemanticModel!;
|
||||
var parentInvocations = invocationOperation
|
||||
.Syntax.Ancestors()
|
||||
.OfType<InvocationExpressionSyntax>()
|
||||
.Select(i => model.GetOperation(i))
|
||||
.Select(i => model.GetOperation(i, context.CancellationToken))
|
||||
.OfType<IInvocationOperation>()
|
||||
.ToList();
|
||||
|
||||
if (parentInvocations.All(parent =>
|
||||
parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType)))
|
||||
parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType, SymbolEqualityComparer.Default)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,14 @@ public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
|
||||
/// <inheritdoc />
|
||||
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
||||
{
|
||||
var ansiConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||
var ansiConsoleExtensionsType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions");
|
||||
|
||||
if (ansiConsoleType is null && ansiConsoleExtensionsType is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
compilationStartContext.RegisterOperationAction(
|
||||
context =>
|
||||
{
|
||||
@@ -31,22 +39,17 @@ public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
|
||||
return;
|
||||
}
|
||||
|
||||
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||
var ansiConsoleExtensionsType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions");
|
||||
|
||||
if (!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleType) &&
|
||||
!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleExtensionsType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#pragma warning disable RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
|
||||
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
|
||||
#pragma warning restore RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
|
||||
var model = context.Operation.SemanticModel!;
|
||||
var parentInvocations = invocationOperation
|
||||
.Syntax.Ancestors()
|
||||
.OfType<InvocationExpressionSyntax>()
|
||||
.Select(i => model.GetOperation(i))
|
||||
.Select(i => model.GetOperation(i, context.CancellationToken))
|
||||
.OfType<IInvocationOperation>()
|
||||
.ToList();
|
||||
|
||||
@@ -56,7 +59,7 @@ public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
|
||||
|
||||
if (parentInvocations.All(parent =>
|
||||
parent.TargetMethod.Name != "Start" ||
|
||||
!liveTypes.Contains(parent.TargetMethod.ContainingType)))
|
||||
!liveTypes.Contains(parent.TargetMethod.ContainingType, SymbolEqualityComparer.Default)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,13 @@ public class UseSpectreInsteadOfSystemConsoleAnalyzer : SpectreAnalyzer
|
||||
/// <inheritdoc />
|
||||
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
||||
{
|
||||
var systemConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("System.Console");
|
||||
var spectreConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||
if (systemConsoleType == null || spectreConsoleType == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
compilationStartContext.RegisterOperationAction(
|
||||
context =>
|
||||
{
|
||||
@@ -31,8 +38,6 @@ public class UseSpectreInsteadOfSystemConsoleAnalyzer : SpectreAnalyzer
|
||||
return;
|
||||
}
|
||||
|
||||
var systemConsoleType = context.Compilation.GetTypeByMetadataName("System.Console");
|
||||
|
||||
if (!SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.ContainingType, systemConsoleType))
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
|
||||
using Microsoft.CodeAnalysis.Editing;
|
||||
using Microsoft.CodeAnalysis.Simplification;
|
||||
|
||||
namespace Spectre.Console.Analyzer.CodeActions;
|
||||
|
||||
@@ -31,83 +32,171 @@ public class SwitchToAnsiConsoleAction : CodeAction
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var originalCaller = ((MemberAccessExpressionSyntax)_originalInvocation.Expression).Name.ToString();
|
||||
|
||||
var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (syntaxTree == null)
|
||||
{
|
||||
return _document;
|
||||
}
|
||||
|
||||
var root = (CompilationUnitSyntax)await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// If there is an ansiConsole passed into the method then we'll use it.
|
||||
// otherwise we'll check for a field level instance.
|
||||
// if neither of those exist we'll fall back to the static param.
|
||||
var ansiConsoleParameterDeclaration = GetAnsiConsoleParameterDeclaration();
|
||||
var ansiConsoleFieldIdentifier = GetAnsiConsoleFieldDeclaration();
|
||||
var ansiConsoleIdentifier = ansiConsoleParameterDeclaration ??
|
||||
ansiConsoleFieldIdentifier ??
|
||||
Constants.StaticInstance;
|
||||
|
||||
// Replace the System.Console call with a call to the identifier above.
|
||||
var newRoot = root.ReplaceNode(
|
||||
_originalInvocation,
|
||||
GetImportedSpectreCall(originalCaller, ansiConsoleIdentifier));
|
||||
|
||||
// If we are calling the static instance and Spectre isn't imported yet we should do so.
|
||||
if (ansiConsoleIdentifier == Constants.StaticInstance && root.Usings.ToList().All(i => i.Name.ToString() != Constants.SpectreConsole))
|
||||
{
|
||||
newRoot = newRoot.AddUsings(Syntax.SpectreUsing);
|
||||
}
|
||||
|
||||
return _document.WithSyntaxRoot(newRoot);
|
||||
}
|
||||
|
||||
private string? GetAnsiConsoleParameterDeclaration()
|
||||
{
|
||||
return _originalInvocation
|
||||
.Ancestors().OfType<MethodDeclarationSyntax>()
|
||||
.First()
|
||||
.ParameterList.Parameters
|
||||
.FirstOrDefault(i => i.Type?.NormalizeWhitespace()?.ToString() == "IAnsiConsole")
|
||||
?.Identifier.Text;
|
||||
}
|
||||
|
||||
private string? GetAnsiConsoleFieldDeclaration()
|
||||
{
|
||||
// let's look to see if our call is in a static method.
|
||||
// if so we'll only want to look for static IAnsiConsoles
|
||||
// and vice-versa if we aren't.
|
||||
var isStatic = _originalInvocation
|
||||
.Ancestors()
|
||||
.OfType<MethodDeclarationSyntax>()
|
||||
.First()
|
||||
.Modifiers.Any(i => i.IsKind(SyntaxKind.StaticKeyword));
|
||||
|
||||
return _originalInvocation
|
||||
.Ancestors().OfType<ClassDeclarationSyntax>()
|
||||
.First()
|
||||
.Members
|
||||
.OfType<FieldDeclarationSyntax>()
|
||||
.FirstOrDefault(i =>
|
||||
i.Declaration.Type.NormalizeWhitespace().ToString() == "IAnsiConsole" &&
|
||||
(!isStatic ^ i.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.StaticKeyword))))
|
||||
?.Declaration.Variables.First().Identifier.Text;
|
||||
}
|
||||
|
||||
private ExpressionSyntax GetImportedSpectreCall(string originalCaller, string ansiConsoleIdentifier)
|
||||
{
|
||||
return ExpressionStatement(
|
||||
InvocationExpression(
|
||||
MemberAccessExpression(
|
||||
SyntaxKind.SimpleMemberAccessExpression,
|
||||
IdentifierName(ansiConsoleIdentifier),
|
||||
IdentifierName(originalCaller)))
|
||||
.WithArgumentList(_originalInvocation.ArgumentList)
|
||||
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia())
|
||||
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia()))
|
||||
.Expression;
|
||||
{
|
||||
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
|
||||
var compilation = editor.SemanticModel.Compilation;
|
||||
|
||||
var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation;
|
||||
if (operation == null)
|
||||
{
|
||||
return _document;
|
||||
}
|
||||
|
||||
// If there is an IAnsiConsole passed into the method then we'll use it.
|
||||
// otherwise we'll check for a field level instance.
|
||||
// if neither of those exist we'll fall back to the static param.
|
||||
var spectreConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||
var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole");
|
||||
|
||||
ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol;
|
||||
if (iansiConsoleSymbol != null)
|
||||
{
|
||||
var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition);
|
||||
|
||||
foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start))
|
||||
{
|
||||
// LookupSymbols check the accessibility of the symbol, but it can
|
||||
// suggest instance members when the current context is static.
|
||||
var symbolType = symbol switch
|
||||
{
|
||||
IParameterSymbol parameter => parameter.Type,
|
||||
IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type,
|
||||
IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type,
|
||||
ILocalSymbol local => local.Type,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
// Locals can be returned even if there are not valid in the current context. For instance,
|
||||
// it can return locals declared after the current location. Or it can return locals that
|
||||
// should not be accessible in a static local function.
|
||||
//
|
||||
// void Sample()
|
||||
// {
|
||||
// int local = 0;
|
||||
// static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it
|
||||
// }
|
||||
//
|
||||
// Parameters from the ancestor methods or local functions are also returned even if the operation is in a static local function.
|
||||
if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter)
|
||||
{
|
||||
var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start;
|
||||
|
||||
// The local is not part of the source tree
|
||||
if (localPosition == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// The local is declared after the current expression
|
||||
if (localPosition > _originalInvocation.Span.Start)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// The local is declared outside the static local function
|
||||
if (isInStaticContext && localPosition < parentStaticMemberStartPosition)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol))
|
||||
{
|
||||
accessibleConsoleSymbol = symbol;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (accessibleConsoleSymbol == null)
|
||||
{
|
||||
return _document;
|
||||
}
|
||||
|
||||
// Replace the original invocation
|
||||
var generator = editor.Generator;
|
||||
var consoleExpression = accessibleConsoleSymbol switch
|
||||
{
|
||||
ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
|
||||
_ => generator.IdentifierName(accessibleConsoleSymbol.Name),
|
||||
};
|
||||
|
||||
var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments)
|
||||
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia())
|
||||
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia());
|
||||
|
||||
editor.ReplaceNode(_originalInvocation, newExpression);
|
||||
|
||||
return editor.GetChangedDocument();
|
||||
}
|
||||
|
||||
private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol)
|
||||
{
|
||||
if (symbol == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var iface in symbol.AllInterfaces)
|
||||
{
|
||||
if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition)
|
||||
{
|
||||
// Local functions can be nested, and an instance local function can be declared
|
||||
// in a static local function. So, you need to continue to check ancestors when a
|
||||
// local function is not static.
|
||||
foreach (var member in operation.Syntax.Ancestors())
|
||||
{
|
||||
if (member is LocalFunctionStatementSyntax localFunction)
|
||||
{
|
||||
var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken);
|
||||
if (symbol != null && symbol.IsStatic)
|
||||
{
|
||||
parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (member is LambdaExpressionSyntax lambdaExpression)
|
||||
{
|
||||
var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol;
|
||||
if (symbol != null && symbol.IsStatic)
|
||||
{
|
||||
parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (member is AnonymousMethodExpressionSyntax anonymousMethod)
|
||||
{
|
||||
var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol;
|
||||
if (symbol != null && symbol.IsStatic)
|
||||
{
|
||||
parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (member is MethodDeclarationSyntax methodDeclaration)
|
||||
{
|
||||
parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start;
|
||||
|
||||
var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken);
|
||||
return symbol != null && symbol.IsStatic;
|
||||
}
|
||||
}
|
||||
|
||||
parentStaticMemberStartPosition = -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class StaticAnsiConsoleToInstanceFix : CodeFixProvider
|
||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
|
||||
if (root != null)
|
||||
{
|
||||
var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>();
|
||||
var methodDeclaration = root.FindNode(context.Span, getInnermostNodeForTie: true).FirstAncestorOrSelf<InvocationExpressionSyntax>();
|
||||
if (methodDeclaration != null)
|
||||
{
|
||||
context.RegisterCodeFix(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Spectre.Console.Cli.Internal.Configuration;
|
||||
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
/// <summary>
|
||||
@@ -39,10 +41,11 @@ public sealed class CommandApp : ICommandApp
|
||||
/// Sets the default command.
|
||||
/// </summary>
|
||||
/// <typeparam name="TCommand">The command type.</typeparam>
|
||||
public void SetDefaultCommand<TCommand>()
|
||||
/// <returns>A <see cref="DefaultCommandConfigurator"/> that can be used to configure the default command.</returns>
|
||||
public DefaultCommandConfigurator SetDefaultCommand<TCommand>()
|
||||
where TCommand : class, ICommand
|
||||
{
|
||||
GetConfigurator().SetDefaultCommand<TCommand>();
|
||||
return new DefaultCommandConfigurator(GetConfigurator().SetDefaultCommand<TCommand>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Spectre.Console.Cli.Internal.Configuration;
|
||||
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
/// <summary>
|
||||
@@ -8,6 +10,7 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
|
||||
where TDefaultCommand : class, ICommand
|
||||
{
|
||||
private readonly CommandApp _app;
|
||||
private readonly DefaultCommandConfigurator _defaultCommandConfigurator;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandApp{TDefaultCommand}"/> class.
|
||||
@@ -16,7 +19,7 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
|
||||
public CommandApp(ITypeRegistrar? registrar = null)
|
||||
{
|
||||
_app = new CommandApp(registrar);
|
||||
_app.GetConfigurator().SetDefaultCommand<TDefaultCommand>();
|
||||
_defaultCommandConfigurator = _app.SetDefaultCommand<TDefaultCommand>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -46,5 +49,32 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
|
||||
public Task<int> RunAsync(IEnumerable<string> args)
|
||||
{
|
||||
return _app.RunAsync(args);
|
||||
}
|
||||
|
||||
internal Configurator GetConfigurator()
|
||||
{
|
||||
return _app.GetConfigurator();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the description of the default command.
|
||||
/// </summary>
|
||||
/// <param name="description">The default command description.</param>
|
||||
/// <returns>The same <see cref="CommandApp{TDefaultCommand}"/> instance so that multiple calls can be chained.</returns>
|
||||
public CommandApp<TDefaultCommand> WithDescription(string description)
|
||||
{
|
||||
_defaultCommandConfigurator.WithDescription(description);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets data that will be passed to the command via the <see cref="CommandContext"/>.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to pass to the default command.</param>
|
||||
/// <returns>The same <see cref="CommandApp{TDefaultCommand}"/> instance so that multiple calls can be chained.</returns>
|
||||
public CommandApp<TDefaultCommand> WithData(object data)
|
||||
{
|
||||
_defaultCommandConfigurator.WithData(data);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ public abstract class Command<TSettings> : ICommand<TSettings>
|
||||
/// <param name="context">The command context.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns>The validation result.</returns>
|
||||
public virtual ValidationResult Validate([NotNull] CommandContext context, [NotNull] TSettings settings)
|
||||
public virtual ValidationResult Validate(CommandContext context, TSettings settings)
|
||||
{
|
||||
return ValidationResult.Success();
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public abstract class Command<TSettings> : ICommand<TSettings>
|
||||
/// <param name="context">The command context.</param>
|
||||
/// <param name="settings">The settings.</param>
|
||||
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
|
||||
public abstract int Execute([NotNull] CommandContext context, [NotNull] TSettings settings);
|
||||
public abstract int Execute(CommandContext context, TSettings settings);
|
||||
|
||||
/// <inheritdoc/>
|
||||
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
|
||||
|
||||
@@ -42,6 +42,13 @@ public class CommandRuntimeException : CommandAppException
|
||||
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
|
||||
}
|
||||
|
||||
internal static CommandRuntimeException ConversionFailed(MappedCommandParameter parameter, TypeConverter typeConverter, Exception exception)
|
||||
{
|
||||
var standardValues = typeConverter.GetStandardValuesSupported() ? typeConverter.GetStandardValues() : null;
|
||||
var validValues = standardValues == null ? string.Empty : $" Valid values are '{string.Join("', '", standardValues.Cast<object>().Select(Convert.ToString))}'";
|
||||
return new CommandRuntimeException($"Failed to convert '{parameter.Value}' to {parameter.Parameter.ParameterType.Name}.{validValues}", exception);
|
||||
}
|
||||
|
||||
internal static CommandRuntimeException ValidationFailed(ValidationResult result)
|
||||
{
|
||||
return new CommandRuntimeException(result.Message ?? "Unknown validation error.");
|
||||
|
||||
@@ -5,7 +5,65 @@ namespace Spectre.Console.Cli;
|
||||
/// and <see cref="IConfigurator{TSettings}"/>.
|
||||
/// </summary>
|
||||
public static class ConfiguratorExtensions
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="helpProvider">The help provider to use.</param>
|
||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||
public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHelpProvider helpProvider)
|
||||
{
|
||||
if (configurator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
configurator.SetHelpProvider(helpProvider);
|
||||
return configurator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
|
||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||
public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator)
|
||||
where T : IHelpProvider
|
||||
{
|
||||
if (configurator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
configurator.SetHelpProvider<T>();
|
||||
return configurator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the culture for the application.
|
||||
/// </summary>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="culture">The culture.</param>
|
||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||
/// <remarks>
|
||||
/// Text displayed by <see cref="Help.HelpProvider"/> can be localised, but defaults to English.
|
||||
/// Setting the application culture informs the resource manager which culture to use when fetching strings.
|
||||
/// English will be used when a culture has not been specified
|
||||
/// or a string has not been localised for the specified culture.
|
||||
/// </remarks>
|
||||
public static IConfigurator SetApplicationCulture(this IConfigurator configurator, CultureInfo? culture)
|
||||
{
|
||||
if (configurator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
configurator.Settings.Culture = culture;
|
||||
return configurator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the name of the application.
|
||||
/// </summary>
|
||||
@@ -88,12 +146,12 @@ public static class ConfiguratorExtensions
|
||||
|
||||
configurator.Settings.StrictParsing = true;
|
||||
return configurator;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tells the help writer whether or not to trim trailing period.
|
||||
/// </summary>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="trimTrailingPeriods">True to trim trailing period (default), false to not.</param>
|
||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||
public static IConfigurator TrimTrailingPeriods(this IConfigurator configurator, bool trimTrailingPeriods)
|
||||
@@ -181,7 +239,8 @@ public static class ConfiguratorExtensions
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="name">The name of the command branch.</param>
|
||||
/// <param name="action">The command branch configuration.</param>
|
||||
public static void AddBranch(
|
||||
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||
public static IBranchConfigurator AddBranch(
|
||||
this IConfigurator configurator,
|
||||
string name,
|
||||
Action<IConfigurator<CommandSettings>> action)
|
||||
@@ -191,7 +250,7 @@ public static class ConfiguratorExtensions
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
configurator.AddBranch(name, action);
|
||||
return configurator.AddBranch(name, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -201,7 +260,8 @@ public static class ConfiguratorExtensions
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="name">The name of the command branch.</param>
|
||||
/// <param name="action">The command branch configuration.</param>
|
||||
public static void AddBranch<TSettings>(
|
||||
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||
public static IBranchConfigurator AddBranch<TSettings>(
|
||||
this IConfigurator<TSettings> configurator,
|
||||
string name,
|
||||
Action<IConfigurator<TSettings>> action)
|
||||
@@ -212,7 +272,7 @@ public static class ConfiguratorExtensions
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
configurator.AddBranch(name, action);
|
||||
return configurator.AddBranch(name, action);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -235,6 +295,26 @@ public static class ConfiguratorExtensions
|
||||
return configurator.AddDelegate<EmptyCommandSettings>(name, (c, _) => func(c));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command without settings that executes an async delegate.
|
||||
/// </summary>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="name">The name of the command.</param>
|
||||
/// <param name="func">The delegate to execute as part of command execution.</param>
|
||||
/// <returns>A command configurator that can be used to configure the command further.</returns>
|
||||
public static ICommandConfigurator AddAsyncDelegate(
|
||||
this IConfigurator configurator,
|
||||
string name,
|
||||
Func<CommandContext, Task<int>> func)
|
||||
{
|
||||
if (configurator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
return configurator.AddAsyncDelegate<EmptyCommandSettings>(name, (c, _) => func(c));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command without settings that executes a delegate.
|
||||
/// </summary>
|
||||
@@ -257,6 +337,28 @@ public static class ConfiguratorExtensions
|
||||
return configurator.AddDelegate<TSettings>(name, (c, _) => func(c));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a command without settings that executes an async delegate.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSettings">The command setting type.</typeparam>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="name">The name of the command.</param>
|
||||
/// <param name="func">The delegate to execute as part of command execution.</param>
|
||||
/// <returns>A command configurator that can be used to configure the command further.</returns>
|
||||
public static ICommandConfigurator AddAsyncDelegate<TSettings>(
|
||||
this IConfigurator<TSettings> configurator,
|
||||
string name,
|
||||
Func<CommandContext, Task<int>> func)
|
||||
where TSettings : CommandSettings
|
||||
{
|
||||
if (configurator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
return configurator.AddAsyncDelegate<TSettings>(name, (c, _) => func(c));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the ExceptionsHandler.
|
||||
/// <para>Setting <see cref="ICommandAppSettings.ExceptionHandler"/> this way will use the
|
||||
|
||||
545
src/Spectre.Console.Cli/Help/HelpProvider.cs
Normal file
545
src/Spectre.Console.Cli/Help/HelpProvider.cs
Normal file
@@ -0,0 +1,545 @@
|
||||
using Spectre.Console.Cli.Resources;
|
||||
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// The help provider for Spectre.Console.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Other IHelpProvider implementations can be injected into the CommandApp, if desired.
|
||||
/// </remarks>
|
||||
public class HelpProvider : IHelpProvider
|
||||
{
|
||||
private HelpProviderResources resources;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating how many examples from direct children to show in the help text.
|
||||
/// </summary>
|
||||
protected virtual int MaximumIndirectExamples { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether any default values for command options are shown in the help text.
|
||||
/// </summary>
|
||||
protected virtual bool ShowOptionDefaultValues { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a trailing period of a command description is trimmed in the help text.
|
||||
/// </summary>
|
||||
protected virtual bool TrimTrailingPeriod { get; }
|
||||
|
||||
private sealed class HelpArgument
|
||||
{
|
||||
public string Name { get; }
|
||||
public int Position { get; set; }
|
||||
public bool Required { get; }
|
||||
public string? Description { get; }
|
||||
|
||||
public HelpArgument(string name, int position, bool required, string? description)
|
||||
{
|
||||
Name = name;
|
||||
Position = position;
|
||||
Required = required;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<HelpArgument> Get(ICommandInfo? command)
|
||||
{
|
||||
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>());
|
||||
return arguments;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class HelpOption
|
||||
{
|
||||
public string? Short { get; }
|
||||
public string? Long { get; }
|
||||
public string? Value { get; }
|
||||
public bool? ValueIsOptional { get; }
|
||||
public string? Description { get; }
|
||||
public object? DefaultValue { get; }
|
||||
|
||||
public HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue)
|
||||
{
|
||||
Short = @short;
|
||||
Long = @long;
|
||||
Value = value;
|
||||
ValueIsOptional = valueIsOptional;
|
||||
Description = description;
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<HelpOption> Get(ICommandInfo? command, HelpProviderResources resources)
|
||||
{
|
||||
var parameters = new List<HelpOption>();
|
||||
parameters.Add(new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null));
|
||||
|
||||
// Version information applies to the entire application
|
||||
// Include the "-v" option in the help when at the root of the command line application
|
||||
// Don't allow the "-v" option if users have specified one or more sub-commands
|
||||
if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false))
|
||||
{
|
||||
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
|
||||
}
|
||||
|
||||
parameters.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>());
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HelpProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="settings">The command line application settings used for configuration.</param>
|
||||
public HelpProvider(ICommandAppSettings settings)
|
||||
{
|
||||
this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues;
|
||||
this.MaximumIndirectExamples = settings.MaximumIndirectExamples;
|
||||
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;
|
||||
|
||||
resources = new HelpProviderResources(settings.Culture);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
var result = new List<IRenderable>();
|
||||
|
||||
result.AddRange(GetHeader(model, command));
|
||||
result.AddRange(GetDescription(model, command));
|
||||
result.AddRange(GetUsage(model, command));
|
||||
result.AddRange(GetExamples(model, command));
|
||||
result.AddRange(GetArguments(model, command));
|
||||
result.AddRange(GetOptions(model, command));
|
||||
result.AddRange(GetCommands(model, command));
|
||||
result.AddRange(GetFooter(model, command));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the header for the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetDescription(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
if (command?.Description == null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var composer = new Composer();
|
||||
composer.Style("yellow", $"{resources.Description}:").LineBreak();
|
||||
composer.Text(command.Description).LineBreak();
|
||||
yield return composer.LineBreak();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the usage section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
var composer = new Composer();
|
||||
composer.Style("yellow", $"{resources.Usage}:").LineBreak();
|
||||
composer.Tab().Text(model.ApplicationName);
|
||||
|
||||
var parameters = new List<string>();
|
||||
|
||||
if (command == null)
|
||||
{
|
||||
parameters.Add($"[grey][[{resources.Options}]][/]");
|
||||
parameters.Add($"[aqua]<{resources.Command}>[/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var current in command.Flatten())
|
||||
{
|
||||
var isCurrent = current == command;
|
||||
|
||||
if (!current.IsDefaultCommand)
|
||||
{
|
||||
if (isCurrent)
|
||||
{
|
||||
parameters.Add($"[underline]{current.Name.EscapeMarkup()}[/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.Add($"{current.Name.EscapeMarkup()}");
|
||||
}
|
||||
}
|
||||
|
||||
if (current.Parameters.OfType<ICommandArgument>().Any())
|
||||
{
|
||||
if (isCurrent)
|
||||
{
|
||||
foreach (var argument in current.Parameters.OfType<ICommandArgument>()
|
||||
.Where(a => a.Required).OrderBy(a => a.Position).ToArray())
|
||||
{
|
||||
parameters.Add($"[aqua]<{argument.Value.EscapeMarkup()}>[/]");
|
||||
}
|
||||
}
|
||||
|
||||
var optionalArguments = current.Parameters.OfType<ICommandArgument>().Where(x => !x.Required).ToArray();
|
||||
if (optionalArguments.Length > 0 || !isCurrent)
|
||||
{
|
||||
foreach (var optionalArgument in optionalArguments)
|
||||
{
|
||||
parameters.Add($"[silver][[{optionalArgument.Value.EscapeMarkup()}]][/]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCurrent)
|
||||
{
|
||||
parameters.Add($"[grey][[{resources.Options}]][/]");
|
||||
}
|
||||
}
|
||||
|
||||
if (command.IsBranch && command.DefaultCommand == null)
|
||||
{
|
||||
// The user must specify the command
|
||||
parameters.Add($"[aqua]<{resources.Command}>[/]");
|
||||
}
|
||||
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
|
||||
{
|
||||
// We are on a branch with a default command
|
||||
// The user can optionally specify the command
|
||||
parameters.Add($"[aqua][[{resources.Command}]][/]");
|
||||
}
|
||||
else if (command.IsDefaultCommand)
|
||||
{
|
||||
var commands = model.Commands.Where(x => !x.IsHidden && !x.IsDefaultCommand).ToList();
|
||||
|
||||
if (commands.Count > 0)
|
||||
{
|
||||
// Commands other than the default are present
|
||||
// So make these optional in the usage statement
|
||||
parameters.Add($"[aqua][[{resources.Command}]][/]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
composer.Join(" ", parameters);
|
||||
composer.LineBreak();
|
||||
|
||||
return new[]
|
||||
{
|
||||
composer,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the examples section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
/// <remarks>
|
||||
/// Examples from the command's direct children are used
|
||||
/// if no examples have been set on the specified command or model.
|
||||
/// </remarks>
|
||||
public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
var maxExamples = int.MaxValue;
|
||||
|
||||
var examples = command?.Examples?.ToList() ?? model.Examples?.ToList() ?? new List<string[]>();
|
||||
if (examples.Count == 0)
|
||||
{
|
||||
// Since we're not checking direct examples,
|
||||
// make sure that we limit the number of examples.
|
||||
maxExamples = MaximumIndirectExamples;
|
||||
|
||||
// Start at the current command (if exists)
|
||||
// or alternatively commence at the model.
|
||||
var commandContainer = command ?? (ICommandContainer)model;
|
||||
var queue = new Queue<ICommandContainer>(new[] { commandContainer });
|
||||
|
||||
// Traverse the command tree and look for examples.
|
||||
// As soon as a node contains commands, bail.
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
var current = queue.Dequeue();
|
||||
|
||||
foreach (var child in current.Commands.Where(x => !x.IsHidden))
|
||||
{
|
||||
if (child.Examples.Count > 0)
|
||||
{
|
||||
examples.AddRange(child.Examples);
|
||||
}
|
||||
|
||||
queue.Enqueue(child);
|
||||
}
|
||||
|
||||
if (examples.Count >= maxExamples)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.Min(maxExamples, examples.Count) > 0)
|
||||
{
|
||||
var composer = new Composer();
|
||||
composer.LineBreak();
|
||||
composer.Style("yellow", $"{resources.Examples}:").LineBreak();
|
||||
|
||||
for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++)
|
||||
{
|
||||
var args = string.Join(" ", examples[index]);
|
||||
composer.Tab().Text(model.ApplicationName).Space().Style("grey", args);
|
||||
composer.LineBreak();
|
||||
}
|
||||
|
||||
return new[] { composer };
|
||||
}
|
||||
|
||||
return Array.Empty<IRenderable>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the arguments section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
var arguments = HelpArgument.Get(command);
|
||||
if (arguments.Count == 0)
|
||||
{
|
||||
return Array.Empty<IRenderable>();
|
||||
}
|
||||
|
||||
var result = new List<IRenderable>
|
||||
{
|
||||
new Markup(Environment.NewLine),
|
||||
new Markup($"[yellow]{resources.Arguments}:[/]"),
|
||||
new Markup(Environment.NewLine),
|
||||
};
|
||||
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
|
||||
|
||||
foreach (var argument in arguments.Where(x => x.Required).OrderBy(x => x.Position))
|
||||
{
|
||||
grid.AddRow(
|
||||
$"[silver]<{argument.Name.EscapeMarkup()}>[/]",
|
||||
argument.Description?.TrimEnd('.') ?? " ");
|
||||
}
|
||||
|
||||
foreach (var argument in arguments.Where(x => !x.Required).OrderBy(x => x.Position))
|
||||
{
|
||||
grid.AddRow(
|
||||
$"[grey][[{argument.Name.EscapeMarkup()}]][/]",
|
||||
argument.Description?.TrimEnd('.') ?? " ");
|
||||
}
|
||||
|
||||
result.Add(grid);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the options section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
// Collect all options into a single structure.
|
||||
var parameters = HelpOption.Get(command, resources);
|
||||
if (parameters.Count == 0)
|
||||
{
|
||||
return Array.Empty<IRenderable>();
|
||||
}
|
||||
|
||||
var result = new List<IRenderable>
|
||||
{
|
||||
new Markup(Environment.NewLine),
|
||||
new Markup($"[yellow]{resources.Options}:[/]"),
|
||||
new Markup(Environment.NewLine),
|
||||
};
|
||||
|
||||
var helpOptions = parameters.ToArray();
|
||||
var defaultValueColumn = ShowOptionDefaultValues && helpOptions.Any(e => e.DefaultValue != null);
|
||||
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
|
||||
if (defaultValueColumn)
|
||||
{
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0, 4, 0) });
|
||||
}
|
||||
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
|
||||
|
||||
static string GetOptionParts(HelpOption option)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
if (option.Short != null)
|
||||
{
|
||||
builder.Append('-').Append(option.Short.EscapeMarkup());
|
||||
if (option.Long != null)
|
||||
{
|
||||
builder.Append(", ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append(" ");
|
||||
if (option.Long != null)
|
||||
{
|
||||
builder.Append(" ");
|
||||
}
|
||||
}
|
||||
|
||||
if (option.Long != null)
|
||||
{
|
||||
builder.Append("--").Append(option.Long.EscapeMarkup());
|
||||
}
|
||||
|
||||
if (option.Value != null)
|
||||
{
|
||||
builder.Append(' ');
|
||||
if (option.ValueIsOptional ?? false)
|
||||
{
|
||||
builder.Append("[grey][[").Append(option.Value.EscapeMarkup()).Append("]][/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Append("[silver]<").Append(option.Value.EscapeMarkup()).Append(">[/]");
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
if (defaultValueColumn)
|
||||
{
|
||||
grid.AddRow(" ", $"[lime]{resources.Default}[/]", " ");
|
||||
}
|
||||
|
||||
foreach (var option in helpOptions)
|
||||
{
|
||||
var columns = new List<string> { GetOptionParts(option) };
|
||||
if (defaultValueColumn)
|
||||
{
|
||||
static string Bold(object obj) => $"[bold]{obj.ToString().EscapeMarkup()}[/]";
|
||||
|
||||
var defaultValue = option.DefaultValue switch
|
||||
{
|
||||
null => " ",
|
||||
"" => " ",
|
||||
Array { Length: 0 } => " ",
|
||||
Array array => string.Join(", ", array.Cast<object>().Select(Bold)),
|
||||
_ => Bold(option.DefaultValue),
|
||||
};
|
||||
columns.Add(defaultValue);
|
||||
}
|
||||
|
||||
columns.Add(option.Description?.TrimEnd('.') ?? " ");
|
||||
|
||||
grid.AddRow(columns.ToArray());
|
||||
}
|
||||
|
||||
result.Add(grid);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the commands section of the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetCommands(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
var commandContainer = command ?? (ICommandContainer)model;
|
||||
bool isDefaultCommand = command?.IsDefaultCommand ?? false;
|
||||
|
||||
var commands = isDefaultCommand ? model.Commands : commandContainer.Commands;
|
||||
commands = commands.Where(x => !x.IsHidden).ToList();
|
||||
|
||||
if (commands.Count == 0)
|
||||
{
|
||||
return Array.Empty<IRenderable>();
|
||||
}
|
||||
|
||||
var result = new List<IRenderable>
|
||||
{
|
||||
new Markup(Environment.NewLine),
|
||||
new Markup($"[yellow]{resources.Commands}:[/]"),
|
||||
new Markup(Environment.NewLine),
|
||||
};
|
||||
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(0, 0) });
|
||||
|
||||
foreach (var child in commands)
|
||||
{
|
||||
var arguments = new Composer();
|
||||
arguments.Style("silver", child.Name.EscapeMarkup());
|
||||
arguments.Space();
|
||||
|
||||
foreach (var argument in HelpArgument.Get(child).Where(a => a.Required))
|
||||
{
|
||||
arguments.Style("silver", $"<{argument.Name.EscapeMarkup()}>");
|
||||
arguments.Space();
|
||||
}
|
||||
|
||||
if (TrimTrailingPeriod)
|
||||
{
|
||||
grid.AddRow(
|
||||
arguments.ToString().TrimEnd(),
|
||||
child.Description?.TrimEnd('.') ?? " ");
|
||||
}
|
||||
else
|
||||
{
|
||||
grid.AddRow(
|
||||
arguments.ToString().TrimEnd(),
|
||||
child.Description ?? " ");
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(grid);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the footer for the help information.
|
||||
/// </summary>
|
||||
/// <param name="model">The command model to write help for.</param>
|
||||
/// <param name="command">The command for which to write help information (optional).</param>
|
||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||
public virtual IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo? command)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
131
src/Spectre.Console.Cli/Help/HelpProviderResources.cs
Normal file
131
src/Spectre.Console.Cli/Help/HelpProviderResources.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System.Resources;
|
||||
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
internal class HelpProviderResources
|
||||
{
|
||||
private readonly ResourceManager resourceManager = new ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
|
||||
private readonly CultureInfo? resourceCulture = null;
|
||||
|
||||
public HelpProviderResources()
|
||||
{
|
||||
}
|
||||
|
||||
public HelpProviderResources(CultureInfo? culture)
|
||||
{
|
||||
resourceCulture = culture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for ARGUMENTS.
|
||||
/// </summary>
|
||||
internal string Arguments
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("Arguments", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for COMMAND.
|
||||
/// </summary>
|
||||
internal string Command
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("Command", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for COMMANDS.
|
||||
/// </summary>
|
||||
internal string Commands
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("Commands", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for DEFAULT.
|
||||
/// </summary>
|
||||
internal string Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("Default", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for DESCRIPTION.
|
||||
/// </summary>
|
||||
internal string Description
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("Description", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for EXAMPLES.
|
||||
/// </summary>
|
||||
internal string Examples
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("Examples", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for OPTIONS.
|
||||
/// </summary>
|
||||
internal string Options
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("Options", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for Prints help information.
|
||||
/// </summary>
|
||||
internal string PrintHelpDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("PrintHelpDescription", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for Prints version information.
|
||||
/// </summary>
|
||||
internal string PrintVersionDescription
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("PrintVersionDescription", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the localised string for USAGE.
|
||||
/// </summary>
|
||||
internal string Usage
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceManager.GetString("Usage", resourceCulture) ?? string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
src/Spectre.Console.Cli/Help/ICommandArgument.cs
Normal file
17
src/Spectre.Console.Cli/Help/ICommandArgument.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command argument.
|
||||
/// </summary>
|
||||
public interface ICommandArgument : ICommandParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the value of the argument.
|
||||
/// </summary>
|
||||
string Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the position of the argument.
|
||||
/// </summary>
|
||||
int Position { get; }
|
||||
}
|
||||
25
src/Spectre.Console.Cli/Help/ICommandContainer.cs
Normal file
25
src/Spectre.Console.Cli/Help/ICommandContainer.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command container.
|
||||
/// </summary>
|
||||
public interface ICommandContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the examples for the container.
|
||||
/// </summary>
|
||||
IReadOnlyList<string[]> Examples { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all commands in the container.
|
||||
/// </summary>
|
||||
IReadOnlyList<ICommandInfo> Commands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default command for the container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns null if a default command has not been set.
|
||||
/// </remarks>
|
||||
ICommandInfo? DefaultCommand { get; }
|
||||
}
|
||||
42
src/Spectre.Console.Cli/Help/ICommandInfo.cs
Normal file
42
src/Spectre.Console.Cli/Help/ICommandInfo.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an executable command.
|
||||
/// </summary>
|
||||
public interface ICommandInfo : ICommandContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the command.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the command.
|
||||
/// </summary>
|
||||
string? Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the command is a branch.
|
||||
/// </summary>
|
||||
bool IsBranch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the command is the default command within its container.
|
||||
/// </summary>
|
||||
bool IsDefaultCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the command is hidden.
|
||||
/// </summary>
|
||||
bool IsHidden { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parameters associated with the command.
|
||||
/// </summary>
|
||||
IReadOnlyList<ICommandParameter> Parameters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent command, if any.
|
||||
/// </summary>
|
||||
ICommandInfo? Parent { get; }
|
||||
}
|
||||
23
src/Spectre.Console.Cli/Help/ICommandInfoExtensions.cs
Normal file
23
src/Spectre.Console.Cli/Help/ICommandInfoExtensions.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
internal static class ICommandInfoExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Walks up the command.Parent tree, adding each command into a list as it goes.
|
||||
/// </summary>
|
||||
/// <remarks>The first command added to the list is the current (ie. this one).</remarks>
|
||||
/// <returns>The list of commands from current to root, as traversed by <see cref="CommandInfo.Parent"/>.</returns>
|
||||
public static List<ICommandInfo> Flatten(this ICommandInfo commandInfo)
|
||||
{
|
||||
var result = new Stack<Help.ICommandInfo>();
|
||||
|
||||
var current = commandInfo;
|
||||
while (current != null)
|
||||
{
|
||||
result.Push(current);
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
return result.ToList();
|
||||
}
|
||||
}
|
||||
12
src/Spectre.Console.Cli/Help/ICommandModel.cs
Normal file
12
src/Spectre.Console.Cli/Help/ICommandModel.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command model.
|
||||
/// </summary>
|
||||
public interface ICommandModel : ICommandContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the application.
|
||||
/// </summary>
|
||||
string ApplicationName { get; }
|
||||
}
|
||||
27
src/Spectre.Console.Cli/Help/ICommandOption.cs
Normal file
27
src/Spectre.Console.Cli/Help/ICommandOption.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command option.
|
||||
/// </summary>
|
||||
public interface ICommandOption : ICommandParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the long names of the option.
|
||||
/// </summary>
|
||||
IReadOnlyList<string> LongNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the short names of the option.
|
||||
/// </summary>
|
||||
IReadOnlyList<string> ShortNames { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value name of the option, if applicable.
|
||||
/// </summary>
|
||||
string? ValueName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the option value is optional.
|
||||
/// </summary>
|
||||
bool ValueIsOptional { get; }
|
||||
}
|
||||
32
src/Spectre.Console.Cli/Help/ICommandParameter.cs
Normal file
32
src/Spectre.Console.Cli/Help/ICommandParameter.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a command parameter.
|
||||
/// </summary>
|
||||
public interface ICommandParameter
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the parameter is a flag.
|
||||
/// </summary>
|
||||
bool IsFlag { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the parameter is required.
|
||||
/// </summary>
|
||||
bool Required { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the parameter.
|
||||
/// </summary>
|
||||
string? Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default value of the parameter, if specified.
|
||||
/// </summary>
|
||||
DefaultValueAttribute? DefaultValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the parameter is hidden.
|
||||
/// </summary>
|
||||
bool IsHidden { get; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user