mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
50 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5d4b2c88e5 | ||
|
|
3acc90e47c | ||
|
|
88515b7d7f | ||
|
|
c5e11626b5 | ||
|
|
2ead177404 | ||
|
|
71f762f646 | ||
|
|
95bff47b85 | ||
|
|
de04619f88 | ||
|
|
ecdfdd4b85 | ||
|
|
a893a9601e | ||
|
|
ff7282ecb8 | ||
|
|
eb38f76a6a | ||
|
|
fc0b553a4a | ||
|
|
1a3249cdae | ||
|
|
43f9ae92ad | ||
|
|
e66d3aab2e | ||
|
|
d921ac6f02 | ||
|
|
5acd83a3ef | ||
|
|
397b742bec | ||
|
|
d30b08201d | ||
|
|
8da05bcc17 | ||
|
|
badcd642ec | ||
|
|
fde9ee04cf | ||
|
|
b6e0b2389a | ||
|
|
3f5e8aabf5 | ||
|
|
ead874e6b2 | ||
|
|
e13410861d | ||
|
|
bf3b91a535 | ||
|
|
72704529c5 | ||
|
|
b21e07ea94 | ||
|
|
703d653ec5 | ||
|
|
71631b248a | ||
|
|
63c22575ea | ||
|
|
9cc888e5ad | ||
|
|
d5b4621233 | ||
|
|
cee97821d6 | ||
|
|
55c763a5c2 | ||
|
|
d03c10623c | ||
|
|
5a52c1f277 | ||
|
|
544e6a92df | ||
|
|
a94bc15746 | ||
|
|
e7ce6a69b7 | ||
|
|
7cf7e84dd8 | ||
|
|
6f1f29967d | ||
|
|
006da0f9ea | ||
|
|
c62f79eded | ||
|
|
1d19079913 | ||
|
|
44300c871f | ||
|
|
989c0b9904 | ||
|
|
cb52eb63ce |
@@ -1,2 +1,5 @@
|
||||
# Use file scoped namespace declarations
|
||||
7b2da0a4f63bf3ceab99d2c88535e74155f2b99c
|
||||
7b2da0a4f63bf3ceab99d2c88535e74155f2b99c
|
||||
|
||||
# fix line-endings
|
||||
e2ad4b1ea5555e701cda4fd400bb6592e318e1ff
|
||||
|
||||
4
.gitattributes
vendored
Normal file
4
.gitattributes
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
* text=auto
|
||||
|
||||
*.cs text eol=lf
|
||||
*.md text eol=lf
|
||||
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -26,3 +26,6 @@ If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
||||
---
|
||||
Please upvote :+1: this issue if you are interested in it.
|
||||
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
3
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -18,3 +18,6 @@ A clear and concise description of any alternative solutions or features you've
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
|
||||
---
|
||||
Please upvote :+1: this issue if you are interested in it.
|
||||
5
.github/pull_request_template.md
vendored
5
.github/pull_request_template.md
vendored
@@ -7,7 +7,7 @@ fixes #
|
||||
|
||||
<!-- formalities. These are not optional. -->
|
||||
|
||||
- [ ] I have read the [Contribution Guidelines](../CONTRIBUTING.md)
|
||||
- [ ] I have read the [Contribution Guidelines](https://github.com/spectreconsole/spectre.console/blob/main/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
|
||||
@@ -17,3 +17,6 @@ fixes #
|
||||
## Changes
|
||||
|
||||
<!-- describe the changes you made. -->
|
||||
|
||||
---
|
||||
Please upvote :+1: this pull request if you are interested in it.
|
||||
60
.github/workflows/ci.yaml
vendored
60
.github/workflows/ci.yaml
vendored
@@ -8,48 +8,6 @@ env:
|
||||
|
||||
jobs:
|
||||
|
||||
###################################################
|
||||
# DOCS
|
||||
###################################################
|
||||
|
||||
docs:
|
||||
name: Documentation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
restore-keys: npm-
|
||||
|
||||
- name: Build
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
cd docs
|
||||
dotnet tool restore
|
||||
dotnet run --configuration Release
|
||||
|
||||
- name: Archive doc generation
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: documentation-output
|
||||
path: docs/output/
|
||||
retention-days: 5
|
||||
|
||||
###################################################
|
||||
# BUILD
|
||||
###################################################
|
||||
@@ -57,25 +15,15 @@ jobs:
|
||||
build:
|
||||
name: Build
|
||||
if: "!contains(github.event.head_commit.message, 'skip-ci')"
|
||||
strategy:
|
||||
matrix:
|
||||
kind: ['linux', 'windows', 'macOS']
|
||||
include:
|
||||
- kind: linux
|
||||
os: ubuntu-latest
|
||||
- kind: windows
|
||||
os: windows-latest
|
||||
- kind: macOS
|
||||
os: macos-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
@@ -90,7 +38,7 @@ jobs:
|
||||
|
||||
- name: Upload Verify Test Results
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: verify-test-results
|
||||
path: |
|
||||
|
||||
77
.github/workflows/publish.yaml
vendored
77
.github/workflows/publish.yaml
vendored
@@ -15,40 +15,34 @@ env:
|
||||
jobs:
|
||||
|
||||
###################################################
|
||||
# BUILD
|
||||
# PUBLISH
|
||||
###################################################
|
||||
|
||||
build:
|
||||
name: Build
|
||||
if: |
|
||||
(!startsWith(github.event.head_commit.message, 'skip-ci')
|
||||
&& !startsWith(github.event.head_commit.message, 'chore:'))
|
||||
|| startsWith(github.ref, 'refs/tags/')
|
||||
strategy:
|
||||
matrix:
|
||||
kind: ['linux', 'windows', 'macOS']
|
||||
include:
|
||||
- kind: linux
|
||||
os: ubuntu-latest
|
||||
- kind: windows
|
||||
os: windows-latest
|
||||
- kind: macOS
|
||||
os: macos-latest
|
||||
runs-on: ${{ matrix.os }}
|
||||
name: Publish NuGet Packages
|
||||
if: "!contains(github.event.head_commit.message, 'skip-ci') || startsWith(github.ref, 'refs/tags/')"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: |
|
||||
6.0.x
|
||||
7.0.x
|
||||
8.0.x
|
||||
|
||||
- name: Build
|
||||
- name: Publish
|
||||
shell: bash
|
||||
run: |
|
||||
dotnet tool restore
|
||||
dotnet cake
|
||||
dotnet cake --target="publish" \
|
||||
--nuget-key="${{secrets.NUGET_API_KEY}}" \
|
||||
--github-key="${{secrets.GITHUB_TOKEN}}"
|
||||
|
||||
###################################################
|
||||
# DOCS
|
||||
@@ -60,20 +54,20 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v3
|
||||
uses: actions/setup-dotnet@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '16'
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.npm
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
@@ -88,35 +82,4 @@ jobs:
|
||||
run: |
|
||||
cd docs
|
||||
dotnet tool restore
|
||||
dotnet run --configuration Release -- deploy
|
||||
|
||||
###################################################
|
||||
# PUBLISH
|
||||
###################################################
|
||||
|
||||
publish:
|
||||
name: Publish NuGet Packages
|
||||
needs: [build]
|
||||
if: "!contains(github.event.head_commit.message, 'skip-ci') || startsWith(github.ref, 'refs/tags/')"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- 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
|
||||
run: |
|
||||
dotnet tool restore
|
||||
dotnet cake --target="publish" \
|
||||
--nuget-key="${{secrets.NUGET_API_KEY}}" \
|
||||
--github-key="${{secrets.GITHUB_TOKEN}}"
|
||||
dotnet run --configuration Release -- deploy
|
||||
24
.github/workflows/top-issues-dashboard.yml
vendored
Normal file
24
.github/workflows/top-issues-dashboard.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Top issues action.
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 */1 * *'
|
||||
|
||||
jobs:
|
||||
ShowAndLabelTopIssues:
|
||||
name: Display and label top issues.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Top Issues action
|
||||
uses: rickstaa/top-issues-action@v1.3.99
|
||||
env:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
top_list_size: 10
|
||||
label: true
|
||||
dashboard: true
|
||||
dashboard_show_total_reactions: true
|
||||
top_issues: true
|
||||
top_bugs: true
|
||||
top_features: true
|
||||
feature_label: feature
|
||||
top_pull_requests: true
|
||||
23
README.md
23
README.md
@@ -3,8 +3,7 @@
|
||||
_[](https://www.nuget.org/packages/spectre.console)_ _[](https://www.nuget.org/packages/spectre.console.cli)_ [](https://app.netlify.com/sites/spectreconsole/deploys)
|
||||
|
||||
A .NET library that makes it easier to create beautiful, cross platform, console applications.
|
||||
It is heavily inspired by the excellent [Rich library](https://github.com/willmcgugan/rich)
|
||||
for Python. For detailed usage instructions, [please refer to the documentation at https://spectreconsole.net/.](https://spectreconsole.net/)
|
||||
It is heavily inspired by the excellent Python library, [Rich](https://github.com/willmcgugan/rich). Detailed instructions for using `Spectre.Console` are located on the project website, https://spectreconsole.net
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -19,18 +18,22 @@ for Python. For detailed usage instructions, [please refer to the documentation
|
||||
|
||||
## Features
|
||||
|
||||
* Written with unit testing in mind.
|
||||
* Supports tables, grids, panels, and a [rich](https://github.com/willmcgugan/rich) inspired markup language.
|
||||
* Supports tables, grids, panels, and a [Rich](https://github.com/willmcgugan/rich) inspired markup language.
|
||||
* Supports the most common SRG parameters when it comes to text
|
||||
styling such as bold, dim, italic, underline, strikethrough,
|
||||
and blinking text.
|
||||
* Supports 3/4/8/24-bit colors in the terminal.
|
||||
The library will detect the capabilities of the current terminal
|
||||
and downgrade colors as needed.
|
||||
|
||||
and downgrade colors as needed.
|
||||
* Written with unit testing in mind.
|
||||
|
||||

|
||||
|
||||
## Important Notices
|
||||
|
||||
> [!IMPORTANT]\
|
||||
> We use the [Top Issues Dashboard](https://github.com/spectreconsole/spectre.console/issues/1517) for tracking community demand. Please upvote :+1: the issues and pull requests you are interested in.
|
||||
|
||||
## Installing
|
||||
|
||||
The fastest way of getting started using `Spectre.Console` is to install the NuGet package.
|
||||
@@ -42,7 +45,7 @@ dotnet add package Spectre.Console
|
||||
## Documentation
|
||||
|
||||
The documentation for `Spectre.Console` can be found at
|
||||
https://spectreconsole.net/
|
||||
https://spectreconsole.net
|
||||
|
||||
## Examples
|
||||
|
||||
@@ -69,7 +72,7 @@ And to run an example:
|
||||
## Sponsors
|
||||
|
||||
The following people are [sponsoring](https://github.com/sponsors/patriksvensson)
|
||||
Spectre.Console to show their support and to ensure the longevity of the project.
|
||||
`Spectre.Console` to show their support and to ensure the longevity of the project.
|
||||
|
||||
* [Rodney Littles II](https://github.com/RLittlesII)
|
||||
* [Martin Björkström](https://github.com/bjorkstromm)
|
||||
@@ -99,6 +102,6 @@ This project is supported by the [.NET Foundation](https://dotnetfoundation.org)
|
||||
|
||||
Copyright © Patrik Svensson, Phil Scott, Nils Andresen
|
||||
|
||||
Spectre.Console is provided as-is under the MIT license. For more information see LICENSE.
|
||||
`Spectre.Console` is provided as-is under the MIT license. For more information see LICENSE.
|
||||
|
||||
* SixLabors.ImageSharp, a library which Spectre.Console relies upon, is licensed under Apache 2.0 when distributed as part of Spectre.Console. The Six Labors Split License covers all other usage, see: https://github.com/SixLabors/ImageSharp/blob/master/LICENSE
|
||||
* SixLabors.ImageSharp, a library which `Spectre.Console` relies upon, is licensed under Apache 2.0 when distributed as part of `Spectre.Console`. The Six Labors Split License covers all other usage, see: https://github.com/SixLabors/ImageSharp/blob/master/LICENSE
|
||||
|
||||
154
docs/Program.cs
154
docs/Program.cs
@@ -1,77 +1,77 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Docs.Extensions;
|
||||
using Docs.Shortcodes;
|
||||
using Docs.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Statiq.App;
|
||||
using Statiq.Common;
|
||||
using Statiq.Core;
|
||||
using Statiq.Web;
|
||||
|
||||
namespace Docs
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static async Task<int> Main(string[] args) =>
|
||||
await Bootstrapper.Factory
|
||||
.CreateWeb(args)
|
||||
.AddSetting(Keys.Host, "spectreconsole.net")
|
||||
.AddSetting(Keys.LinksUseHttps, true)
|
||||
.AddSetting(Constants.EditLink, ConfigureEditLink())
|
||||
.AddSetting(Constants.SourceFiles, new List<string>
|
||||
{
|
||||
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||
"../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||
"../../src/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
|
||||
})
|
||||
.AddSetting(Constants.ExampleSourceFiles, new List<string>
|
||||
{
|
||||
"../../examples/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||
}
|
||||
)
|
||||
.ConfigureServices(i =>
|
||||
{
|
||||
i.AddSingleton(new TypeNameLinks());
|
||||
})
|
||||
.ConfigureSite("spectreconsole", "spectre.console", "main")
|
||||
.AddShortcode("Children", typeof(ChildrenShortcode))
|
||||
.AddShortcode("ColorTable", typeof(ColorTableShortcode))
|
||||
.AddShortcode("EmojiTable", typeof(EmojiTableShortcode))
|
||||
.AddShortcode("Alert", typeof(AlertShortcode))
|
||||
.AddShortcode("Info", typeof(InfoShortcode))
|
||||
.AddShortcode("AsciiCast", typeof(AsciiCastShortcode))
|
||||
.AddShortcode("Example", typeof(ExampleSnippet))
|
||||
.AddPipelines()
|
||||
.BuildPipeline(
|
||||
"Bootstrap",
|
||||
builder => builder
|
||||
.WithInputReadFiles("../node_modules/asciinema-player/dist/bundle/asciinema-player.js")
|
||||
.WithProcessModules(new SetDestination(Config.FromDocument(doc => new NormalizedPath($"./assets/{doc.Source.FileName}")), true))
|
||||
.WithOutputWriteFiles()
|
||||
)
|
||||
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("npm", "install --audit false --fund false --progress false")
|
||||
{
|
||||
LogErrors = false
|
||||
})
|
||||
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("dotnet", "playwright install chromium"))
|
||||
.AddProcess(ProcessTiming.BeforeDeployment, _ => new ProcessLauncher("npm", "run build:tailwind")
|
||||
{
|
||||
LogErrors = false
|
||||
})
|
||||
.RunAsync();
|
||||
|
||||
private static Config<string> ConfigureEditLink()
|
||||
{
|
||||
return Config.FromDocument((doc, ctx) =>
|
||||
{
|
||||
return string.Format("https://github.com/{0}/{1}/edit/{2}/docs/input/{3}",
|
||||
ctx.GetString(Constants.Site.Owner),
|
||||
ctx.GetString(Constants.Site.Repository),
|
||||
ctx.GetString(Constants.Site.Branch),
|
||||
doc.Source.GetRelativeInputPath());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Docs.Extensions;
|
||||
using Docs.Shortcodes;
|
||||
using Docs.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Statiq.App;
|
||||
using Statiq.Common;
|
||||
using Statiq.Core;
|
||||
using Statiq.Web;
|
||||
|
||||
namespace Docs
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
public static async Task<int> Main(string[] args) =>
|
||||
await Bootstrapper.Factory
|
||||
.CreateWeb(args)
|
||||
.AddSetting(Keys.Host, "spectreconsole.net")
|
||||
.AddSetting(Keys.LinksUseHttps, true)
|
||||
.AddSetting(Constants.EditLink, ConfigureEditLink())
|
||||
.AddSetting(Constants.SourceFiles, new List<string>
|
||||
{
|
||||
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||
"../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||
"../../src/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
|
||||
})
|
||||
.AddSetting(Constants.ExampleSourceFiles, new List<string>
|
||||
{
|
||||
"../../examples/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||
}
|
||||
)
|
||||
.ConfigureServices(i =>
|
||||
{
|
||||
i.AddSingleton(new TypeNameLinks());
|
||||
})
|
||||
.ConfigureSite("spectreconsole", "spectre.console", "main")
|
||||
.AddShortcode("Children", typeof(ChildrenShortcode))
|
||||
.AddShortcode("ColorTable", typeof(ColorTableShortcode))
|
||||
.AddShortcode("EmojiTable", typeof(EmojiTableShortcode))
|
||||
.AddShortcode("Alert", typeof(AlertShortcode))
|
||||
.AddShortcode("Info", typeof(InfoShortcode))
|
||||
.AddShortcode("AsciiCast", typeof(AsciiCastShortcode))
|
||||
.AddShortcode("Example", typeof(ExampleSnippet))
|
||||
.AddPipelines()
|
||||
.BuildPipeline(
|
||||
"Bootstrap",
|
||||
builder => builder
|
||||
.WithInputReadFiles("../node_modules/asciinema-player/dist/bundle/asciinema-player.js")
|
||||
.WithProcessModules(new SetDestination(Config.FromDocument(doc => new NormalizedPath($"./assets/{doc.Source.FileName}")), true))
|
||||
.WithOutputWriteFiles()
|
||||
)
|
||||
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("npm", "install --audit false --fund false --progress false")
|
||||
{
|
||||
LogErrors = false
|
||||
})
|
||||
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("dotnet", "playwright install chromium"))
|
||||
.AddProcess(ProcessTiming.BeforeDeployment, _ => new ProcessLauncher("npm", "run build:tailwind")
|
||||
{
|
||||
LogErrors = false
|
||||
})
|
||||
.RunAsync();
|
||||
|
||||
private static Config<string> ConfigureEditLink()
|
||||
{
|
||||
return Config.FromDocument((doc, ctx) =>
|
||||
{
|
||||
return string.Format("https://github.com/{0}/{1}/edit/{2}/docs/input/{3}",
|
||||
ctx.GetString(Constants.Site.Owner),
|
||||
ctx.GetString(Constants.Site.Repository),
|
||||
ctx.GetString(Constants.Site.Branch),
|
||||
doc.Source.GetRelativeInputPath());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ Description: "*Spectre.Console* makes it easy to write text with different style
|
||||
Highlights:
|
||||
- Bold, Italic, Underline, strikethrough
|
||||
- Dim, Invert
|
||||
- Conceal, slowblink, rapidblink
|
||||
- Conceal, slowblink, rapidblink
|
||||
- Links
|
||||
---
|
||||
|
||||
@@ -46,9 +46,9 @@ Note that what styles that can be used is defined by the system or your terminal
|
||||
<tr>
|
||||
<td><code>strikethrough</code></td>
|
||||
<td>Shows text with a horizontal line through the center</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>link</link></td>
|
||||
<td>Creates a clickable link within text</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>link</link></td>
|
||||
<td>Creates a clickable link within text</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,63 @@
|
||||
Title: Spectre.Console 0.48 released!
|
||||
Description: .NET 8, custom help providers, and more!
|
||||
Published: 2023-11-22
|
||||
Category: Release Notes
|
||||
Excluded: false
|
||||
---
|
||||
|
||||
Version 0.48 of Spectre.Console has been released!
|
||||
|
||||
Several rendering issues have been addressed, such as fixing problems related to rendering inside status causing corrupt output, avoiding exceptions on Rows with no children, as well as addressing rendering bugs in TextPath.
|
||||
|
||||
New features have been added, such as the ability to show separators between table rows. Other notable additions include progress bar header and footer support, customizable (and localizable) help providers, and the option to style text and confirmation prompts.
|
||||
|
||||
# New Contributors
|
||||
|
||||
* [@icalvo](https://github.com/icalvo) made their first contribution in [#1215](https://github.com/spectreconsole/spectre.console/pull/1215)
|
||||
* [@fredrikbentzen](https://github.com/fredrikbentzen) made their first contribution in [#1132](https://github.com/spectreconsole/spectre.console/pull/1132)
|
||||
* [@jeppevammenkristensen](https://github.com/jeppevammenkristensen) made their first contribution in [#1241](https://github.com/spectreconsole/spectre.console/pull/1241)
|
||||
* [@tomaszprasolek](https://github.com/tomaszprasolek) made their first contribution in [#1257](https://github.com/spectreconsole/spectre.console/pull/1257)
|
||||
* [@olabacker](https://github.com/olabacker) made their first contribution in [#1302](https://github.com/spectreconsole/spectre.console/pull/1302)
|
||||
* [@AndrewRathbun](https://github.com/AndrewRathbun) made their first contribution in [#1315](https://github.com/spectreconsole/spectre.console/pull/1315)
|
||||
|
||||
|
||||
# What's Changed
|
||||
|
||||
## Rendering
|
||||
|
||||
* Add .NET 8 support by [@patriksvensson](https://github.com/patriksvensson) in [#1367](https://github.com/spectreconsole/spectre.console/pull/1367)
|
||||
* Fixed render issue where writeline inside status caused corrupt output #415 #694 by [@fredrikbentzen](https://github.com/fredrikbentzen) in [#1132]([#1132](https://github.com/spectreconsole/spectre.console/pull/1132))
|
||||
* Relax the SDK requirements by rolling forward to the latest feature by [@0xced](https://github.com/0xced) in [#1237]([#1237](https://github.com/spectreconsole/spectre.console/pull/1237))
|
||||
* Add fix to avoid exception on rows with no children by [@jeppevammenkristensen](https://github.com/jeppevammenkristensen) in [#1241]([#1241](https://github.com/spectreconsole/spectre.console/pull/1241))
|
||||
* Set `end_of_line` to `LF` instead of `CRLF` by [@0xced](https://github.com/0xced) in [#1256](https://github.com/spectreconsole/spectre.console/pull/1256)
|
||||
* Fix `Rule` widget docs by [@tomaszprasolek](https://github.com/tomaszprasolek) in [#1257](https://github.com/spectreconsole/spectre.console/pull/1257)
|
||||
* Added the missing columns-cast by [@nils](https://github.com/nils)-a in [#1294](https://github.com/spectreconsole/spectre.console/pull/1294)
|
||||
* Render tables with zero-width columns by [@Frassle](https://github.com/Frassle) in [#1197](https://github.com/spectreconsole/spectre.console/pull/1197)
|
||||
* Fix figlet centering possibly throwing due to negative size by [@olabacker](https://github.com/olabacker) in [#1302](https://github.com/spectreconsole/spectre.console/pull/1302)
|
||||
* Add option to show separator between table rows by [@patriksvensson](https://github.com/patriksvensson) in [#1304](https://github.com/spectreconsole/spectre.console/pull/1304)
|
||||
* Enable setting the color of the values in a `BreakdownChart` by [@nils](https://github.com/nils)-a in [#1303](https://github.com/spectreconsole/spectre.console/pull/1303)
|
||||
* Progress bar header and footer by [@phil](https://github.com/phil)-scott-78 in [#1262](https://github.com/spectreconsole/spectre.console/pull/1262)
|
||||
* Add an example showing the decorations off by [@Frassle](https://github.com/Frassle) in [#1191](https://github.com/spectreconsole/spectre.console/pull/1191)
|
||||
* Fixes `TextPath` rendering bugs by [@patriksvensson](https://github.com/patriksvensson) in [#1308](https://github.com/spectreconsole/spectre.console/pull/1308)
|
||||
* Fix greedy row measure by [@nils](https://github.com/nils)-a in [#1338](https://github.com/spectreconsole/spectre.console/pull/1338)
|
||||
* Fix `AnsiConsoleOutput` safe height by [@0xced](https://github.com/0xced) in [#1358](https://github.com/spectreconsole/spectre.console/pull/1358)
|
||||
* Allow passing a nullable style in `DefaultValueStyle()` and `ChoicesStyle()` by [@0xced](https://github.com/0xced) in [#1359](https://github.com/spectreconsole/spectre.console/pull/1359)
|
||||
* Allow `ConfirmationPrompt` Styling by [@wbaldoumas](https://github.com/wbaldoumas) in [#1210](https://github.com/spectreconsole/spectre.console/pull/1210)
|
||||
|
||||
## CLI
|
||||
* Add async command unit tests by [@FrankRay78](https://github.com/FrankRay78) in [#1228](https://github.com/spectreconsole/spectre.console/pull/1228)
|
||||
* Add support for async delegate by [@icalvo](https://github.com/icalvo) in [#1215]([#1215](https://github.com/spectreconsole/spectre.console/pull/1215))
|
||||
* Remove unnecessary `[NotNull]` attributes by [@0xced](https://github.com/0xced) in [#1255](https://github.com/spectreconsole/spectre.console/pull/1255)
|
||||
* Allow custom help providers by [@FrankRay78](https://github.com/FrankRay78) in [#1259](https://github.com/spectreconsole/spectre.console/pull/1259)
|
||||
* Specified details for settings for the argument vector by [@nils](https://github.com/nils)-a in [#1301](https://github.com/spectreconsole/spectre.console/pull/1301)
|
||||
* Add support for localisation in help provider by [@FrankRay78](https://github.com/FrankRay78) in [#1349](https://github.com/spectreconsole/spectre.console/pull/1349)
|
||||
* Fix DefaultValue for `FileInfo` and `DirectoryInfo` by [@0xced](https://github.com/0xced) in [#1238](https://github.com/spectreconsole/spectre.console/pull/1238)
|
||||
|
||||
## Documentation & Samples
|
||||
* Added a minimal PR template by [@nils](https://github.com/nils)-a in [#1318](https://github.com/spectreconsole/spectre.console/pull/1318)
|
||||
* Fix typo in `showcase` sample by [@AndrewRathbun](https://github.com/AndrewRathbun) in [#1315](https://github.com/spectreconsole/spectre.console/pull/1315)
|
||||
* Update `columns` sample to showcase nicer data by [@nils](https://github.com/nils)-a in [#1295](https://github.com/spectreconsole/spectre.console/pull/1295)
|
||||
* Change all `SetErrorHandler` to `SetExceptionHandler` by [@nils](https://github.com/nils)-a in [#1298](https://github.com/spectreconsole/spectre.console/pull/1298)
|
||||
|
||||
## Other stuff
|
||||
* Ensure the `Generator` project compiles by [@patriksvensson](https://github.com/patriksvensson) in [#1371](https://github.com/spectreconsole/spectre.console/pull/1371)
|
||||
@@ -0,0 +1,55 @@
|
||||
Title: Spectre.Console 0.49 released!
|
||||
Description: Bug fixes, bug fixes, bug fixes
|
||||
Published: 2024-04-23
|
||||
Category: Release Notes
|
||||
Excluded: false
|
||||
---
|
||||
|
||||
Version 0.49 of Spectre.Console has been released!
|
||||
|
||||
## New Contributors
|
||||
* @baronfel made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1425
|
||||
* @DarqueWarrior made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1431
|
||||
* @tonycknight made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1435
|
||||
* @caesay made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1439
|
||||
* @jsheely made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1414
|
||||
* @danielcweber made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1456
|
||||
* @martincostello made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1477
|
||||
* @slang25 made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1289
|
||||
* @thomhurst made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1250
|
||||
* @gerardog made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1489
|
||||
* @yenneferofvengerberg made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1503
|
||||
* @BlazeFace made their first contribution in https://github.com/spectreconsole/spectre.console/pull/1509
|
||||
|
||||
## Changes
|
||||
|
||||
* Cleanup line endings by @nils-a in https://github.com/spectreconsole/spectre.console/pull/1381
|
||||
* Added Spectre.Console.Cli to quick-start. by @nils-a in https://github.com/spectreconsole/spectre.console/pull/1413
|
||||
* Fix rendering of ListPrompt for odd pageSizes by @nils-a in https://github.com/spectreconsole/spectre.console/pull/1365
|
||||
* Remove mandelbrot example due to conflicting license by @patriksvensson in https://github.com/spectreconsole/spectre.console/pull/1426
|
||||
* Allow specifying a property to ignore the use of build-time packages for versioning and analysis by @baronfel in https://github.com/spectreconsole/spectre.console/pull/1425
|
||||
* Add the possibility to register multiple interceptors by @nils-a in https://github.com/spectreconsole/spectre.console/pull/1412
|
||||
* Added the ITypeResolver to the ExceptionHandler by @nils-a in https://github.com/spectreconsole/spectre.console/pull/1411
|
||||
* Updated typo in commandApp.md by @DarqueWarrior in https://github.com/spectreconsole/spectre.console/pull/1431
|
||||
* Command with -v displays app version instead of executing the command by @FrankRay78 in https://github.com/spectreconsole/spectre.console/pull/1427
|
||||
* HelpProvider colors should be configurable by @FrankRay78 in https://github.com/spectreconsole/spectre.console/pull/1408
|
||||
* Direct contributors to the current CONTRIBUTING.md by @tonycknight in https://github.com/spectreconsole/spectre.console/pull/1435
|
||||
* Fix deadlock when cancelling prompts by @caesay in https://github.com/spectreconsole/spectre.console/pull/1439
|
||||
* Add progress bar value formatter by @jsheely in https://github.com/spectreconsole/spectre.console/pull/1414
|
||||
* Update dependencies and do some clean-up by @patriksvensson in https://github.com/spectreconsole/spectre.console/pull/1440
|
||||
* Delete [UsesVerify], which has become obsolete through the latest update. by @danielcweber in https://github.com/spectreconsole/spectre.console/pull/1456
|
||||
* Don't erase secret prompt text upon backspace when mask is null by @danielcweber in https://github.com/spectreconsole/spectre.console/pull/1458
|
||||
* Update dependencies to the latest version by @patriksvensson in https://github.com/spectreconsole/spectre.console/pull/1459
|
||||
* Automatically register command settings by @patriksvensson in https://github.com/spectreconsole/spectre.console/pull/1463
|
||||
* Remove [DebuggerDisplay] from Paragraph by @martincostello in https://github.com/spectreconsole/spectre.console/pull/1477
|
||||
* Selection Prompt Search by @slang25 in https://github.com/spectreconsole/spectre.console/pull/1289
|
||||
* Update dependency SixLabors.ImageSharp to v3.1.3 by @renovate in https://github.com/spectreconsole/spectre.console/pull/1486
|
||||
* Positioned Progress Tasks - Before or After Other Tasks by @thomhurst in https://github.com/spectreconsole/spectre.console/pull/1250
|
||||
* Added NoStackTrace to ExceptionFormats by @gerardog in https://github.com/spectreconsole/spectre.console/pull/1489
|
||||
* Pipe character for listing options (issue 1434) by @FrankRay78 in https://github.com/spectreconsole/spectre.console/pull/1498
|
||||
* Improve XmlDoc output by @yenneferofvengerberg in https://github.com/spectreconsole/spectre.console/pull/1503
|
||||
* Revert 71a5d830 to undo flickering regression by @phil-scott-78 in https://github.com/spectreconsole/spectre.console/pull/1504
|
||||
* AddDelegate uses an abstract type when used in a branch by @BlazeFace in https://github.com/spectreconsole/spectre.console/pull/1509
|
||||
* Missing Separator When Headers are Hidden by @BlazeFace in https://github.com/spectreconsole/spectre.console/pull/1513
|
||||
* Expose raw arguments on the command context by @patriksvensson in https://github.com/spectreconsole/spectre.console/pull/1523
|
||||
* Add token representation to remaining arguments by @patriksvensson in https://github.com/spectreconsole/spectre.console/pull/1525
|
||||
@@ -1,47 +1,75 @@
|
||||
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.
|
||||
|
||||
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`.
|
||||
|
||||
## Styling the help text
|
||||
|
||||
Basic styling is applied to the generated help text by default, however this is configurable.
|
||||
|
||||
`HelpProviderStyle` is the `Spectre.Console` class that holds the style information for the help text.
|
||||
|
||||
The default theme shipped with Spectre.Console is provided by a factory method, `HelpProviderStyle.Default`.
|
||||
|
||||
However, you can explicitly set a custom theme when configuring a CommandApp, for example:
|
||||
|
||||
```csharp
|
||||
config.Settings.HelpProviderStyles = new HelpProviderStyle()
|
||||
{
|
||||
Description = new DescriptionStyle()
|
||||
{
|
||||
Header = "bold",
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
Removing all styling from help text is also possible, a good choice for ensuring maximum accessibility. This is configured by clearing the style provider entirely:
|
||||
|
||||
```csharp
|
||||
config.Settings.HelpProviderStyles = null;
|
||||
```
|
||||
|
||||
See [Markup](../markup) for information about the use of markup in Spectre.Console, and [Styles](xref:styles) for a listing of supported styles.
|
||||
|
||||
## 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.
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ app.Configure(config =>
|
||||
|
||||
## Multiple Commands
|
||||
|
||||
In the previous example we have a single command that is configured. For complex command line applications, it is common for them to have multiple commands (or verbs) defined. Examples of applications like this are `git`, `dotnet` and `gh`. For example, git would have a `commit` command and along with other commits like `add` or `rebase`. Each with their own settings and validation. With `Spectre.Console.Cli` we use the `Configure` method to add these commands.
|
||||
In the previous example we have a single command that is configured. For complex command line applications, it is common for them to have multiple commands (or verbs) defined. Examples of applications like this are `git`, `dotnet` and `gh`. For example, git would have a `commit` command and along with other commands like `add` or `rebase`. Each with their own settings and validation. With `Spectre.Console.Cli` we use the `Configure` method to add these commands.
|
||||
|
||||
For example, to add three different commands to the application:
|
||||
|
||||
@@ -81,9 +81,12 @@ Hint: If you do write your own implementation of `TypeRegistrar` and `TypeResolv
|
||||
there is a utility `TypeRegistrarBaseTests` available that can be used to ensure your implementations adhere to the required implementation. Simply call `TypeRegistrarBaseTests.RunAllTests()` and expect no `TypeRegistrarBaseTests.TestFailedException` to be thrown.
|
||||
|
||||
## Interception
|
||||
Interceptors can be registered with the `TypeRegistrar` (or with a custom DI-Container). Alternatively, `CommandApp` also provides a `SetInterceptor` configuration.
|
||||
|
||||
`CommandApp` also provides a `SetInterceptor` configuration. An interceptor is run before all commands are executed. This is typically used for configuring logging or other infrastructure concerns.
|
||||
All interceptors must implement `ICommandInterceptor`. Upon execution of a command, The `Intercept`-Method of an instance of your interceptor will be called with the parsed settings. This provides an opportunity for configuring any infrastructure or modifying the settings.
|
||||
When the command has been run, the `InterceptResult`-Method of the same instance is called with the result of the command.
|
||||
This provides an opportunity to modify the result and also to tear down any infrastructure in use.
|
||||
|
||||
All interceptors must implement `ICommandInterceptor`. Upon execution of a command, an instance of your interceptor will be called with the parsed settings. This provides an opportunity for configuring any infrastructure or modifying the settings.
|
||||
The `Intercept`-Method of each interceptor is run before the command is executed and the `InterceptResult`-Method is run after it. These are typically used for configuring logging or other infrastructure concerns.
|
||||
|
||||
For an example of using the interceptor to configure logging, see the [Serilog demo](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Logging).
|
||||
|
||||
@@ -53,7 +53,11 @@ Using the `SetExceptionHandler()` during configuration it is possible to handle
|
||||
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 `SetExceptionHandler(Func<Exception, int> handler)`
|
||||
The `ITypeResolver?` parameter will be null, when the exception occurs while no `ITypeResolver` is available.
|
||||
(Basically the `ITypeResolver` will be set, when the exception occurs during a command execution, but not
|
||||
during the parsing phase and construction of the command.)
|
||||
|
||||
### Using `SetExceptionHandler(Func<Exception, ITypeResolver?, 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.
|
||||
@@ -71,7 +75,7 @@ namespace MyApp
|
||||
|
||||
app.Configure(config =>
|
||||
{
|
||||
config.SetExceptionHandler(ex =>
|
||||
config.SetExceptionHandler((ex, resolver) =>
|
||||
{
|
||||
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
||||
return -99;
|
||||
@@ -84,9 +88,9 @@ namespace MyApp
|
||||
}
|
||||
```
|
||||
|
||||
### Using `SetExceptionHandler(Action<Exception> handler)`
|
||||
### Using `SetExceptionHandler(Action<Exception, ITypeResolver?> handler)`
|
||||
|
||||
Using this method exceptions can be handled in a custom way, much the same as with the `SetExceptionHandler(Func<Exception, int> handler)`.
|
||||
Using this method exceptions can be handled in a custom way, much the same as with the `SetExceptionHandler(Func<Exception, ITypeResolver?, 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`.
|
||||
|
||||
@@ -103,7 +107,7 @@ namespace MyApp
|
||||
|
||||
app.Configure(config =>
|
||||
{
|
||||
config.SetExceptionHandler(ex =>
|
||||
config.SetExceptionHandler((ex, resolver) =>
|
||||
{
|
||||
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
||||
});
|
||||
|
||||
@@ -1,119 +1,119 @@
|
||||
Title: Markup
|
||||
Order: 30
|
||||
Description: The Markup class allows you to output rich text to the console.
|
||||
Highlights:
|
||||
- Easily add *color*.
|
||||
- Add hyperlinks to for supported terminals.
|
||||
- Emoji 🚀 parsing.
|
||||
Reference:
|
||||
- M:Spectre.Console.AnsiConsole.Markup(System.String)
|
||||
- M:Spectre.Console.AnsiConsole.MarkupLine(System.String)
|
||||
- T:Spectre.Console.Markup
|
||||
---
|
||||
|
||||
The `Markup` class allows you to output rich text to the console.
|
||||
|
||||
## Syntax
|
||||
|
||||
Console markup uses a syntax inspired by bbcode. If you write the style (see [Styles](xref:styles))
|
||||
in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Write(new Markup("[bold yellow]Hello[/] [red]World![/]"));
|
||||
```
|
||||
|
||||
The `Markup` class implements `IRenderable` which means that you
|
||||
can use this in tables, grids, and panels. Most classes that support
|
||||
rendering of `IRenderable` also have overloads for rendering rich text.
|
||||
|
||||
```csharp
|
||||
var table = new Table();
|
||||
table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]")));
|
||||
table.AddColumn(new TableColumn("[blue]Bar[/]"));
|
||||
AnsiConsole.Write(table);
|
||||
```
|
||||
|
||||
## Convenience methods
|
||||
|
||||
There are also convenience methods on `AnsiConsole` that can be used
|
||||
to write markup text to the console without instantiating a new `Markup`
|
||||
instance.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[underline green]Hello[/] ");
|
||||
AnsiConsole.MarkupLine("[bold]World[/]");
|
||||
```
|
||||
|
||||
## Escaping format characters
|
||||
|
||||
To output a `[` you use `[[`, and to output a `]` you use `]]`.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[[Hello]] "); // [Hello]
|
||||
AnsiConsole.Markup("[red][[World]][/]"); // [World]
|
||||
```
|
||||
|
||||
You can also use the `EscapeMarkup` extension method.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".EscapeMarkup());
|
||||
```
|
||||
You can also use the `Markup.Escape` method.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[red]{0}[/]", Markup.Escape("Hello [World]"));
|
||||
```
|
||||
|
||||
## Escaping Interpolated Strings
|
||||
|
||||
When working with interpolated strings, you can use the `MarkupInterpolated` and `MarkupLineInterpolated` methods to automatically escape the values in the interpolated string "holes".
|
||||
|
||||
```csharp
|
||||
string hello = "Hello [World]";
|
||||
AnsiConsole.MarkupInterpolated($"[red]{hello}[/]");
|
||||
```
|
||||
|
||||
## Setting background color
|
||||
|
||||
You can set the background color in markup by prefixing the color with `on`.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[bold yellow on blue]Hello[/]");
|
||||
AnsiConsole.Markup("[default on blue]World[/]");
|
||||
```
|
||||
|
||||
## Rendering emojis
|
||||
|
||||
To output an emoji as part of markup, you can use emoji shortcodes.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("Hello :globe_showing_europe_africa:!");
|
||||
```
|
||||
|
||||
For a list of emoji, see the [Emojis](xref:emojis) appendix section.
|
||||
|
||||
## Colors
|
||||
|
||||
In the examples above, all colors were referenced by their name,
|
||||
but you can also use the hex or rgb representation for colors in markdown.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[red]Foo[/] ");
|
||||
AnsiConsole.Markup("[#ff0000]Bar[/] ");
|
||||
AnsiConsole.Markup("[rgb(255,0,0)]Baz[/] ");
|
||||
```
|
||||
|
||||
For a list of colors, see the [Colors](xref:colors) appendix section.
|
||||
|
||||
## Links
|
||||
|
||||
To output a clickable link, you can use the `[link]` style.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[link]https://spectreconsole.net[/]");
|
||||
AnsiConsole.Markup("[link=https://spectreconsole.net]Spectre Console Documentation[/]");
|
||||
```
|
||||
|
||||
## Styles
|
||||
|
||||
For a list of styles, see the [Styles](xref:styles) appendix section.
|
||||
Title: Markup
|
||||
Order: 30
|
||||
Description: The Markup class allows you to output rich text to the console.
|
||||
Highlights:
|
||||
- Easily add *color*.
|
||||
- Add hyperlinks to for supported terminals.
|
||||
- Emoji 🚀 parsing.
|
||||
Reference:
|
||||
- M:Spectre.Console.AnsiConsole.Markup(System.String)
|
||||
- M:Spectre.Console.AnsiConsole.MarkupLine(System.String)
|
||||
- T:Spectre.Console.Markup
|
||||
---
|
||||
|
||||
The `Markup` class allows you to output rich text to the console.
|
||||
|
||||
## Syntax
|
||||
|
||||
Console markup uses a syntax inspired by bbcode. If you write the style (see [Styles](xref:styles))
|
||||
in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Write(new Markup("[bold yellow]Hello[/] [red]World![/]"));
|
||||
```
|
||||
|
||||
The `Markup` class implements `IRenderable` which means that you
|
||||
can use this in tables, grids, and panels. Most classes that support
|
||||
rendering of `IRenderable` also have overloads for rendering rich text.
|
||||
|
||||
```csharp
|
||||
var table = new Table();
|
||||
table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]")));
|
||||
table.AddColumn(new TableColumn("[blue]Bar[/]"));
|
||||
AnsiConsole.Write(table);
|
||||
```
|
||||
|
||||
## Convenience methods
|
||||
|
||||
There are also convenience methods on `AnsiConsole` that can be used
|
||||
to write markup text to the console without instantiating a new `Markup`
|
||||
instance.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[underline green]Hello[/] ");
|
||||
AnsiConsole.MarkupLine("[bold]World[/]");
|
||||
```
|
||||
|
||||
## Escaping format characters
|
||||
|
||||
To output a `[` you use `[[`, and to output a `]` you use `]]`.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[[Hello]] "); // [Hello]
|
||||
AnsiConsole.Markup("[red][[World]][/]"); // [World]
|
||||
```
|
||||
|
||||
You can also use the `EscapeMarkup` extension method.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".EscapeMarkup());
|
||||
```
|
||||
You can also use the `Markup.Escape` method.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[red]{0}[/]", Markup.Escape("Hello [World]"));
|
||||
```
|
||||
|
||||
## Escaping Interpolated Strings
|
||||
|
||||
When working with interpolated strings, you can use the `MarkupInterpolated` and `MarkupLineInterpolated` methods to automatically escape the values in the interpolated string "holes".
|
||||
|
||||
```csharp
|
||||
string hello = "Hello [World]";
|
||||
AnsiConsole.MarkupInterpolated($"[red]{hello}[/]");
|
||||
```
|
||||
|
||||
## Setting background color
|
||||
|
||||
You can set the background color in markup by prefixing the color with `on`.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[bold yellow on blue]Hello[/]");
|
||||
AnsiConsole.Markup("[default on blue]World[/]");
|
||||
```
|
||||
|
||||
## Rendering emojis
|
||||
|
||||
To output an emoji as part of markup, you can use emoji shortcodes.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("Hello :globe_showing_europe_africa:!");
|
||||
```
|
||||
|
||||
For a list of emoji, see the [Emojis](xref:emojis) appendix section.
|
||||
|
||||
## Colors
|
||||
|
||||
In the examples above, all colors were referenced by their name,
|
||||
but you can also use the hex or rgb representation for colors in markdown.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[red]Foo[/] ");
|
||||
AnsiConsole.Markup("[#ff0000]Bar[/] ");
|
||||
AnsiConsole.Markup("[rgb(255,0,0)]Baz[/] ");
|
||||
```
|
||||
|
||||
For a list of colors, see the [Colors](xref:colors) appendix section.
|
||||
|
||||
## Links
|
||||
|
||||
To output a clickable link, you can use the `[link]` style.
|
||||
|
||||
```csharp
|
||||
AnsiConsole.Markup("[link]https://spectreconsole.net[/]");
|
||||
AnsiConsole.Markup("[link=https://spectreconsole.net]Spectre Console Documentation[/]");
|
||||
```
|
||||
|
||||
## Styles
|
||||
|
||||
For a list of styles, see the [Styles](xref:styles) appendix section.
|
||||
|
||||
@@ -63,22 +63,22 @@ What's the secret number? _
|
||||
|
||||
```text
|
||||
Enter password: ************_
|
||||
```
|
||||
|
||||
## Masks
|
||||
|
||||
```
|
||||
|
||||
## Masks
|
||||
|
||||
<?# Example symbol="M:Prompt.Program.AskPasswordWithCustomMask" project="Prompt" /?>
|
||||
|
||||
|
||||
```text
|
||||
```text
|
||||
Enter password: ------------_
|
||||
```
|
||||
|
||||
You can utilize a null character to completely hide input.
|
||||
|
||||
```
|
||||
|
||||
You can utilize a null character to completely hide input.
|
||||
|
||||
<?# Example symbol="M:Prompt.Program.AskPasswordWithNullMask" project="Prompt" /?>
|
||||
|
||||
```text
|
||||
```text
|
||||
Enter password: _
|
||||
```
|
||||
|
||||
|
||||
@@ -8,9 +8,10 @@ to install the NuGet package.
|
||||
|
||||
```text
|
||||
> dotnet add package Spectre.Console
|
||||
> dotnet add package Spectre.Console.Cli
|
||||
```
|
||||
|
||||
After that you will need to reference the `Spectre.Console` namespace.
|
||||
After that you will need to reference the `Spectre.Console` and `Spectre.Console.Cli` namespaces.
|
||||
Once that is done, you can start using all the available features.
|
||||
|
||||
```csharp
|
||||
|
||||
@@ -132,3 +132,13 @@ AnsiConsole.Write(new BreakdownChart()
|
||||
.AddItem(new Fruit("Mango", 3, Color.Orange4))
|
||||
.AddItems(items));
|
||||
```
|
||||
|
||||
### Add value formatter to chart numbers
|
||||
|
||||
```csharp
|
||||
var chart = new BreakdownChart();
|
||||
chart.UseValueFormater(value => value.ToString("N0"));
|
||||
|
||||
// This can be simplified as extension methods are chainable.
|
||||
var chart = new BreakdownChart().UseValueFormatter(v => v.ToString("N0"));
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
Title: Calendar
|
||||
Title: Calendar
|
||||
Order: 40
|
||||
RedirectFrom: calendar
|
||||
Description: "The **Calendar** is used to render a calendar to the terminal."
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Statiq.App;
|
||||
using Statiq.App;
|
||||
using Statiq.Common;
|
||||
using Statiq.Web;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Docs.Extensions
|
||||
namespace Docs.Extensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Docs.Models
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using Docs.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Statiq.Common;
|
||||
using Statiq.Common;
|
||||
using Statiq.Web.GitHub;
|
||||
using Statiq.Web.Netlify;
|
||||
|
||||
|
||||
@@ -1,119 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Playwright;
|
||||
using Statiq.Common;
|
||||
using Statiq.Core;
|
||||
using Statiq.Web;
|
||||
using Statiq.Web.Modules;
|
||||
using Statiq.Web.Pipelines;
|
||||
|
||||
namespace Docs.Pipelines
|
||||
{
|
||||
public class SocialImages : Pipeline
|
||||
{
|
||||
public SocialImages()
|
||||
{
|
||||
Dependencies.AddRange(nameof(Inputs));
|
||||
|
||||
ProcessModules = new ModuleList
|
||||
{
|
||||
new GetPipelineDocuments(ContentType.Content),
|
||||
|
||||
// Filter to non-archive content
|
||||
new FilterDocuments(Config.FromDocument(doc => !Archives.IsArchive(doc))),
|
||||
|
||||
// Process the content
|
||||
new CacheDocuments
|
||||
{
|
||||
new AddTitle(),
|
||||
new SetDestination(true),
|
||||
new ExecuteIf(Config.FromSetting(WebKeys.OptimizeContentFileNames, true))
|
||||
{
|
||||
new OptimizeFileName()
|
||||
},
|
||||
new GenerateSocialImage(),
|
||||
}
|
||||
};
|
||||
|
||||
OutputModules = new ModuleList { new WriteFiles() };
|
||||
}
|
||||
}
|
||||
|
||||
class GenerateSocialImage : ParallelModule
|
||||
{
|
||||
private IPlaywright _playwright;
|
||||
private IBrowser _browser;
|
||||
private WebApplication _app;
|
||||
private IBrowserContext _context;
|
||||
|
||||
protected override async Task BeforeExecutionAsync(IExecutionContext context)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
builder.Logging.ClearProviders();
|
||||
|
||||
builder.Services
|
||||
.AddRazorPages()
|
||||
.WithRazorPagesRoot("/src/SocialCards/");
|
||||
|
||||
_app = builder.Build();
|
||||
_app.MapRazorPages();
|
||||
_app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(
|
||||
Path.Combine(builder.Environment.ContentRootPath, "src/SocialCards")),
|
||||
RequestPath = "/static"
|
||||
});
|
||||
|
||||
await _app.StartAsync().ConfigureAwait(false);
|
||||
|
||||
_playwright = await Playwright.CreateAsync().ConfigureAwait(false);
|
||||
_browser = await _playwright.Chromium.LaunchAsync().ConfigureAwait(false);
|
||||
_context = await _browser.NewContextAsync(new BrowserNewContextOptions {
|
||||
ViewportSize = new ViewportSize { Width = 1200, Height = 618 },
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override async Task FinallyAsync(IExecutionContext context)
|
||||
{
|
||||
await _context.DisposeAsync().ConfigureAwait(false);
|
||||
await _browser.DisposeAsync().ConfigureAwait(false);
|
||||
_playwright.Dispose();
|
||||
await _app.DisposeAsync().ConfigureAwait(false);
|
||||
await base.FinallyAsync(context);
|
||||
}
|
||||
|
||||
protected override async Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context)
|
||||
{
|
||||
var url = _app.Urls.FirstOrDefault(u => u.StartsWith("http://"));
|
||||
var page = await _context.NewPageAsync().ConfigureAwait(false);
|
||||
|
||||
var title = input.GetString("Title");
|
||||
var description = input.GetString("Description");
|
||||
var highlights = input.GetList<string>("Highlights") ?? Array.Empty<string>();
|
||||
|
||||
await page.GotoAsync($"{url}/?title={title}&desc={description}&highlights={string.Join("||", highlights)}");
|
||||
|
||||
// This will not just wait for the page to load over the network, but it'll also give
|
||||
// chrome a chance to complete rendering of the fonts while the wait timeout completes.
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle).ConfigureAwait(false);
|
||||
var bytes = await page.ScreenshotAsync().ConfigureAwait(false);
|
||||
await page.CloseAsync().ConfigureAwait(false);
|
||||
|
||||
var destination = input.Destination.InsertSuffix("-social").ChangeExtension("png");
|
||||
var doc = context.CreateDocument(
|
||||
input.Source,
|
||||
destination,
|
||||
new MetadataItems { { "DocId", input.Id }},
|
||||
context.GetContentProvider(bytes));
|
||||
|
||||
return new[] { doc };
|
||||
}
|
||||
}
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Playwright;
|
||||
using Statiq.Common;
|
||||
using Statiq.Core;
|
||||
using Statiq.Web;
|
||||
using Statiq.Web.Modules;
|
||||
using Statiq.Web.Pipelines;
|
||||
|
||||
namespace Docs.Pipelines
|
||||
{
|
||||
public class SocialImages : Pipeline
|
||||
{
|
||||
public SocialImages()
|
||||
{
|
||||
Dependencies.AddRange(nameof(Inputs));
|
||||
|
||||
ProcessModules = new ModuleList
|
||||
{
|
||||
new GetPipelineDocuments(ContentType.Content),
|
||||
|
||||
// Filter to non-archive content
|
||||
new FilterDocuments(Config.FromDocument(doc => !Archives.IsArchive(doc))),
|
||||
|
||||
// Process the content
|
||||
new CacheDocuments
|
||||
{
|
||||
new AddTitle(),
|
||||
new SetDestination(true),
|
||||
new ExecuteIf(Config.FromSetting(WebKeys.OptimizeContentFileNames, true))
|
||||
{
|
||||
new OptimizeFileName()
|
||||
},
|
||||
new GenerateSocialImage(),
|
||||
}
|
||||
};
|
||||
|
||||
OutputModules = new ModuleList { new WriteFiles() };
|
||||
}
|
||||
}
|
||||
|
||||
class GenerateSocialImage : ParallelModule
|
||||
{
|
||||
private IPlaywright _playwright;
|
||||
private IBrowser _browser;
|
||||
private WebApplication _app;
|
||||
private IBrowserContext _context;
|
||||
|
||||
protected override async Task BeforeExecutionAsync(IExecutionContext context)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
builder.Logging.ClearProviders();
|
||||
|
||||
builder.Services
|
||||
.AddRazorPages()
|
||||
.WithRazorPagesRoot("/src/SocialCards/");
|
||||
|
||||
_app = builder.Build();
|
||||
_app.MapRazorPages();
|
||||
_app.UseStaticFiles(new StaticFileOptions
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(
|
||||
Path.Combine(builder.Environment.ContentRootPath, "src/SocialCards")),
|
||||
RequestPath = "/static"
|
||||
});
|
||||
|
||||
await _app.StartAsync().ConfigureAwait(false);
|
||||
|
||||
_playwright = await Playwright.CreateAsync().ConfigureAwait(false);
|
||||
_browser = await _playwright.Chromium.LaunchAsync().ConfigureAwait(false);
|
||||
_context = await _browser.NewContextAsync(new BrowserNewContextOptions {
|
||||
ViewportSize = new ViewportSize { Width = 1200, Height = 618 },
|
||||
}).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
protected override async Task FinallyAsync(IExecutionContext context)
|
||||
{
|
||||
await _context.DisposeAsync().ConfigureAwait(false);
|
||||
await _browser.DisposeAsync().ConfigureAwait(false);
|
||||
_playwright.Dispose();
|
||||
await _app.DisposeAsync().ConfigureAwait(false);
|
||||
await base.FinallyAsync(context);
|
||||
}
|
||||
|
||||
protected override async Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context)
|
||||
{
|
||||
var url = _app.Urls.FirstOrDefault(u => u.StartsWith("http://"));
|
||||
var page = await _context.NewPageAsync().ConfigureAwait(false);
|
||||
|
||||
var title = input.GetString("Title");
|
||||
var description = input.GetString("Description");
|
||||
var highlights = input.GetList<string>("Highlights") ?? Array.Empty<string>();
|
||||
|
||||
await page.GotoAsync($"{url}/?title={title}&desc={description}&highlights={string.Join("||", highlights)}");
|
||||
|
||||
// This will not just wait for the page to load over the network, but it'll also give
|
||||
// chrome a chance to complete rendering of the fonts while the wait timeout completes.
|
||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle).ConfigureAwait(false);
|
||||
var bytes = await page.ScreenshotAsync().ConfigureAwait(false);
|
||||
await page.CloseAsync().ConfigureAwait(false);
|
||||
|
||||
var destination = input.Destination.InsertSuffix("-social").ChangeExtension("png");
|
||||
var doc = context.CreateDocument(
|
||||
input.Source,
|
||||
destination,
|
||||
new MetadataItems { { "DocId", input.Id }},
|
||||
context.GetContentProvider(bytes));
|
||||
|
||||
return new[] { doc };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Docs.Extensions;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
|
||||
<div id="container">
|
||||
<div id="console">
|
||||
<div class="line"><span style="color:var(--brightBlack)">╭─</span><span style="color:var(--folder)"></span><span style="background-color:var(--folder);color:var(--black)"> ~/spectre.console</span><span style="color:var(--folder);background-color:var(--dotnet)"></span><span style="background-color:var(--blue)"> .NET 7.0 </span><span style="color:var(--dotnet);background-color:var(--git)"></span><span style="background-color:var(--git);color:var(--background)">  main </span><span style="color:var(--git)"></span></div>
|
||||
<div class="line"><span style="color:var(--brightBlack)">╭─</span><span style="color:var(--folder)"></span><span style="background-color:var(--folder);color:var(--black)"> ~/spectre.console</span><span style="color:var(--folder);background-color:var(--dotnet)"></span><span style="background-color:var(--blue)"> .NET 8.0 </span><span style="color:var(--dotnet);background-color:var(--git)"></span><span style="background-color:var(--git);color:var(--background)">  main </span><span style="color:var(--git)"></span></div>
|
||||
<div class="line"><span style="color:var(--brightBlack)">╰─</span> dotnet run</div>
|
||||
<div class="line"></div>
|
||||
<div class="line">╭────────────────────────────────────────────────────────╮</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
@@ -9,10 +9,16 @@
|
||||
]
|
||||
},
|
||||
"dotnet-example": {
|
||||
"version": "2.0.0",
|
||||
"version": "3.1.0",
|
||||
"commands": [
|
||||
"dotnet-example"
|
||||
]
|
||||
},
|
||||
"verify.tool": {
|
||||
"version": "0.6.0",
|
||||
"commands": [
|
||||
"dotnet-verify"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,22 +15,22 @@ public static class Program
|
||||
{
|
||||
config.SetApplicationName("fake-dotnet");
|
||||
config.ValidateExamples();
|
||||
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")
|
||||
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");
|
||||
});
|
||||
|
||||
|
||||
@@ -1,30 +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,
|
||||
};
|
||||
}
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,20 +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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,23 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
using Spectre.Console.Cli;
|
||||
using Spectre.Console.Cli.Help;
|
||||
|
||||
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));
|
||||
|
||||
// Render an unstyled help text for maximum accessibility
|
||||
config.Settings.HelpProviderStyles = null;
|
||||
});
|
||||
|
||||
return app.Run(args);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
Ported from: https://rosettacode.org/wiki/Mandelbrot_set#C.23
|
||||
Licensed under GNU Free Documentation License 1.2
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Canvas;
|
||||
|
||||
public static class Mandelbrot
|
||||
{
|
||||
private const double MaxValueExtent = 2.0;
|
||||
|
||||
private struct ComplexNumber
|
||||
{
|
||||
public double Real { get; }
|
||||
public double Imaginary { get; }
|
||||
|
||||
public ComplexNumber(double real, double imaginary)
|
||||
{
|
||||
Real = real;
|
||||
Imaginary = imaginary;
|
||||
}
|
||||
|
||||
public static ComplexNumber operator +(ComplexNumber x, ComplexNumber y)
|
||||
{
|
||||
return new ComplexNumber(x.Real + y.Real, x.Imaginary + y.Imaginary);
|
||||
}
|
||||
|
||||
public static ComplexNumber operator *(ComplexNumber x, ComplexNumber y)
|
||||
{
|
||||
return new ComplexNumber(x.Real * y.Real - x.Imaginary * y.Imaginary,
|
||||
x.Real * y.Imaginary + x.Imaginary * y.Real);
|
||||
}
|
||||
|
||||
public double Abs()
|
||||
{
|
||||
return Real * Real + Imaginary * Imaginary;
|
||||
}
|
||||
}
|
||||
|
||||
public static Spectre.Console.Canvas Generate(int width, int height)
|
||||
{
|
||||
var canvas = new Spectre.Console.Canvas(width, height);
|
||||
|
||||
var scale = 2 * MaxValueExtent / Math.Min(canvas.Width, canvas.Height);
|
||||
for (var i = 0; i < canvas.Height; i++)
|
||||
{
|
||||
var y = (canvas.Height / 2 - i) * scale;
|
||||
for (var j = 0; j < canvas.Width; j++)
|
||||
{
|
||||
var x = (j - canvas.Width / 2) * scale;
|
||||
var value = Calculate(new ComplexNumber(x, y));
|
||||
canvas.SetPixel(j, i, GetColor(value));
|
||||
}
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
|
||||
private static double Calculate(ComplexNumber c)
|
||||
{
|
||||
const int MaxIterations = 1000;
|
||||
const double MaxNorm = MaxValueExtent * MaxValueExtent;
|
||||
|
||||
var iteration = 0;
|
||||
var z = new ComplexNumber();
|
||||
do
|
||||
{
|
||||
z = z * z + c;
|
||||
iteration++;
|
||||
} while (z.Abs() < MaxNorm && iteration < MaxIterations);
|
||||
|
||||
return iteration < MaxIterations
|
||||
? (double)iteration / MaxIterations
|
||||
: 0;
|
||||
}
|
||||
|
||||
private static Color GetColor(double value)
|
||||
{
|
||||
const double MaxColor = 256;
|
||||
const double ContrastValue = 0.2;
|
||||
return new Color(0, 0, (byte)(MaxColor * Math.Pow(value, ContrastValue)));
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,6 @@ public static class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
// Draw a mandelbrot set using a Canvas
|
||||
var mandelbrot = Mandelbrot.Generate(32, 32);
|
||||
Render(mandelbrot, "Mandelbrot");
|
||||
|
||||
// Draw an image using CanvasImage powered by ImageSharp.
|
||||
// This requires the "Spectre.Console.ImageSharp" NuGet package.
|
||||
var image = new CanvasImage("cake.png");
|
||||
@@ -21,7 +17,7 @@ public static class Program
|
||||
image.MaxWidth(16);
|
||||
Render(image, "Image from file (16 wide)");
|
||||
|
||||
// Draw image again, but without render width
|
||||
// Draw image again, but without max width
|
||||
image.NoMaxWidth();
|
||||
image.Mutate(ctx => ctx.Grayscale().Rotate(-45).EntropyCrop());
|
||||
Render(image, "Image from file (fit, greyscale, rotated)");
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -33,11 +33,11 @@ namespace Prompt
|
||||
var age = AskAge();
|
||||
|
||||
WriteDivider("Secrets");
|
||||
var password = AskPassword();
|
||||
|
||||
var password = AskPassword();
|
||||
|
||||
WriteDivider("Mask");
|
||||
var mask = AskPasswordWithCustomMask();
|
||||
|
||||
var mask = AskPasswordWithCustomMask();
|
||||
|
||||
WriteDivider("Null Mask");
|
||||
var nullMask = AskPasswordWithNullMask();
|
||||
|
||||
@@ -54,8 +54,8 @@ namespace Prompt
|
||||
.AddRow("[grey]Favorite fruit[/]", fruit)
|
||||
.AddRow("[grey]Favorite sport[/]", sport)
|
||||
.AddRow("[grey]Age[/]", age.ToString())
|
||||
.AddRow("[grey]Password[/]", password)
|
||||
.AddRow("[grey]Mask[/]", mask)
|
||||
.AddRow("[grey]Password[/]", password)
|
||||
.AddRow("[grey]Mask[/]", mask)
|
||||
.AddRow("[grey]Null Mask[/]", nullMask)
|
||||
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
|
||||
}
|
||||
@@ -110,6 +110,7 @@ namespace Prompt
|
||||
{
|
||||
fruit = AnsiConsole.Prompt(
|
||||
new SelectionPrompt<string>()
|
||||
.EnableSearch()
|
||||
.Title("Ok, but if you could only choose [green]one[/]?")
|
||||
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
|
||||
.AddChoices(favorites));
|
||||
@@ -153,22 +154,22 @@ namespace Prompt
|
||||
new TextPrompt<string>("Enter [green]password[/]?")
|
||||
.PromptStyle("red")
|
||||
.Secret());
|
||||
}
|
||||
|
||||
public static string AskPasswordWithCustomMask()
|
||||
{
|
||||
}
|
||||
|
||||
public static string AskPasswordWithCustomMask()
|
||||
{
|
||||
return AnsiConsole.Prompt(
|
||||
new TextPrompt<string>("Enter [green]password[/]?")
|
||||
.PromptStyle("red")
|
||||
.Secret('-'));
|
||||
}
|
||||
|
||||
public static string AskPasswordWithNullMask()
|
||||
{
|
||||
.Secret('-'));
|
||||
}
|
||||
|
||||
public static string AskPasswordWithNullMask()
|
||||
{
|
||||
return AnsiConsole.Prompt(
|
||||
new TextPrompt<string>("Enter [green]password[/]?")
|
||||
.PromptStyle("red")
|
||||
.Secret(null));
|
||||
.Secret(null));
|
||||
}
|
||||
|
||||
public static string AskColor()
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/global",
|
||||
"sdk": {
|
||||
"version": "8.0.100",
|
||||
"version": "8.0.200",
|
||||
"rollForward": "latestFeature"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Spectre.Console;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Generator.Commands
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
<PropertyGroup Label="Settings">
|
||||
<Deterministic>true</Deterministic>
|
||||
<LangVersion>10</LangVersion>
|
||||
<LangVersion>12</LangVersion>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>embedded</DebugType>
|
||||
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
|
||||
@@ -15,8 +15,8 @@
|
||||
|
||||
<PropertyGroup Label="Package Information">
|
||||
<Description>A library that makes it easier to create beautiful console applications.</Description>
|
||||
<Copyright>Patrik Svensson, Phil Scott, Nils Andresen</Copyright>
|
||||
<Authors>Patrik Svensson, Phil Scott, Nils Andresen</Authors>
|
||||
<Copyright>Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray</Copyright>
|
||||
<Authors>Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray</Authors>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<RepositoryUrl>https://github.com/spectreconsole/spectre.console</RepositoryUrl>
|
||||
<PackageIcon>small-logo.png</PackageIcon>
|
||||
@@ -30,14 +30,15 @@
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup Label="Package References">
|
||||
<PackageReference Include="MinVer" PrivateAssets="All" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.1.1" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
||||
|
||||
<!-- Allow folks to build with minimal dependencies (though they will need to provide their own Version data) -->
|
||||
<ItemGroup Label="Build Tools Package References" Condition="'$(UseBuildTimeTools)' != 'false'">
|
||||
<PackageReference Include="MinVer" PrivateAssets="All" Version="4.3.0" />
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Roslynator.Analyzers" Version="4.1.2">
|
||||
<PackageReference Include="Roslynator.Analyzers" Version="4.12.1">
|
||||
<PrivateAssets>All</PrivateAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project>
|
||||
<Target Name="Versioning" BeforeTargets="MinVer">
|
||||
<PropertyGroup Label="Build">
|
||||
<MinVerDefaultPreReleasePhase>preview</MinVerDefaultPreReleasePhase>
|
||||
<MinVerDefaultPreReleaseIdentifiers>preview.0</MinVerDefaultPreReleaseIdentifiers>
|
||||
<MinVerVerbosity>normal</MinVerVerbosity>
|
||||
</PropertyGroup>
|
||||
</Target>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.CodeAnalysis.Editing;
|
||||
using Microsoft.CodeAnalysis.Editing;
|
||||
using Microsoft.CodeAnalysis.Simplification;
|
||||
|
||||
namespace Spectre.Console.Analyzer.CodeActions;
|
||||
@@ -32,171 +32,171 @@ public class SwitchToAnsiConsoleAction : CodeAction
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
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;
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -8,20 +8,21 @@
|
||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
|
||||
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.2.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.2.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" PrivateAssets="all" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="bin\Debug\netstandard2.0\\Spectre.Console.Analyzer.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Spectre.Console.Cli.Internal.Configuration;
|
||||
|
||||
using Spectre.Console.Cli.Internal.Configuration;
|
||||
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
/// <summary>
|
||||
@@ -102,7 +102,7 @@ public sealed class CommandApp : ICommandApp
|
||||
|
||||
if (_configurator.Settings.ExceptionHandler != null)
|
||||
{
|
||||
return _configurator.Settings.ExceptionHandler(ex);
|
||||
return _configurator.Settings.ExceptionHandler(ex, null);
|
||||
}
|
||||
|
||||
// Render the exception.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using Spectre.Console.Cli.Internal.Configuration;
|
||||
|
||||
using Spectre.Console.Cli.Internal.Configuration;
|
||||
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
/// <summary>
|
||||
@@ -49,8 +49,8 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
|
||||
public Task<int> RunAsync(IEnumerable<string> args)
|
||||
{
|
||||
return _app.RunAsync(args);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal Configurator GetConfigurator()
|
||||
{
|
||||
return _app.GetConfigurator();
|
||||
|
||||
@@ -13,6 +13,11 @@ public sealed class CommandContext
|
||||
/// </value>
|
||||
public IRemainingArguments Remaining { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the arguments that were passed to the applicaton.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> Arguments { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the command.
|
||||
/// </summary>
|
||||
@@ -32,11 +37,17 @@ public sealed class CommandContext
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CommandContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="arguments">All arguments that were passed to the application.</param>
|
||||
/// <param name="remaining">The remaining arguments.</param>
|
||||
/// <param name="name">The command name.</param>
|
||||
/// <param name="data">The command data.</param>
|
||||
public CommandContext(IRemainingArguments remaining, string name, object? data)
|
||||
public CommandContext(
|
||||
IEnumerable<string> arguments,
|
||||
IRemainingArguments remaining,
|
||||
string name,
|
||||
object? data)
|
||||
{
|
||||
Arguments = arguments.ToSafeReadOnlyList();
|
||||
Remaining = remaining ?? throw new System.ArgumentNullException(nameof(remaining));
|
||||
Name = name ?? throw new System.ArgumentNullException(nameof(name));
|
||||
Data = data;
|
||||
|
||||
@@ -5,40 +5,40 @@ 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)
|
||||
{
|
||||
{
|
||||
/// <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
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
configurator.SetHelpProvider<T>();
|
||||
return configurator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -82,7 +82,7 @@ public static class ConfiguratorExtensions
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the auto-detected version of the application.
|
||||
/// Sets the version of the application.
|
||||
/// </summary>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="version">The version of application.</param>
|
||||
@@ -98,6 +98,25 @@ public static class ConfiguratorExtensions
|
||||
return configurator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uses the version retrieved from the <see cref="AssemblyInformationalVersionAttribute"/>
|
||||
/// as the application's version.
|
||||
/// </summary>
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||
public static IConfigurator UseAssemblyInformationalVersion(this IConfigurator configurator)
|
||||
{
|
||||
if (configurator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
configurator.Settings.ApplicationVersion =
|
||||
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
|
||||
|
||||
return configurator;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hides the <c>DEFAULT</c> column that lists default values coming from the
|
||||
/// <see cref="DefaultValueAttribute"/> in the options help text.
|
||||
@@ -229,7 +248,7 @@ public static class ConfiguratorExtensions
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
}
|
||||
|
||||
configurator.Settings.Interceptor = interceptor;
|
||||
configurator.Settings.Registrar.RegisterInstance<ICommandInterceptor>(interceptor);
|
||||
return configurator;
|
||||
}
|
||||
|
||||
@@ -324,11 +343,16 @@ public static class ConfiguratorExtensions
|
||||
/// <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 AddDelegate<TSettings>(
|
||||
this IConfigurator<TSettings> configurator,
|
||||
this IConfigurator<TSettings>? configurator,
|
||||
string name,
|
||||
Func<CommandContext, int> func)
|
||||
where TSettings : CommandSettings
|
||||
where TSettings : CommandSettings
|
||||
{
|
||||
if (typeof(TSettings).IsAbstract)
|
||||
{
|
||||
AddDelegate(configurator as IConfigurator<EmptyCommandSettings>, name, func);
|
||||
}
|
||||
|
||||
if (configurator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configurator));
|
||||
@@ -367,11 +391,11 @@ public static class ConfiguratorExtensions
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="exceptionHandler">The Action that handles the exception.</param>
|
||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Action<Exception> exceptionHandler)
|
||||
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Action<Exception, ITypeResolver?> exceptionHandler)
|
||||
{
|
||||
return configurator.SetExceptionHandler(ex =>
|
||||
return configurator.SetExceptionHandler((ex, resolver) =>
|
||||
{
|
||||
exceptionHandler(ex);
|
||||
exceptionHandler(ex, resolver);
|
||||
return -1;
|
||||
});
|
||||
}
|
||||
@@ -382,7 +406,7 @@ public static class ConfiguratorExtensions
|
||||
/// <param name="configurator">The configurator.</param>
|
||||
/// <param name="exceptionHandler">The Action that handles the exception.</param>
|
||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Func<Exception, int>? exceptionHandler)
|
||||
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Func<Exception, ITypeResolver?, int>? exceptionHandler)
|
||||
{
|
||||
if (configurator == null)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Spectre.Console.Cli.Resources;
|
||||
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
@@ -10,23 +8,32 @@ namespace Spectre.Console.Cli.Help;
|
||||
/// </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 readonly HelpProviderResources resources;
|
||||
private readonly HelpProviderStyle? helpStyles;
|
||||
|
||||
/// <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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to emit the markup styles, inline, when rendering the help text.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Useful for unit testing different styling of the same help text.
|
||||
/// </remarks>
|
||||
protected virtual bool RenderMarkupInline { get; } = false;
|
||||
|
||||
private sealed class HelpArgument
|
||||
{
|
||||
public string Name { get; }
|
||||
@@ -34,7 +41,7 @@ public class HelpProvider : IHelpProvider
|
||||
public bool Required { get; }
|
||||
public string? Description { get; }
|
||||
|
||||
public HelpArgument(string name, int position, bool required, string? description)
|
||||
private HelpArgument(string name, int position, bool required, string? description)
|
||||
{
|
||||
Name = name;
|
||||
Position = position;
|
||||
@@ -61,7 +68,7 @@ public class HelpProvider : IHelpProvider
|
||||
public string? Description { get; }
|
||||
public object? DefaultValue { get; }
|
||||
|
||||
public HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue)
|
||||
private HelpOption(string? @short, string? @long, string? @value, bool? valueIsOptional, string? description, object? defaultValue)
|
||||
{
|
||||
Short = @short;
|
||||
Long = @long;
|
||||
@@ -71,17 +78,27 @@ public class HelpProvider : IHelpProvider
|
||||
DefaultValue = defaultValue;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<HelpOption> Get(ICommandInfo? command, HelpProviderResources resources)
|
||||
public static IReadOnlyList<HelpOption> Get(
|
||||
ICommandModel model,
|
||||
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));
|
||||
var parameters = new List<HelpOption>
|
||||
{
|
||||
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?.Parent == null) && !(command?.IsBranch ?? false))
|
||||
{
|
||||
// Only show the version command if there is an
|
||||
// application version set.
|
||||
if (model.ApplicationVersion != null)
|
||||
{
|
||||
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
|
||||
}
|
||||
}
|
||||
|
||||
parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o =>
|
||||
@@ -92,55 +109,59 @@ public class HelpProvider : IHelpProvider
|
||||
?? 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;
|
||||
}
|
||||
|
||||
/// <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/>
|
||||
// Don't provide a default style if HelpProviderStyles is null,
|
||||
// as the user will have explicitly done this to output unstyled help text
|
||||
this.helpStyles = settings.HelpProviderStyles;
|
||||
|
||||
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));
|
||||
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(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>
|
||||
}
|
||||
|
||||
/// <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>
|
||||
{
|
||||
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)
|
||||
@@ -148,30 +169,30 @@ public class HelpProvider : IHelpProvider
|
||||
yield break;
|
||||
}
|
||||
|
||||
var composer = new Composer();
|
||||
composer.Style("yellow", $"{resources.Description}:").LineBreak();
|
||||
var composer = NewComposer();
|
||||
composer.Style(helpStyles?.Description?.Header ?? Style.Plain, $"{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>
|
||||
}
|
||||
|
||||
/// <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();
|
||||
var composer = NewComposer();
|
||||
composer.Style(helpStyles?.Usage?.Header ?? Style.Plain, $"{resources.Usage}:").LineBreak();
|
||||
composer.Tab().Text(model.ApplicationName);
|
||||
|
||||
var parameters = new List<string>();
|
||||
var parameters = new List<Composer>();
|
||||
|
||||
if (command == null)
|
||||
{
|
||||
parameters.Add($"[grey][[{resources.Options}]][/]");
|
||||
parameters.Add($"[aqua]<{resources.Command}>[/]");
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]"));
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.Command}>"));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -183,11 +204,11 @@ public class HelpProvider : IHelpProvider
|
||||
{
|
||||
if (isCurrent)
|
||||
{
|
||||
parameters.Add($"[underline]{current.Name.EscapeMarkup()}[/]");
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.CurrentCommand ?? Style.Plain, $"{current.Name}"));
|
||||
}
|
||||
else
|
||||
{
|
||||
parameters.Add($"{current.Name.EscapeMarkup()}");
|
||||
parameters.Add(NewComposer().Text(current.Name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,7 +219,7 @@ public class HelpProvider : IHelpProvider
|
||||
foreach (var argument in current.Parameters.OfType<ICommandArgument>()
|
||||
.Where(a => a.Required).OrderBy(a => a.Position).ToArray())
|
||||
{
|
||||
parameters.Add($"[aqua]<{argument.Value.EscapeMarkup()}>[/]");
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.RequiredArgument ?? Style.Plain, $"<{argument.Value}>"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -207,61 +228,58 @@ public class HelpProvider : IHelpProvider
|
||||
{
|
||||
foreach (var optionalArgument in optionalArguments)
|
||||
{
|
||||
parameters.Add($"[silver][[{optionalArgument.Value.EscapeMarkup()}]][/]");
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.OptionalArgument ?? Style.Plain, $"[{optionalArgument.Value}]"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isCurrent)
|
||||
{
|
||||
parameters.Add($"[grey][[{resources.Options}]][/]");
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]"));
|
||||
}
|
||||
}
|
||||
|
||||
if (command.IsBranch && command.DefaultCommand == null)
|
||||
{
|
||||
{
|
||||
// The user must specify the command
|
||||
parameters.Add($"[aqua]<{resources.Command}>[/]");
|
||||
}
|
||||
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
|
||||
{
|
||||
// We are on a branch with a default command
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.Command}>"));
|
||||
}
|
||||
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
|
||||
{
|
||||
// We are on a branch with a default command
|
||||
// The user can optionally specify the command
|
||||
parameters.Add($"[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}]][/]");
|
||||
}
|
||||
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{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(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{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)
|
||||
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;
|
||||
|
||||
@@ -272,12 +290,12 @@ public class HelpProvider : IHelpProvider
|
||||
// make sure that we limit the number of examples.
|
||||
maxExamples = MaximumIndirectExamples;
|
||||
|
||||
// Start at the current command (if exists)
|
||||
// or alternatively commence at the model.
|
||||
// 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.
|
||||
// Traverse the command tree and look for examples.
|
||||
// As soon as a node contains commands, bail.
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
@@ -302,14 +320,14 @@ public class HelpProvider : IHelpProvider
|
||||
|
||||
if (Math.Min(maxExamples, examples.Count) > 0)
|
||||
{
|
||||
var composer = new Composer();
|
||||
var composer = NewComposer();
|
||||
composer.LineBreak();
|
||||
composer.Style("yellow", $"{resources.Examples}:").LineBreak();
|
||||
composer.Style(helpStyles?.Examples?.Header ?? Style.Plain, $"{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.Tab().Text(model.ApplicationName).Space().Style(helpStyles?.Examples?.Arguments ?? Style.Plain, args);
|
||||
composer.LineBreak();
|
||||
}
|
||||
|
||||
@@ -317,14 +335,14 @@ public class HelpProvider : IHelpProvider
|
||||
}
|
||||
|
||||
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>
|
||||
}
|
||||
|
||||
/// <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);
|
||||
@@ -334,11 +352,9 @@ public class HelpProvider : IHelpProvider
|
||||
}
|
||||
|
||||
var result = new List<IRenderable>
|
||||
{
|
||||
new Markup(Environment.NewLine),
|
||||
new Markup($"[yellow]{resources.Arguments}:[/]"),
|
||||
new Markup(Environment.NewLine),
|
||||
};
|
||||
{
|
||||
NewComposer().LineBreak().Style(helpStyles?.Arguments?.Header ?? Style.Plain, $"{resources.Arguments}:").LineBreak(),
|
||||
};
|
||||
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
|
||||
@@ -347,43 +363,41 @@ public class HelpProvider : IHelpProvider
|
||||
foreach (var argument in arguments.Where(x => x.Required).OrderBy(x => x.Position))
|
||||
{
|
||||
grid.AddRow(
|
||||
$"[silver]<{argument.Name.EscapeMarkup()}>[/]",
|
||||
argument.Description?.TrimEnd('.') ?? " ");
|
||||
NewComposer().Style(helpStyles?.Arguments?.RequiredArgument ?? Style.Plain, $"<{argument.Name}>"),
|
||||
NewComposer().Text(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('.') ?? " ");
|
||||
NewComposer().Style(helpStyles?.Arguments?.OptionalArgument ?? Style.Plain, $"[{argument.Name}]"),
|
||||
NewComposer().Text(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>
|
||||
}
|
||||
|
||||
/// <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);
|
||||
var parameters = HelpOption.Get(model, 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),
|
||||
};
|
||||
{
|
||||
NewComposer().LineBreak().Style(helpStyles?.Options?.Header ?? Style.Plain, $"{resources.Options}:").LineBreak(),
|
||||
};
|
||||
|
||||
var helpOptions = parameters.ToArray();
|
||||
var defaultValueColumn = ShowOptionDefaultValues && helpOptions.Any(e => e.DefaultValue != null);
|
||||
@@ -397,71 +411,24 @@ public class HelpProvider : IHelpProvider
|
||||
|
||||
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}[/]", " ");
|
||||
grid.AddRow(
|
||||
NewComposer().Space(),
|
||||
NewComposer().Style(helpStyles?.Options?.DefaultValueHeader ?? Style.Plain, resources.Default),
|
||||
NewComposer().Space());
|
||||
}
|
||||
|
||||
foreach (var option in helpOptions)
|
||||
{
|
||||
var columns = new List<string> { GetOptionParts(option) };
|
||||
var columns = new List<IRenderable>() { 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(GetDefaultValueForOption(option.DefaultValue));
|
||||
}
|
||||
|
||||
columns.Add(option.Description?.TrimEnd('.') ?? " ");
|
||||
columns.Add(NewComposer().Text(option.Description?.TrimEnd('.') ?? " "));
|
||||
|
||||
grid.AddRow(columns.ToArray());
|
||||
}
|
||||
@@ -469,18 +436,18 @@ public class HelpProvider : IHelpProvider
|
||||
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>
|
||||
}
|
||||
|
||||
/// <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 commandContainer = command ?? (ICommandContainer)model;
|
||||
bool isDefaultCommand = command?.IsDefaultCommand ?? false;
|
||||
|
||||
var commands = isDefaultCommand ? model.Commands : commandContainer.Commands;
|
||||
commands = commands.Where(x => !x.IsHidden).ToList();
|
||||
@@ -491,11 +458,9 @@ public class HelpProvider : IHelpProvider
|
||||
}
|
||||
|
||||
var result = new List<IRenderable>
|
||||
{
|
||||
new Markup(Environment.NewLine),
|
||||
new Markup($"[yellow]{resources.Commands}:[/]"),
|
||||
new Markup(Environment.NewLine),
|
||||
};
|
||||
{
|
||||
NewComposer().LineBreak().Style(helpStyles?.Commands?.Header ?? Style.Plain, $"{resources.Commands}:").LineBreak(),
|
||||
};
|
||||
|
||||
var grid = new Grid();
|
||||
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
|
||||
@@ -503,43 +468,102 @@ public class HelpProvider : IHelpProvider
|
||||
|
||||
foreach (var child in commands)
|
||||
{
|
||||
var arguments = new Composer();
|
||||
arguments.Style("silver", child.Name.EscapeMarkup());
|
||||
var arguments = NewComposer();
|
||||
arguments.Style(helpStyles?.Commands?.ChildCommand ?? Style.Plain, child.Name);
|
||||
arguments.Space();
|
||||
|
||||
foreach (var argument in HelpArgument.Get(child).Where(a => a.Required))
|
||||
{
|
||||
arguments.Style("silver", $"<{argument.Name.EscapeMarkup()}>");
|
||||
arguments.Style(helpStyles?.Commands?.RequiredArgument ?? Style.Plain, $"<{argument.Name}>");
|
||||
arguments.Space();
|
||||
}
|
||||
|
||||
if (TrimTrailingPeriod)
|
||||
{
|
||||
grid.AddRow(
|
||||
arguments.ToString().TrimEnd(),
|
||||
child.Description?.TrimEnd('.') ?? " ");
|
||||
NewComposer().Text(arguments.ToString().TrimEnd()),
|
||||
NewComposer().Text(child.Description?.TrimEnd('.') ?? " "));
|
||||
}
|
||||
else
|
||||
{
|
||||
grid.AddRow(
|
||||
arguments.ToString().TrimEnd(),
|
||||
child.Description ?? " ");
|
||||
NewComposer().Text(arguments.ToString().TrimEnd()),
|
||||
NewComposer().Text(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>
|
||||
}
|
||||
|
||||
/// <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;
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
private Composer NewComposer()
|
||||
{
|
||||
return new Composer(RenderMarkupInline);
|
||||
}
|
||||
|
||||
private IRenderable GetOptionParts(HelpOption option)
|
||||
{
|
||||
var composer = NewComposer();
|
||||
|
||||
if (option.Short != null)
|
||||
{
|
||||
composer.Text("-").Text(option.Short);
|
||||
if (option.Long != null)
|
||||
{
|
||||
composer.Text(", ");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
composer.Text(" ");
|
||||
if (option.Long != null)
|
||||
{
|
||||
composer.Text(" ");
|
||||
}
|
||||
}
|
||||
|
||||
if (option.Long != null)
|
||||
{
|
||||
composer.Text("--").Text(option.Long);
|
||||
}
|
||||
|
||||
if (option.Value != null)
|
||||
{
|
||||
composer.Text(" ");
|
||||
if (option.ValueIsOptional ?? false)
|
||||
{
|
||||
composer.Style(helpStyles?.Options?.OptionalOption ?? Style.Plain, $"[{option.Value}]");
|
||||
}
|
||||
else
|
||||
{
|
||||
composer.Style(helpStyles?.Options?.RequiredOption ?? Style.Plain, $"<{option.Value}>");
|
||||
}
|
||||
}
|
||||
|
||||
return composer;
|
||||
}
|
||||
|
||||
private Composer GetDefaultValueForOption(object? defaultValue)
|
||||
{
|
||||
return defaultValue switch
|
||||
{
|
||||
null => NewComposer().Text(" "),
|
||||
"" => NewComposer().Text(" "),
|
||||
Array { Length: 0 } => NewComposer().Text(" "),
|
||||
Array array => NewComposer().Join(", ", array.Cast<object>().Select(o => NewComposer().Style(helpStyles?.Options?.DefaultValue ?? Style.Plain, o.ToString() ?? string.Empty))),
|
||||
_ => NewComposer().Style(helpStyles?.Options?.DefaultValue ?? Style.Plain, defaultValue?.ToString() ?? string.Empty),
|
||||
};
|
||||
}
|
||||
}
|
||||
219
src/Spectre.Console.Cli/Help/HelpProviderStyles.cs
Normal file
219
src/Spectre.Console.Cli/Help/HelpProviderStyles.cs
Normal file
@@ -0,0 +1,219 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
/// <summary>
|
||||
/// Styles for the HelpProvider to use when rendering help text.
|
||||
/// </summary>
|
||||
public sealed class HelpProviderStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the style for describing the purpose or details of a command.
|
||||
/// </summary>
|
||||
public DescriptionStyle? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for specifying the usage format of a command.
|
||||
/// </summary>
|
||||
public UsageStyle? Usage { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for providing examples of command usage.
|
||||
/// </summary>
|
||||
public ExampleStyle? Examples { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for specifying arguments in a command.
|
||||
/// </summary>
|
||||
public ArgumentStyle? Arguments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for specifying options or flags in a command.
|
||||
/// </summary>
|
||||
public OptionStyle? Options { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for specifying subcommands or nested commands.
|
||||
/// </summary>
|
||||
public CommandStyle? Commands { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default HelpProvider styles.
|
||||
/// </summary>
|
||||
public static HelpProviderStyle Default { get; } =
|
||||
new HelpProviderStyle()
|
||||
{
|
||||
Description = new DescriptionStyle()
|
||||
{
|
||||
Header = "yellow",
|
||||
},
|
||||
Usage = new UsageStyle()
|
||||
{
|
||||
Header = "yellow",
|
||||
CurrentCommand = "underline",
|
||||
Command = "aqua",
|
||||
Options = "grey",
|
||||
RequiredArgument = "aqua",
|
||||
OptionalArgument = "silver",
|
||||
},
|
||||
Examples = new ExampleStyle()
|
||||
{
|
||||
Header = "yellow",
|
||||
Arguments = "grey",
|
||||
},
|
||||
Arguments = new ArgumentStyle()
|
||||
{
|
||||
Header = "yellow",
|
||||
RequiredArgument = "silver",
|
||||
OptionalArgument = "silver",
|
||||
},
|
||||
Commands = new CommandStyle()
|
||||
{
|
||||
Header = "yellow",
|
||||
ChildCommand = "silver",
|
||||
RequiredArgument = "silver",
|
||||
},
|
||||
Options = new OptionStyle()
|
||||
{
|
||||
Header = "yellow",
|
||||
DefaultValueHeader = "lime",
|
||||
DefaultValue = "bold",
|
||||
RequiredOption = "silver",
|
||||
OptionalOption = "grey",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines styles for describing the purpose or details of a command.
|
||||
/// </summary>
|
||||
public sealed class DescriptionStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the style for the header in the description.
|
||||
/// </summary>
|
||||
public Style? Header { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines styles for specifying the usage format of a command.
|
||||
/// </summary>
|
||||
public sealed class UsageStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the style for the header in the usage.
|
||||
/// </summary>
|
||||
public Style? Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for the current command in the usage.
|
||||
/// </summary>
|
||||
public Style? CurrentCommand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for general commands in the usage.
|
||||
/// </summary>
|
||||
public Style? Command { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for options in the usage.
|
||||
/// </summary>
|
||||
public Style? Options { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for required arguments in the usage.
|
||||
/// </summary>
|
||||
public Style? RequiredArgument { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for optional arguments in the usage.
|
||||
/// </summary>
|
||||
public Style? OptionalArgument { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines styles for providing examples of command usage.
|
||||
/// </summary>
|
||||
public sealed class ExampleStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the style for the header in the examples.
|
||||
/// </summary>
|
||||
public Style? Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for arguments in the examples.
|
||||
/// </summary>
|
||||
public Style? Arguments { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines styles for specifying arguments in a command.
|
||||
/// </summary>
|
||||
public sealed class ArgumentStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the style for the header in the arguments.
|
||||
/// </summary>
|
||||
public Style? Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for required arguments.
|
||||
/// </summary>
|
||||
public Style? RequiredArgument { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for optional arguments.
|
||||
/// </summary>
|
||||
public Style? OptionalArgument { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines styles for specifying subcommands or nested commands.
|
||||
/// </summary>
|
||||
public sealed class CommandStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the style for the header in the command section.
|
||||
/// </summary>
|
||||
public Style? Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for child commands in the command section.
|
||||
/// </summary>
|
||||
public Style? ChildCommand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for required arguments in the command section.
|
||||
/// </summary>
|
||||
public Style? RequiredArgument { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defines styles for specifying options or flags in a command.
|
||||
/// </summary>
|
||||
public sealed class OptionStyle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the style for the header in the options.
|
||||
/// </summary>
|
||||
public Style? Header { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for the header of default values in the options.
|
||||
/// </summary>
|
||||
public Style? DefaultValueHeader { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for default values in the options.
|
||||
/// </summary>
|
||||
public Style? DefaultValue { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for required options.
|
||||
/// </summary>
|
||||
public Style? RequiredOption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style for optional options.
|
||||
/// </summary>
|
||||
public Style? OptionalOption { get; set; }
|
||||
}
|
||||
@@ -4,7 +4,7 @@ namespace Spectre.Console.Cli.Help;
|
||||
/// Represents a command container.
|
||||
/// </summary>
|
||||
public interface ICommandContainer
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets all the examples for the container.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,42 +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; }
|
||||
/// <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; }
|
||||
}
|
||||
@@ -1,23 +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();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,9 @@ public interface ICommandModel : ICommandContainer
|
||||
/// Gets the name of the application.
|
||||
/// </summary>
|
||||
string ApplicationName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version of the application.
|
||||
/// </summary>
|
||||
string? ApplicationVersion { get; }
|
||||
}
|
||||
|
||||
@@ -1,27 +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; }
|
||||
/// <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; }
|
||||
}
|
||||
@@ -1,32 +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; }
|
||||
/// <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; }
|
||||
}
|
||||
@@ -1,20 +1,20 @@
|
||||
namespace Spectre.Console.Cli.Help;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The help provider interface for Spectre.Console.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations of this interface are responsbile
|
||||
/// for writing command help to the terminal when the
|
||||
/// `-h` or `--help` has been specified on the command line.
|
||||
/// The help provider interface for Spectre.Console.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Implementations of this interface are responsbile
|
||||
/// for writing command help to the terminal when the
|
||||
/// `-h` or `--help` has been specified on the command line.
|
||||
/// </remarks>
|
||||
public interface IHelpProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes help information for the application.
|
||||
/// </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 representing the help information.</returns>
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes help information for the application.
|
||||
/// </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 representing the help information.</returns>
|
||||
IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command);
|
||||
}
|
||||
|
||||
@@ -24,22 +24,27 @@ public interface ICommandAppSettings
|
||||
/// <summary>
|
||||
/// Gets or sets the application version (use it to override auto-detected value).
|
||||
/// </summary>
|
||||
string? ApplicationVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating how many examples from direct children to show in the help text.
|
||||
/// </summary>
|
||||
int MaximumIndirectExamples { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether any default values for command options are shown in the help text.
|
||||
/// </summary>
|
||||
bool ShowOptionDefaultValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text.
|
||||
/// </summary>
|
||||
bool TrimTrailingPeriod { get; set; }
|
||||
string? ApplicationVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating how many examples from direct children to show in the help text.
|
||||
/// </summary>
|
||||
int MaximumIndirectExamples { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether any default values for command options are shown in the help text.
|
||||
/// </summary>
|
||||
bool ShowOptionDefaultValues { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text.
|
||||
/// </summary>
|
||||
bool TrimTrailingPeriod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the styles to used when rendering the help text.
|
||||
/// </summary>
|
||||
HelpProviderStyle? HelpProviderStyles { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IAnsiConsole"/>.
|
||||
@@ -50,6 +55,7 @@ public interface ICommandAppSettings
|
||||
/// Gets or sets the <see cref="ICommandInterceptor"/> used
|
||||
/// to intercept settings before it's being sent to the command.
|
||||
/// </summary>
|
||||
[Obsolete("Register the interceptor with the ITypeRegistrar.")]
|
||||
ICommandInterceptor? Interceptor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -65,14 +71,14 @@ public interface ICommandAppSettings
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not parsing is strict.
|
||||
/// </summary>
|
||||
bool StrictParsing { get; set; }
|
||||
|
||||
bool StrictParsing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether or not flags found on the commnd line
|
||||
/// that would normally result in a <see cref="CommandParseException"/> being thrown
|
||||
/// during parsing with the message "Flags cannot be assigned a value."
|
||||
/// Gets or sets a value indicating whether or not flags found on the commnd line
|
||||
/// that would normally result in a <see cref="CommandParseException"/> being thrown
|
||||
/// during parsing with the message "Flags cannot be assigned a value."
|
||||
/// should instead be added to the remaining arguments collection.
|
||||
/// </summary>
|
||||
/// </summary>
|
||||
bool ConvertFlagsToRemainingArguments { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -90,6 +96,8 @@ public interface ICommandAppSettings
|
||||
/// <summary>
|
||||
/// Gets or sets a handler for Exceptions.
|
||||
/// <para>This handler will not be called, if <see cref="PropagateExceptions"/> is set to <c>true</c>.</para>
|
||||
/// The <see cref="ITypeResolver"/> argument will only be not-null, when the exception occurs during execution of
|
||||
/// a command. I.e. only when the resolver is available.
|
||||
/// </summary>
|
||||
public Func<Exception, int>? ExceptionHandler { get; set; }
|
||||
public Func<Exception, ITypeResolver?, int>? ExceptionHandler { get; set; }
|
||||
}
|
||||
@@ -12,5 +12,25 @@ public interface ICommandInterceptor
|
||||
/// </summary>
|
||||
/// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
|
||||
/// <param name="settings">The intercepted <see cref="CommandSettings"/>.</param>
|
||||
void Intercept(CommandContext context, CommandSettings settings);
|
||||
void Intercept(CommandContext context, CommandSettings settings)
|
||||
#if NETSTANDARD2_0
|
||||
;
|
||||
#else
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Intercepts a command result before it's passed as the result.
|
||||
/// </summary>
|
||||
/// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
|
||||
/// <param name="settings">The intercepted <see cref="CommandSettings"/>.</param>
|
||||
/// <param name="result">The result from the command execution.</param>
|
||||
void InterceptResult(CommandContext context, CommandSettings settings, ref int result)
|
||||
#if NETSTANDARD2_0
|
||||
;
|
||||
#else
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -4,19 +4,19 @@ namespace Spectre.Console.Cli;
|
||||
/// Represents a configurator.
|
||||
/// </summary>
|
||||
public interface IConfigurator
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <param name="helpProvider">The help provider to use.</param>
|
||||
public void SetHelpProvider(IHelpProvider helpProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
|
||||
public void SetHelpProvider<T>()
|
||||
where T : IHelpProvider;
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <param name="helpProvider">The help provider to use.</param>
|
||||
public void SetHelpProvider(IHelpProvider helpProvider);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the help provider for the application.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
|
||||
public void SetHelpProvider<T>()
|
||||
where T : IHelpProvider;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command app settings.
|
||||
@@ -66,5 +66,5 @@ public interface IConfigurator
|
||||
/// <param name="action">The command branch configurator.</param>
|
||||
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||
IBranchConfigurator AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
|
||||
where TSettings : CommandSettings;
|
||||
where TSettings : CommandSettings;
|
||||
}
|
||||
@@ -12,6 +12,7 @@ public interface IRemainingArguments
|
||||
|
||||
/// <summary>
|
||||
/// Gets the raw, non-parsed remaining arguments.
|
||||
/// This is normally everything after the `--` delimiter.
|
||||
/// </summary>
|
||||
IReadOnlyList<string> Raw { get; }
|
||||
}
|
||||
@@ -1,146 +1,179 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandExecutor
|
||||
{
|
||||
private readonly ITypeRegistrar _registrar;
|
||||
|
||||
public CommandExecutor(ITypeRegistrar registrar)
|
||||
{
|
||||
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
|
||||
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
|
||||
}
|
||||
|
||||
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
|
||||
{
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
args ??= new List<string>();
|
||||
|
||||
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
||||
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
|
||||
|
||||
// Create the command model.
|
||||
var model = CommandModelBuilder.Build(configuration);
|
||||
_registrar.RegisterInstance(typeof(CommandModel), model);
|
||||
_registrar.RegisterDependencies(model);
|
||||
|
||||
// Asking for version? Kind of a hack, but it's alright.
|
||||
// We should probably make this a bit better in the future.
|
||||
if (args.Contains("-v") || args.Contains("--version"))
|
||||
{
|
||||
var console = configuration.Settings.Console.GetConsole();
|
||||
console.WriteLine(ResolveApplicationVersion(configuration));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Parse and map the model against the arguments.
|
||||
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
|
||||
|
||||
// Register the arguments with the container.
|
||||
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
||||
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
||||
|
||||
// Create the resolver.
|
||||
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
|
||||
{
|
||||
// Get the registered help provider, falling back to the default provider
|
||||
// if no custom implementations have been registered.
|
||||
var helpProviders = resolver.Resolve(typeof(IEnumerable<IHelpProvider>)) as IEnumerable<IHelpProvider>;
|
||||
var helpProvider = helpProviders?.LastOrDefault() ?? new HelpProvider(configuration.Settings);
|
||||
|
||||
// Currently the root?
|
||||
if (parsedResult?.Tree == null)
|
||||
{
|
||||
// Display help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the command to execute.
|
||||
var leaf = parsedResult.Tree.GetLeafCommand();
|
||||
if (leaf.Command.IsBranch || leaf.ShowHelp)
|
||||
{
|
||||
// Branches can't be executed. Show help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return leaf.ShowHelp ? 0 : 1;
|
||||
}
|
||||
|
||||
// Is this the default and is it called without arguments when there are required arguments?
|
||||
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
|
||||
{
|
||||
// Display help for default command.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the content.
|
||||
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
|
||||
|
||||
// Execute the command tree.
|
||||
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable CS8603 // Possible null reference return.
|
||||
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
|
||||
{
|
||||
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
|
||||
|
||||
var parserContext = new CommandTreeParserContext(args, settings.ParsingMode);
|
||||
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||
var parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||
|
||||
var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
|
||||
var lastParsedCommand = lastParsedLeaf?.Command;
|
||||
if (lastParsedLeaf != null && lastParsedCommand != null &&
|
||||
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
|
||||
lastParsedCommand.DefaultCommand != null)
|
||||
{
|
||||
// Insert this branch's default command into the command line
|
||||
// arguments and try again to see if it will parse.
|
||||
var argsWithDefaultCommand = new List<string>(args);
|
||||
|
||||
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
|
||||
|
||||
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
|
||||
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
|
||||
parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||
}
|
||||
|
||||
return parsedResult;
|
||||
}
|
||||
#pragma warning restore CS8603 // Possible null reference return.
|
||||
|
||||
private static string ResolveApplicationVersion(IConfiguration configuration)
|
||||
{
|
||||
return
|
||||
configuration.Settings.ApplicationVersion ?? // potential override
|
||||
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
|
||||
}
|
||||
|
||||
private static Task<int> Execute(
|
||||
CommandTree leaf,
|
||||
CommandTree tree,
|
||||
CommandContext context,
|
||||
ITypeResolver resolver,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
// Bind the command tree against the settings.
|
||||
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
|
||||
configuration.Settings.Interceptor?.Intercept(context, settings);
|
||||
|
||||
// Create and validate the command.
|
||||
var command = leaf.CreateCommand(resolver);
|
||||
var validationResult = command.Validate(context, settings);
|
||||
if (!validationResult.Successful)
|
||||
{
|
||||
throw CommandRuntimeException.ValidationFailed(validationResult);
|
||||
}
|
||||
|
||||
// Execute the command.
|
||||
return command.Execute(context, settings);
|
||||
}
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class CommandExecutor
|
||||
{
|
||||
private readonly ITypeRegistrar _registrar;
|
||||
|
||||
public CommandExecutor(ITypeRegistrar registrar)
|
||||
{
|
||||
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
|
||||
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
|
||||
}
|
||||
|
||||
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
|
||||
{
|
||||
if (configuration == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
var arguments = args.ToSafeReadOnlyList();
|
||||
|
||||
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
||||
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
|
||||
|
||||
// Create the command model.
|
||||
var model = CommandModelBuilder.Build(configuration);
|
||||
_registrar.RegisterInstance(typeof(CommandModel), model);
|
||||
_registrar.RegisterDependencies(model);
|
||||
|
||||
// No default command?
|
||||
if (model.DefaultCommand == null)
|
||||
{
|
||||
// Got at least one argument?
|
||||
var firstArgument = arguments.FirstOrDefault();
|
||||
if (firstArgument != null)
|
||||
{
|
||||
// Asking for version? Kind of a hack, but it's alright.
|
||||
// We should probably make this a bit better in the future.
|
||||
if (firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase) ||
|
||||
firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (configuration.Settings.ApplicationVersion != null)
|
||||
{
|
||||
var console = configuration.Settings.Console.GetConsole();
|
||||
console.MarkupLine(configuration.Settings.ApplicationVersion);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse and map the model against the arguments.
|
||||
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, arguments);
|
||||
|
||||
// Register the arguments with the container.
|
||||
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
||||
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
||||
|
||||
// Create the resolver.
|
||||
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
|
||||
{
|
||||
// Get the registered help provider, falling back to the default provider
|
||||
// if no custom implementations have been registered.
|
||||
var helpProviders = resolver.Resolve(typeof(IEnumerable<IHelpProvider>)) as IEnumerable<IHelpProvider>;
|
||||
var helpProvider = helpProviders?.LastOrDefault() ?? new HelpProvider(configuration.Settings);
|
||||
|
||||
// Currently the root?
|
||||
if (parsedResult?.Tree == null)
|
||||
{
|
||||
// Display help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get the command to execute.
|
||||
var leaf = parsedResult.Tree.GetLeafCommand();
|
||||
if (leaf.Command.IsBranch || leaf.ShowHelp)
|
||||
{
|
||||
// Branches can't be executed. Show help.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return leaf.ShowHelp ? 0 : 1;
|
||||
}
|
||||
|
||||
// Is this the default and is it called without arguments when there are required arguments?
|
||||
if (leaf.Command.IsDefaultCommand && arguments.Count == 0 && leaf.Command.Parameters.Any(p => p.Required))
|
||||
{
|
||||
// Display help for default command.
|
||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Create the content.
|
||||
var context = new CommandContext(
|
||||
arguments,
|
||||
parsedResult.Remaining,
|
||||
leaf.Command.Name,
|
||||
leaf.Command.Data);
|
||||
|
||||
// Execute the command tree.
|
||||
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IReadOnlyList<string> args)
|
||||
{
|
||||
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
|
||||
|
||||
var parserContext = new CommandTreeParserContext(args, settings.ParsingMode);
|
||||
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||
var parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||
|
||||
var lastParsedLeaf = parsedResult.Tree?.GetLeafCommand();
|
||||
var lastParsedCommand = lastParsedLeaf?.Command;
|
||||
if (lastParsedLeaf != null && lastParsedCommand != null &&
|
||||
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
|
||||
lastParsedCommand.DefaultCommand != null)
|
||||
{
|
||||
// Insert this branch's default command into the command line
|
||||
// arguments and try again to see if it will parse.
|
||||
var argsWithDefaultCommand = new List<string>(args);
|
||||
|
||||
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
|
||||
|
||||
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
|
||||
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
|
||||
parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||
}
|
||||
|
||||
return parsedResult;
|
||||
}
|
||||
|
||||
private static async Task<int> Execute(
|
||||
CommandTree leaf,
|
||||
CommandTree tree,
|
||||
CommandContext context,
|
||||
ITypeResolver resolver,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Bind the command tree against the settings.
|
||||
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
|
||||
var interceptors =
|
||||
((IEnumerable<ICommandInterceptor>?)resolver.Resolve(typeof(IEnumerable<ICommandInterceptor>))
|
||||
?? Array.Empty<ICommandInterceptor>()).ToList();
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (configuration.Settings.Interceptor != null)
|
||||
{
|
||||
interceptors.Add(configuration.Settings.Interceptor);
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
foreach (var interceptor in interceptors)
|
||||
{
|
||||
interceptor.Intercept(context, settings);
|
||||
}
|
||||
|
||||
// Create and validate the command.
|
||||
var command = leaf.CreateCommand(resolver);
|
||||
var validationResult = command.Validate(context, settings);
|
||||
if (!validationResult.Successful)
|
||||
{
|
||||
throw CommandRuntimeException.ValidationFailed(validationResult);
|
||||
}
|
||||
|
||||
// Execute the command.
|
||||
var result = await command.Execute(context, settings);
|
||||
foreach (var interceptor in interceptors)
|
||||
{
|
||||
interceptor.InterceptResult(context, settings, ref result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex) when (configuration.Settings is { ExceptionHandler: not null, PropagateExceptions: false })
|
||||
{
|
||||
return configuration.Settings.ExceptionHandler(ex, resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,13 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
|
||||
|
||||
node.SetNullableAttribute("Settings", command.SettingsType?.FullName);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(command.Description))
|
||||
{
|
||||
var descriptionNode = doc.CreateElement("Description");
|
||||
descriptionNode.InnerText = command.Description;
|
||||
node.AppendChild(descriptionNode);
|
||||
}
|
||||
|
||||
// Parameters
|
||||
if (command.Parameters.Count > 0)
|
||||
{
|
||||
@@ -103,6 +110,27 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
|
||||
node.AppendChild(CreateCommandNode(doc, childCommand));
|
||||
}
|
||||
|
||||
// Examples
|
||||
if (command.Examples.Count > 0)
|
||||
{
|
||||
var exampleRootNode = doc.CreateElement("Examples");
|
||||
foreach (var example in command.Examples.SelectMany(static x => x))
|
||||
{
|
||||
var exampleNode = CreateExampleNode(doc, example);
|
||||
exampleRootNode.AppendChild(exampleNode);
|
||||
}
|
||||
|
||||
node.AppendChild(exampleRootNode);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static XmlNode CreateExampleNode(XmlDocument document, string example)
|
||||
{
|
||||
var node = document.CreateElement("Example");
|
||||
node.SetAttribute("commandLine", example);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,22 +4,43 @@ internal sealed class Composer : IRenderable
|
||||
{
|
||||
private readonly StringBuilder _content;
|
||||
|
||||
/// <summary>
|
||||
/// Whether to emit the markup styles, inline, when rendering the content.
|
||||
/// </summary>
|
||||
private readonly bool _renderMarkup = false;
|
||||
|
||||
public Composer()
|
||||
{
|
||||
_content = new StringBuilder();
|
||||
}
|
||||
|
||||
public Composer(bool renderMarkup)
|
||||
: this()
|
||||
{
|
||||
_renderMarkup = renderMarkup;
|
||||
}
|
||||
|
||||
public Composer Text(string text)
|
||||
{
|
||||
_content.Append(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Composer Style(Style style, string text)
|
||||
{
|
||||
_content.Append('[').Append(style.ToMarkup()).Append(']');
|
||||
_content.Append(text.EscapeMarkup());
|
||||
_content.Append("[/]");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Composer Style(string style, string text)
|
||||
{
|
||||
_content.Append('[').Append(style).Append(']');
|
||||
_content.Append(text.EscapeMarkup());
|
||||
_content.Append("[/]");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -28,6 +49,7 @@ internal sealed class Composer : IRenderable
|
||||
_content.Append('[').Append(style).Append(']');
|
||||
action(this);
|
||||
_content.Append("[/]");
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -72,12 +94,19 @@ internal sealed class Composer : IRenderable
|
||||
return this;
|
||||
}
|
||||
|
||||
public Composer Join(string separator, IEnumerable<string> composers)
|
||||
public Composer Join(string separator, IEnumerable<Composer> composers)
|
||||
{
|
||||
if (composers != null)
|
||||
{
|
||||
Space();
|
||||
Text(string.Join(separator, composers));
|
||||
foreach (var composer in composers)
|
||||
{
|
||||
if (_content.ToString().Length > 0)
|
||||
{
|
||||
Text(separator);
|
||||
}
|
||||
|
||||
Text(composer.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
@@ -85,12 +114,26 @@ internal sealed class Composer : IRenderable
|
||||
|
||||
public Measurement Measure(RenderOptions options, int maxWidth)
|
||||
{
|
||||
return ((IRenderable)new Markup(_content.ToString())).Measure(options, maxWidth);
|
||||
if (_renderMarkup)
|
||||
{
|
||||
return ((IRenderable)new Paragraph(_content.ToString())).Measure(options, maxWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((IRenderable)new Markup(_content.ToString())).Measure(options, maxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
|
||||
{
|
||||
return ((IRenderable)new Markup(_content.ToString())).Render(options, maxWidth);
|
||||
if (_renderMarkup)
|
||||
{
|
||||
return ((IRenderable)new Paragraph(_content.ToString())).Render(options, maxWidth);
|
||||
}
|
||||
else
|
||||
{
|
||||
return ((IRenderable)new Markup(_content.ToString())).Render(options, maxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal sealed class BranchConfigurator : IBranchConfigurator
|
||||
{
|
||||
|
||||
@@ -5,29 +5,34 @@ internal sealed class CommandAppSettings : ICommandAppSettings
|
||||
public CultureInfo? Culture { get; set; }
|
||||
public string? ApplicationName { get; set; }
|
||||
public string? ApplicationVersion { get; set; }
|
||||
public int MaximumIndirectExamples { get; set; }
|
||||
public bool ShowOptionDefaultValues { get; set; }
|
||||
public int MaximumIndirectExamples { get; set; }
|
||||
public bool ShowOptionDefaultValues { get; set; }
|
||||
public IAnsiConsole? Console { get; set; }
|
||||
[Obsolete("Register the interceptor with the ITypeRegistrar.")]
|
||||
public ICommandInterceptor? Interceptor { get; set; }
|
||||
public ITypeRegistrarFrontend Registrar { get; set; }
|
||||
public CaseSensitivity CaseSensitivity { get; set; }
|
||||
public bool PropagateExceptions { get; set; }
|
||||
public bool ValidateExamples { get; set; }
|
||||
public bool TrimTrailingPeriod { get; set; } = true;
|
||||
public bool StrictParsing { get; set; }
|
||||
public bool ConvertFlagsToRemainingArguments { get; set; } = false;
|
||||
public bool TrimTrailingPeriod { get; set; }
|
||||
public HelpProviderStyle? HelpProviderStyles { get; set; }
|
||||
public bool StrictParsing { get; set; }
|
||||
public bool ConvertFlagsToRemainingArguments { get; set; }
|
||||
|
||||
public ParsingMode ParsingMode =>
|
||||
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
|
||||
|
||||
public Func<Exception, int>? ExceptionHandler { get; set; }
|
||||
public Func<Exception, ITypeResolver?, int>? ExceptionHandler { get; set; }
|
||||
|
||||
public CommandAppSettings(ITypeRegistrar registrar)
|
||||
{
|
||||
Registrar = new TypeRegistrar(registrar);
|
||||
CaseSensitivity = CaseSensitivity.All;
|
||||
ShowOptionDefaultValues = true;
|
||||
ShowOptionDefaultValues = true;
|
||||
MaximumIndirectExamples = 5;
|
||||
TrimTrailingPeriod = true;
|
||||
HelpProviderStyles = HelpProviderStyle.Default;
|
||||
ConvertFlagsToRemainingArguments = false;
|
||||
}
|
||||
|
||||
public bool IsTrue(Func<CommandAppSettings, bool> func, string environmentVariableName)
|
||||
|
||||
@@ -19,19 +19,19 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
|
||||
Settings = new CommandAppSettings(registrar);
|
||||
Examples = new List<string[]>();
|
||||
}
|
||||
|
||||
public void SetHelpProvider(IHelpProvider helpProvider)
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
|
||||
}
|
||||
|
||||
public void SetHelpProvider<T>()
|
||||
where T : IHelpProvider
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.Register(typeof(IHelpProvider), typeof(T));
|
||||
}
|
||||
|
||||
public void SetHelpProvider(IHelpProvider helpProvider)
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
|
||||
}
|
||||
|
||||
public void SetHelpProvider<T>()
|
||||
where T : IHelpProvider
|
||||
{
|
||||
// Register the help provider
|
||||
_registrar.Register(typeof(IHelpProvider), typeof(T));
|
||||
}
|
||||
|
||||
public void AddExample(params string[] args)
|
||||
{
|
||||
|
||||
@@ -124,7 +124,7 @@ internal static class TemplateParser
|
||||
foreach (var character in token.Value)
|
||||
{
|
||||
if (!char.IsLetterOrDigit(character) &&
|
||||
character != '=' && character != '-' && character != '_')
|
||||
character != '=' && character != '-' && character != '_' && character != '|')
|
||||
{
|
||||
throw CommandTemplateException.InvalidCharacterInValueName(template, token, character);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal static class EnumerableExtensions
|
||||
{
|
||||
public static IReadOnlyList<T> ToSafeReadOnlyList<T>(this IEnumerable<T> source)
|
||||
{
|
||||
return source switch
|
||||
{
|
||||
null => new List<T>(),
|
||||
IReadOnlyList<T> list => list,
|
||||
_ => source.ToList(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,12 @@ internal static class TypeRegistrarExtensions
|
||||
throw new InvalidOperationException("Command setting type cannot be null.");
|
||||
}
|
||||
|
||||
if (command.SettingsType is { IsAbstract: false, IsClass: true })
|
||||
{
|
||||
// Register the settings type
|
||||
registrar?.Register(command.SettingsType, command.SettingsType);
|
||||
}
|
||||
|
||||
if (command.CommandType != null)
|
||||
{
|
||||
registrar?.Register(command.CommandType, command.CommandType);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
|
||||
internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
||||
{
|
||||
public string Name { get; }
|
||||
@@ -20,14 +20,14 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
||||
|
||||
// only branches can have a default command
|
||||
public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null;
|
||||
public bool IsHidden { get; }
|
||||
|
||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList();
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList();
|
||||
ICommandInfo? ICommandInfo.Parent => Parent;
|
||||
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||
|
||||
public bool IsHidden { get; }
|
||||
|
||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList();
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList();
|
||||
ICommandInfo? ICommandInfo.Parent => Parent;
|
||||
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||
|
||||
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
|
||||
{
|
||||
Parent = parent;
|
||||
@@ -54,5 +54,5 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
||||
Description = description.Description;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,18 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
|
||||
internal sealed class CommandModel : ICommandContainer, ICommandModel
|
||||
{
|
||||
public string? ApplicationName { get; }
|
||||
public string? ApplicationVersion { get; }
|
||||
public ParsingMode ParsingMode { get; }
|
||||
public IList<CommandInfo> Commands { get; }
|
||||
public IList<string[]> Examples { get; }
|
||||
|
||||
public IList<string[]> Examples { get; }
|
||||
|
||||
public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);
|
||||
|
||||
string ICommandModel.ApplicationName => GetApplicationName(ApplicationName);
|
||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Commands.Cast<ICommandInfo>().ToList();
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||
|
||||
public CommandModel(
|
||||
@@ -20,20 +21,21 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel
|
||||
IEnumerable<string[]> examples)
|
||||
{
|
||||
ApplicationName = settings.ApplicationName;
|
||||
ApplicationVersion = settings.ApplicationVersion;
|
||||
ParsingMode = settings.ParsingMode;
|
||||
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
|
||||
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the application.
|
||||
/// If the provided <paramref name="applicationName"/> is not null or empty,
|
||||
/// it is returned. Otherwise the name of the current application
|
||||
/// is determined based on the executable file's name.
|
||||
/// </summary>
|
||||
/// <param name="applicationName">The optional name of the application.</param>
|
||||
/// <returns>
|
||||
/// The name of the application, or a default value of "?" if no valid application name can be determined.
|
||||
Commands = new List<CommandInfo>(commands);
|
||||
Examples = new List<string[]>(examples);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the application.
|
||||
/// If the provided <paramref name="applicationName"/> is not null or empty,
|
||||
/// it is returned. Otherwise the name of the current application
|
||||
/// is determined based on the executable file's name.
|
||||
/// </summary>
|
||||
/// <param name="applicationName">The optional name of the application.</param>
|
||||
/// <returns>
|
||||
/// The name of the application, or a default value of "?" if no valid application name can be determined.
|
||||
/// </returns>
|
||||
private static string GetApplicationName(string? applicationName)
|
||||
{
|
||||
@@ -45,7 +47,7 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel
|
||||
|
||||
private static string? GetApplicationFile()
|
||||
{
|
||||
var location = Assembly.GetEntryAssembly()?.Location;
|
||||
var location = Assembly.GetEntryAssembly()?.Location;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(location))
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
|
||||
internal static class CommandModelBuilder
|
||||
{
|
||||
// Consider removing this in favor for value tuples at some point.
|
||||
@@ -31,8 +31,8 @@ internal static class CommandModelBuilder
|
||||
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
|
||||
|
||||
// Build the default command.
|
||||
var defaultCommand = Build(null, configuration.DefaultCommand);
|
||||
|
||||
var defaultCommand = Build(null, configuration.DefaultCommand);
|
||||
|
||||
result.Add(defaultCommand);
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ internal static class CommandModelBuilder
|
||||
foreach (var childCommand in command.Children)
|
||||
{
|
||||
var child = Build(info, childCommand);
|
||||
info.Children.Add(child);
|
||||
info.Children.Add(child);
|
||||
}
|
||||
|
||||
// Normalize argument positions.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
|
||||
internal abstract class CommandParameter : ICommandParameterInfo, ICommandParameter
|
||||
{
|
||||
public Guid Id { get; }
|
||||
@@ -17,10 +17,10 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
|
||||
public string PropertyName => Property.Name;
|
||||
|
||||
public virtual bool WantRawValue => ParameterType.IsPairDeconstructable()
|
||||
&& (PairDeconstructor != null || Converter == null);
|
||||
|
||||
public bool IsFlag => ParameterKind == ParameterKind.Flag;
|
||||
|
||||
&& (PairDeconstructor != null || Converter == null);
|
||||
|
||||
public bool IsFlag => ParameterKind == ParameterKind.Flag;
|
||||
|
||||
protected CommandParameter(
|
||||
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
|
||||
string? description, TypeConverterAttribute? converter,
|
||||
|
||||
@@ -8,13 +8,13 @@ internal interface ICommandContainer
|
||||
/// <summary>
|
||||
/// Gets all commands in the container.
|
||||
/// </summary>
|
||||
IList<CommandInfo> Commands { get; }
|
||||
|
||||
IList<CommandInfo> Commands { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default command for the container.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns null if a default command has not been set.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns null if a default command has not been set.
|
||||
/// </remarks>
|
||||
CommandInfo? DefaultCommand { get; }
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using static Spectre.Console.Cli.CommandTreeTokenizer;
|
||||
|
||||
using static Spectre.Console.Cli.CommandTreeTokenizer;
|
||||
|
||||
namespace Spectre.Console.Cli;
|
||||
|
||||
internal class CommandTreeParser
|
||||
{
|
||||
private readonly CommandModel _configuration;
|
||||
private readonly ParsingMode _parsingMode;
|
||||
private readonly CommandOptionAttribute _help;
|
||||
private readonly CommandOptionAttribute _help;
|
||||
private readonly bool _convertFlagsToRemainingArguments;
|
||||
|
||||
public CaseSensitivity CaseSensitivity { get; }
|
||||
@@ -21,20 +21,20 @@ internal class CommandTreeParser
|
||||
{
|
||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
_parsingMode = parsingMode ?? _configuration.ParsingMode;
|
||||
_help = new CommandOptionAttribute("-h|--help");
|
||||
_help = new CommandOptionAttribute("-h|--help");
|
||||
_convertFlagsToRemainingArguments = convertFlagsToRemainingArguments ?? false;
|
||||
|
||||
CaseSensitivity = caseSensitivity;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public CommandTreeParserResult Parse(IEnumerable<string> args)
|
||||
{
|
||||
var parserContext = new CommandTreeParserContext(args, _parsingMode);
|
||||
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||
|
||||
return Parse(parserContext, tokenizerResult);
|
||||
}
|
||||
|
||||
{
|
||||
var parserContext = new CommandTreeParserContext(args, _parsingMode);
|
||||
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||
|
||||
return Parse(parserContext, tokenizerResult);
|
||||
}
|
||||
|
||||
public CommandTreeParserResult Parse(CommandTreeParserContext context, CommandTreeTokenizerResult tokenizerResult)
|
||||
{
|
||||
var tokens = tokenizerResult.Tokens;
|
||||
@@ -258,8 +258,8 @@ internal class CommandTreeParser
|
||||
// Find the option.
|
||||
var option = node.FindOption(token.Value, isLongOption, CaseSensitivity);
|
||||
if (option != null)
|
||||
{
|
||||
ParseOptionValue(context, stream, token, node, option);
|
||||
{
|
||||
ParseOptionValue(context, stream, token, node, option);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -291,10 +291,10 @@ internal class CommandTreeParser
|
||||
CommandTreeParserContext context,
|
||||
CommandTreeTokenStream stream,
|
||||
CommandTreeToken token,
|
||||
CommandTree current,
|
||||
CommandTree current,
|
||||
CommandParameter? parameter = null)
|
||||
{
|
||||
bool addToMappedCommandParameters = parameter != null;
|
||||
{
|
||||
bool addToMappedCommandParameters = parameter != null;
|
||||
|
||||
var value = default(string);
|
||||
|
||||
@@ -302,59 +302,55 @@ internal class CommandTreeParser
|
||||
var valueToken = stream.Peek();
|
||||
if (valueToken?.TokenKind == CommandTreeToken.Kind.String)
|
||||
{
|
||||
var parseValue = true;
|
||||
if (token.TokenKind == CommandTreeToken.Kind.ShortOption && token.IsGrouped)
|
||||
{
|
||||
parseValue = false;
|
||||
}
|
||||
bool parseValue = token is not { TokenKind: CommandTreeToken.Kind.ShortOption, IsGrouped: true };
|
||||
|
||||
if (context.State == State.Normal && parseValue)
|
||||
{
|
||||
// Is this a command?
|
||||
if (current.Command.FindCommand(valueToken.Value, CaseSensitivity) == null)
|
||||
{
|
||||
if (parameter != null)
|
||||
{
|
||||
if (parameter.ParameterKind == ParameterKind.Flag)
|
||||
{
|
||||
if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!valueToken.HadSeparator)
|
||||
{
|
||||
// Do nothing
|
||||
// - assume valueToken is unrelated to the flag parameter (ie. we've parsed it unnecessarily)
|
||||
// - rely on the "No value?" code below to set the flag to its default value
|
||||
// - valueToken will be handled on the next pass of the parser
|
||||
}
|
||||
else
|
||||
{
|
||||
// Flags cannot be assigned a value.
|
||||
if (_convertFlagsToRemainingArguments)
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
|
||||
context.AddRemainingArgument(token.Value, value);
|
||||
|
||||
// Prevent the option and it's non-boolean value from being added to
|
||||
// mapped parameters (otherwise an exception will be thrown later
|
||||
// when binding the value to the flag in the comand settings)
|
||||
addToMappedCommandParameters = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
}
|
||||
{
|
||||
if (parameter != null)
|
||||
{
|
||||
if (parameter.ParameterKind == ParameterKind.Flag)
|
||||
{
|
||||
if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
if (!valueToken.HadSeparator)
|
||||
{
|
||||
// Do nothing
|
||||
// - assume valueToken is unrelated to the flag parameter (ie. we've parsed it unnecessarily)
|
||||
// - rely on the "No value?" code below to set the flag to its default value
|
||||
// - valueToken will be handled on the next pass of the parser
|
||||
}
|
||||
else
|
||||
{
|
||||
// Flags cannot be assigned a value.
|
||||
if (_convertFlagsToRemainingArguments)
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
|
||||
context.AddRemainingArgument(token.Representation, value);
|
||||
|
||||
// Prevent the option and it's non-boolean value from being added to
|
||||
// mapped parameters (otherwise an exception will be thrown later
|
||||
// when binding the value to the flag in the comand settings)
|
||||
addToMappedCommandParameters = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -364,22 +360,22 @@ internal class CommandTreeParser
|
||||
// In relaxed parsing mode?
|
||||
if (context.ParsingMode == ParsingMode.Relaxed)
|
||||
{
|
||||
context.AddRemainingArgument(token.Value, value);
|
||||
context.AddRemainingArgument(token.Representation, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null);
|
||||
{
|
||||
context.AddRemainingArgument(token.Representation, parseValue ? valueToken.Value : null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parameter == null && // Only add tokens which have not been matched to a command parameter
|
||||
if (parameter == null && // Only add tokens which have not been matched to a command parameter
|
||||
(context.State == State.Remaining || context.ParsingMode == ParsingMode.Relaxed))
|
||||
{
|
||||
context.AddRemainingArgument(token.Value, null);
|
||||
context.AddRemainingArgument(token.Representation, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,10 +395,10 @@ internal class CommandTreeParser
|
||||
if (parameter.IsFlagValue())
|
||||
{
|
||||
value = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -415,9 +411,9 @@ internal class CommandTreeParser
|
||||
}
|
||||
}
|
||||
|
||||
if (parameter != null && addToMappedCommandParameters)
|
||||
{
|
||||
current.Mapped.Add(new MappedCommandParameter(parameter, value));
|
||||
if (parameter != null && addToMappedCommandParameters)
|
||||
{
|
||||
current.Mapped.Add(new MappedCommandParameter(parameter, value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,15 +26,15 @@ internal class CommandTreeParserContext
|
||||
public void IncreaseArgumentPosition()
|
||||
{
|
||||
CurrentArgumentPosition++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void AddRemainingArgument(string key, string? value)
|
||||
{
|
||||
if (!_remaining.ContainsKey(key))
|
||||
{
|
||||
_remaining.Add(key, new List<string?>());
|
||||
}
|
||||
|
||||
{
|
||||
if (!_remaining.ContainsKey(key))
|
||||
{
|
||||
_remaining.Add(key, new List<string?>());
|
||||
}
|
||||
|
||||
_remaining[key].Add(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,11 +6,11 @@ internal sealed class CommandTreeToken
|
||||
public int Position { get; }
|
||||
public string Value { get; }
|
||||
public string Representation { get; }
|
||||
public bool IsGrouped { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>.
|
||||
/// </summary>
|
||||
public bool IsGrouped { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>.
|
||||
/// </summary>
|
||||
public bool HadSeparator { get; set; }
|
||||
|
||||
public enum Kind
|
||||
|
||||
@@ -5,7 +5,7 @@ internal sealed class CommandTreeTokenStream : IReadOnlyList<CommandTreeToken>
|
||||
private readonly List<CommandTreeToken> _tokens;
|
||||
private int _position;
|
||||
|
||||
public int Count => _tokens.Count;
|
||||
public int Count => _tokens.Count;
|
||||
public int Position => _position;
|
||||
|
||||
public CommandTreeToken this[int index] => _tokens[index];
|
||||
|
||||
@@ -29,13 +29,13 @@ internal static class CommandTreeTokenizer
|
||||
var context = new CommandTreeTokenizerContext();
|
||||
|
||||
foreach (var arg in args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(arg))
|
||||
{
|
||||
// Null strings in the args array are still represented as tokens
|
||||
tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty));
|
||||
continue;
|
||||
}
|
||||
{
|
||||
if (string.IsNullOrEmpty(arg))
|
||||
{
|
||||
// Null strings in the args array are still represented as tokens
|
||||
tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty));
|
||||
continue;
|
||||
}
|
||||
|
||||
var start = position;
|
||||
var reader = new TextBuffer(previousReader, arg);
|
||||
@@ -55,30 +55,30 @@ internal static class CommandTreeTokenizer
|
||||
}
|
||||
|
||||
private static int ParseToken(CommandTreeTokenizerContext context, TextBuffer reader, int position, int start, List<CommandTreeToken> tokens)
|
||||
{
|
||||
if (!reader.ReachedEnd && reader.Peek() == '-')
|
||||
{
|
||||
// Option
|
||||
tokens.AddRange(ScanOptions(context, reader));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Command or argument
|
||||
while (reader.Peek() != -1)
|
||||
{
|
||||
if (reader.ReachedEnd)
|
||||
{
|
||||
position += reader.Position - start;
|
||||
break;
|
||||
}
|
||||
|
||||
tokens.Add(ScanString(context, reader));
|
||||
|
||||
// Flush remaining tokens
|
||||
context.FlushRemaining();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (!reader.ReachedEnd && reader.Peek() == '-')
|
||||
{
|
||||
// Option
|
||||
tokens.AddRange(ScanOptions(context, reader));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Command or argument
|
||||
while (reader.Peek() != -1)
|
||||
{
|
||||
if (reader.ReachedEnd)
|
||||
{
|
||||
position += reader.Position - start;
|
||||
break;
|
||||
}
|
||||
|
||||
tokens.Add(ScanString(context, reader));
|
||||
|
||||
// Flush remaining tokens
|
||||
context.FlushRemaining();
|
||||
}
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ internal static class CommandTreeTokenizer
|
||||
builder.Append(current);
|
||||
}
|
||||
|
||||
var value = builder.ToString();
|
||||
var value = builder.ToString();
|
||||
return new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value);
|
||||
}
|
||||
|
||||
@@ -149,8 +149,8 @@ internal static class CommandTreeTokenizer
|
||||
var token = new CommandTreeToken(CommandTreeToken.Kind.String, reader.Position, "=", "=");
|
||||
throw CommandParseException.OptionValueWasExpected(reader.Original, token);
|
||||
}
|
||||
|
||||
var tokenValue = ScanString(context, reader);
|
||||
|
||||
var tokenValue = ScanString(context, reader);
|
||||
tokenValue.HadSeparator = true;
|
||||
result.Add(tokenValue);
|
||||
}
|
||||
@@ -171,7 +171,7 @@ internal static class CommandTreeTokenizer
|
||||
}
|
||||
|
||||
// Encountered a separator?
|
||||
if (current == '=' || current == ':')
|
||||
if (current is '=' or ':')
|
||||
{
|
||||
break;
|
||||
}
|
||||
@@ -184,7 +184,7 @@ internal static class CommandTreeTokenizer
|
||||
var value = current.ToString(CultureInfo.InvariantCulture);
|
||||
result.Add(result.Count == 0
|
||||
? new CommandTreeToken(CommandTreeToken.Kind.ShortOption, position, value, $"-{value}")
|
||||
: new CommandTreeToken(CommandTreeToken.Kind.ShortOption, position + result.Count, value, value));
|
||||
: new CommandTreeToken(CommandTreeToken.Kind.ShortOption, position + result.Count, value, $"-{value}"));
|
||||
}
|
||||
else if (result.Count == 0 && char.IsDigit(current))
|
||||
{
|
||||
@@ -193,7 +193,7 @@ internal static class CommandTreeTokenizer
|
||||
// be tokenized as strings. This block handles parsing those cases, but we only allow this
|
||||
// when the digit is the first character in the token (i.e. "-a1" is always an error), hence the
|
||||
// result.Count == 0 check above.
|
||||
string value = string.Empty;
|
||||
string value = string.Empty;
|
||||
|
||||
while (!reader.ReachedEnd)
|
||||
{
|
||||
@@ -212,12 +212,12 @@ internal static class CommandTreeTokenizer
|
||||
result.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value));
|
||||
}
|
||||
else
|
||||
{
|
||||
{
|
||||
// Create a token representing the short option.
|
||||
var representation = current.ToString(CultureInfo.InvariantCulture);
|
||||
var tokenPosition = position + 1 + result.Count;
|
||||
var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation);
|
||||
|
||||
var representation = current.ToString(CultureInfo.InvariantCulture);
|
||||
var tokenPosition = position + 1 + result.Count;
|
||||
var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation);
|
||||
|
||||
throw CommandParseException.InvalidShortOptionName(reader.Original, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ global using System.Linq;
|
||||
global using System.Reflection;
|
||||
global using System.Text;
|
||||
global using System.Threading.Tasks;
|
||||
global using System.Xml;
|
||||
global using System.Xml;
|
||||
global using Spectre.Console.Cli.Help;
|
||||
global using Spectre.Console.Cli.Unsafe;
|
||||
global using Spectre.Console.Rendering;
|
||||
@@ -1,153 +1,153 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Spectre.Console.Cli.Resources {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class HelpProvider {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal HelpProvider() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to ARGUMENTS.
|
||||
/// </summary>
|
||||
internal static string Arguments {
|
||||
get {
|
||||
return ResourceManager.GetString("Arguments", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to COMMAND.
|
||||
/// </summary>
|
||||
internal static string Command {
|
||||
get {
|
||||
return ResourceManager.GetString("Command", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to COMMANDS.
|
||||
/// </summary>
|
||||
internal static string Commands {
|
||||
get {
|
||||
return ResourceManager.GetString("Commands", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DEFAULT.
|
||||
/// </summary>
|
||||
internal static string Default {
|
||||
get {
|
||||
return ResourceManager.GetString("Default", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DESCRIPTION.
|
||||
/// </summary>
|
||||
internal static string Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to EXAMPLES.
|
||||
/// </summary>
|
||||
internal static string Examples {
|
||||
get {
|
||||
return ResourceManager.GetString("Examples", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to OPTIONS.
|
||||
/// </summary>
|
||||
internal static string Options {
|
||||
get {
|
||||
return ResourceManager.GetString("Options", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Prints help information.
|
||||
/// </summary>
|
||||
internal static string PrintHelpDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("PrintHelpDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Prints version information.
|
||||
/// </summary>
|
||||
internal static string PrintVersionDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("PrintVersionDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to USAGE.
|
||||
/// </summary>
|
||||
internal static string Usage {
|
||||
get {
|
||||
return ResourceManager.GetString("Usage", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Spectre.Console.Cli.Resources {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class HelpProvider {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal HelpProvider() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to ARGUMENTS.
|
||||
/// </summary>
|
||||
internal static string Arguments {
|
||||
get {
|
||||
return ResourceManager.GetString("Arguments", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to COMMAND.
|
||||
/// </summary>
|
||||
internal static string Command {
|
||||
get {
|
||||
return ResourceManager.GetString("Command", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to COMMANDS.
|
||||
/// </summary>
|
||||
internal static string Commands {
|
||||
get {
|
||||
return ResourceManager.GetString("Commands", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DEFAULT.
|
||||
/// </summary>
|
||||
internal static string Default {
|
||||
get {
|
||||
return ResourceManager.GetString("Default", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to DESCRIPTION.
|
||||
/// </summary>
|
||||
internal static string Description {
|
||||
get {
|
||||
return ResourceManager.GetString("Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to EXAMPLES.
|
||||
/// </summary>
|
||||
internal static string Examples {
|
||||
get {
|
||||
return ResourceManager.GetString("Examples", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to OPTIONS.
|
||||
/// </summary>
|
||||
internal static string Options {
|
||||
get {
|
||||
return ResourceManager.GetString("Options", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Prints help information.
|
||||
/// </summary>
|
||||
internal static string PrintHelpDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("PrintHelpDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Prints version information.
|
||||
/// </summary>
|
||||
internal static string PrintVersionDescription {
|
||||
get {
|
||||
return ResourceManager.GetString("PrintVersionDescription", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to USAGE.
|
||||
/// </summary>
|
||||
internal static string Usage {
|
||||
get {
|
||||
return ResourceManager.GetString("Usage", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,19 +31,19 @@
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\HelpProvider.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>HelpProvider.resx</DependentUpon>
|
||||
</Compile>
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\HelpProvider.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>HelpProvider.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\HelpProvider.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>HelpProvider.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\HelpProvider.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>HelpProvider.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0;netstandard2.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Description>A library that extends Spectre.Console with ImageSharp superpowers.</Description>
|
||||
@@ -13,7 +13,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<Compile Include="..\Spectre.Console\Internal\Extensions\EnumerableExtensions.cs" Link="Internal\EnumerableExtensions.cs" />
|
||||
<Compile Include="..\Spectre.Console\Internal\Text\StringBuffer.cs" Link="Internal\StringBuffer.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
|
||||
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
||||
|
||||
@@ -21,4 +21,11 @@ public sealed class CallbackCommandInterceptor : ICommandInterceptor
|
||||
{
|
||||
_callback(context, settings);
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
/// <inheritdoc/>
|
||||
public void InterceptResult(CommandContext context, CommandSettings settings, ref int result)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -141,8 +141,8 @@ public sealed class CommandAppTester
|
||||
.Trim();
|
||||
|
||||
return new CommandAppResult(result, output, context, settings);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the command application asynchronously.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0;netstandard2.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
|
||||
<IsTestProject>false</IsTestProject>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
@@ -19,6 +19,7 @@ public static partial class AnsiConsoleExtensions
|
||||
|
||||
while (true)
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
var rawKey = await console.Input.ReadKeyAsync(true, cancellationToken).ConfigureAwait(false);
|
||||
if (rawKey == null)
|
||||
{
|
||||
@@ -52,7 +53,11 @@ public static partial class AnsiConsoleExtensions
|
||||
if (text.Length > 0)
|
||||
{
|
||||
text = text.Substring(0, text.Length - 1);
|
||||
console.Write("\b \b");
|
||||
|
||||
if (mask != null)
|
||||
{
|
||||
console.Write("\b \b");
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -60,7 +65,7 @@ public static partial class AnsiConsoleExtensions
|
||||
|
||||
if (!char.IsControl(key.KeyChar))
|
||||
{
|
||||
text += key.KeyChar.ToString();
|
||||
text += key.KeyChar.ToString();
|
||||
var output = key.KeyChar.ToString();
|
||||
console.Write(secret ? output.Mask(mask) : output, style);
|
||||
}
|
||||
|
||||
@@ -116,6 +116,43 @@ public static class BarChartExtensions
|
||||
return chart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value formatter for the bar chart using culture info.
|
||||
/// </summary>
|
||||
/// <param name="chart">The bar chart.</param>
|
||||
/// <param name="func">The value formatter function with culture info.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static BarChart UseValueFormatter(this BarChart chart, Func<double, CultureInfo, string>? func)
|
||||
{
|
||||
if (chart is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chart));
|
||||
}
|
||||
|
||||
chart.ValueFormatter = func;
|
||||
return chart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value formatter for the bar chart.
|
||||
/// </summary>
|
||||
/// <param name="chart">The bar chart.</param>
|
||||
/// <param name="func">The value formatter to use.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static BarChart UseValueFormatter(this BarChart chart, Func<double, string>? func)
|
||||
{
|
||||
if (chart is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(chart));
|
||||
}
|
||||
|
||||
chart.ValueFormatter = func != null
|
||||
? (value, _) => func(value)
|
||||
: null;
|
||||
|
||||
return chart;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the width of the bar chart.
|
||||
/// </summary>
|
||||
|
||||
@@ -185,28 +185,122 @@ public static class StringExtensions
|
||||
#else
|
||||
return text.Contains(value, StringComparison.Ordinal);
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "Masks" every character in a string.
|
||||
/// </summary>
|
||||
/// <param name="value">String value to mask.</param>
|
||||
/// <param name="mask">Character to use for masking.</param>
|
||||
/// <returns>Masked string.</returns>
|
||||
public static string Mask(this string value, char? mask)
|
||||
{
|
||||
var output = string.Empty;
|
||||
|
||||
if (mask is null)
|
||||
{
|
||||
return output;
|
||||
}
|
||||
|
||||
foreach (var c in value)
|
||||
{
|
||||
output += mask;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
#if NETSTANDARD2_0
|
||||
internal static bool Contains(this string target, string value, System.StringComparison comparisonType)
|
||||
{
|
||||
return target.IndexOf(value, comparisonType) != -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// "Masks" every character in a string.
|
||||
/// </summary>
|
||||
/// <param name="value">String value to mask.</param>
|
||||
/// <param name="mask">Character to use for masking.</param>
|
||||
/// <returns>Masked string.</returns>
|
||||
public static string Mask(this string value, char? mask)
|
||||
{
|
||||
if (mask is null)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
return new string(mask.Value, value.Length);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Highlights the first text match in provided value.
|
||||
/// </summary>
|
||||
/// <param name="value">Input value.</param>
|
||||
/// <param name="searchText">Text to search for.</param>
|
||||
/// <param name="highlightStyle">The style to apply to the matched text.</param>
|
||||
/// <returns>Markup of input with the first matched text highlighted.</returns>
|
||||
internal static string Highlight(this string value, string searchText, Style? highlightStyle)
|
||||
{
|
||||
if (value is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
if (searchText is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(searchText));
|
||||
}
|
||||
|
||||
if (highlightStyle is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(highlightStyle));
|
||||
}
|
||||
|
||||
if (searchText.Length == 0)
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
var foundSearchPattern = false;
|
||||
var builder = new StringBuilder();
|
||||
using var tokenizer = new MarkupTokenizer(value);
|
||||
while (tokenizer.MoveNext())
|
||||
{
|
||||
var token = tokenizer.Current!;
|
||||
|
||||
switch (token.Kind)
|
||||
{
|
||||
case MarkupTokenKind.Text:
|
||||
{
|
||||
var tokenValue = token.Value;
|
||||
if (tokenValue.Length == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (foundSearchPattern)
|
||||
{
|
||||
builder.Append(tokenValue);
|
||||
break;
|
||||
}
|
||||
|
||||
var index = tokenValue.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
|
||||
if (index == -1)
|
||||
{
|
||||
builder.Append(tokenValue);
|
||||
break;
|
||||
}
|
||||
|
||||
foundSearchPattern = true;
|
||||
var before = tokenValue.Substring(0, index);
|
||||
var match = tokenValue.Substring(index, searchText.Length);
|
||||
var after = tokenValue.Substring(index + searchText.Length);
|
||||
|
||||
builder
|
||||
.Append(before)
|
||||
.AppendWithStyle(highlightStyle, match)
|
||||
.Append(after);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case MarkupTokenKind.Open:
|
||||
{
|
||||
builder.Append("[" + token.Value + "]");
|
||||
break;
|
||||
}
|
||||
|
||||
case MarkupTokenKind.Close:
|
||||
{
|
||||
builder.Append("[/]");
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
throw new InvalidOperationException("Unknown markup token kind.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Spectre.Console;
|
||||
namespace Spectre.Console;
|
||||
|
||||
/// <summary>
|
||||
/// Represents horizontal alignment.
|
||||
|
||||
@@ -31,12 +31,11 @@ internal static class EnumerableExtensions
|
||||
}
|
||||
|
||||
public static int IndexOf<T>(this IEnumerable<T> source, T item)
|
||||
where T : class
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var candidate in source)
|
||||
{
|
||||
if (candidate == item)
|
||||
if (Equals(candidate, item))
|
||||
{
|
||||
return index;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user