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
|
# 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**
|
**Additional context**
|
||||||
Add any other context about the problem here.
|
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**
|
**Additional context**
|
||||||
Add any other context or screenshots about the feature request here.
|
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. -->
|
<!-- 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
|
- [ ] 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
|
- [ ] A maintainer has signed off on the changes and the issue was assigned to me
|
||||||
- [ ] All newly added code is adequately covered by tests
|
- [ ] All newly added code is adequately covered by tests
|
||||||
@@ -17,3 +17,6 @@ fixes #
|
|||||||
## Changes
|
## Changes
|
||||||
|
|
||||||
<!-- describe the changes you made. -->
|
<!-- 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:
|
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
|
# BUILD
|
||||||
###################################################
|
###################################################
|
||||||
@@ -57,25 +15,15 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
name: Build
|
name: Build
|
||||||
if: "!contains(github.event.head_commit.message, 'skip-ci')"
|
if: "!contains(github.event.head_commit.message, 'skip-ci')"
|
||||||
strategy:
|
runs-on: ubuntu-latest
|
||||||
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 }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup .NET SDK
|
- name: Setup .NET SDK
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: |
|
dotnet-version: |
|
||||||
6.0.x
|
6.0.x
|
||||||
@@ -90,7 +38,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Upload Verify Test Results
|
- name: Upload Verify Test Results
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: verify-test-results
|
name: verify-test-results
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
77
.github/workflows/publish.yaml
vendored
77
.github/workflows/publish.yaml
vendored
@@ -15,40 +15,34 @@ env:
|
|||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
###################################################
|
###################################################
|
||||||
# BUILD
|
# PUBLISH
|
||||||
###################################################
|
###################################################
|
||||||
|
|
||||||
build:
|
build:
|
||||||
name: Build
|
name: Publish NuGet Packages
|
||||||
if: |
|
if: "!contains(github.event.head_commit.message, 'skip-ci') || startsWith(github.ref, 'refs/tags/')"
|
||||||
(!startsWith(github.event.head_commit.message, 'skip-ci')
|
runs-on: ubuntu-latest
|
||||||
&& !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 }}
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup .NET SDK
|
- 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
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
dotnet tool restore
|
dotnet tool restore
|
||||||
dotnet cake
|
dotnet cake --target="publish" \
|
||||||
|
--nuget-key="${{secrets.NUGET_API_KEY}}" \
|
||||||
|
--github-key="${{secrets.GITHUB_TOKEN}}"
|
||||||
|
|
||||||
###################################################
|
###################################################
|
||||||
# DOCS
|
# DOCS
|
||||||
@@ -60,20 +54,20 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup .NET SDK
|
- name: Setup .NET SDK
|
||||||
uses: actions/setup-dotnet@v3
|
uses: actions/setup-dotnet@v4
|
||||||
|
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '16'
|
node-version: '16'
|
||||||
|
|
||||||
- name: Cache dependencies
|
- name: Cache dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.npm
|
path: ~/.npm
|
||||||
key: npm-${{ hashFiles('package-lock.json') }}
|
key: npm-${{ hashFiles('package-lock.json') }}
|
||||||
@@ -88,35 +82,4 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd docs
|
cd docs
|
||||||
dotnet tool restore
|
dotnet tool restore
|
||||||
dotnet run --configuration Release -- deploy
|
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}}"
|
|
||||||
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)
|
_[](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.
|
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)
|
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
|
||||||
for Python. For detailed usage instructions, [please refer to the documentation at https://spectreconsole.net/.](https://spectreconsole.net/)
|
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@@ -19,18 +18,22 @@ for Python. For detailed usage instructions, [please refer to the documentation
|
|||||||
|
|
||||||
## Features
|
## 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
|
* Supports the most common SRG parameters when it comes to text
|
||||||
styling such as bold, dim, italic, underline, strikethrough,
|
styling such as bold, dim, italic, underline, strikethrough,
|
||||||
and blinking text.
|
and blinking text.
|
||||||
* Supports 3/4/8/24-bit colors in the terminal.
|
* Supports 3/4/8/24-bit colors in the terminal.
|
||||||
The library will detect the capabilities of the current 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
|
## Installing
|
||||||
|
|
||||||
The fastest way of getting started using `Spectre.Console` is to install the NuGet package.
|
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
|
## Documentation
|
||||||
|
|
||||||
The documentation for `Spectre.Console` can be found at
|
The documentation for `Spectre.Console` can be found at
|
||||||
https://spectreconsole.net/
|
https://spectreconsole.net
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -69,7 +72,7 @@ And to run an example:
|
|||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
The following people are [sponsoring](https://github.com/sponsors/patriksvensson)
|
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)
|
* [Rodney Littles II](https://github.com/RLittlesII)
|
||||||
* [Martin Björkström](https://github.com/bjorkstromm)
|
* [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
|
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.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Docs.Extensions;
|
using Docs.Extensions;
|
||||||
using Docs.Shortcodes;
|
using Docs.Shortcodes;
|
||||||
using Docs.Utilities;
|
using Docs.Utilities;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Statiq.App;
|
using Statiq.App;
|
||||||
using Statiq.Common;
|
using Statiq.Common;
|
||||||
using Statiq.Core;
|
using Statiq.Core;
|
||||||
using Statiq.Web;
|
using Statiq.Web;
|
||||||
|
|
||||||
namespace Docs
|
namespace Docs
|
||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
public static async Task<int> Main(string[] args) =>
|
public static async Task<int> Main(string[] args) =>
|
||||||
await Bootstrapper.Factory
|
await Bootstrapper.Factory
|
||||||
.CreateWeb(args)
|
.CreateWeb(args)
|
||||||
.AddSetting(Keys.Host, "spectreconsole.net")
|
.AddSetting(Keys.Host, "spectreconsole.net")
|
||||||
.AddSetting(Keys.LinksUseHttps, true)
|
.AddSetting(Keys.LinksUseHttps, true)
|
||||||
.AddSetting(Constants.EditLink, ConfigureEditLink())
|
.AddSetting(Constants.EditLink, ConfigureEditLink())
|
||||||
.AddSetting(Constants.SourceFiles, new List<string>
|
.AddSetting(Constants.SourceFiles, new List<string>
|
||||||
{
|
{
|
||||||
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||||
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||||
"../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
"../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||||
"../../src/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
|
"../../src/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
|
||||||
})
|
})
|
||||||
.AddSetting(Constants.ExampleSourceFiles, new List<string>
|
.AddSetting(Constants.ExampleSourceFiles, new List<string>
|
||||||
{
|
{
|
||||||
"../../examples/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
"../../examples/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.ConfigureServices(i =>
|
.ConfigureServices(i =>
|
||||||
{
|
{
|
||||||
i.AddSingleton(new TypeNameLinks());
|
i.AddSingleton(new TypeNameLinks());
|
||||||
})
|
})
|
||||||
.ConfigureSite("spectreconsole", "spectre.console", "main")
|
.ConfigureSite("spectreconsole", "spectre.console", "main")
|
||||||
.AddShortcode("Children", typeof(ChildrenShortcode))
|
.AddShortcode("Children", typeof(ChildrenShortcode))
|
||||||
.AddShortcode("ColorTable", typeof(ColorTableShortcode))
|
.AddShortcode("ColorTable", typeof(ColorTableShortcode))
|
||||||
.AddShortcode("EmojiTable", typeof(EmojiTableShortcode))
|
.AddShortcode("EmojiTable", typeof(EmojiTableShortcode))
|
||||||
.AddShortcode("Alert", typeof(AlertShortcode))
|
.AddShortcode("Alert", typeof(AlertShortcode))
|
||||||
.AddShortcode("Info", typeof(InfoShortcode))
|
.AddShortcode("Info", typeof(InfoShortcode))
|
||||||
.AddShortcode("AsciiCast", typeof(AsciiCastShortcode))
|
.AddShortcode("AsciiCast", typeof(AsciiCastShortcode))
|
||||||
.AddShortcode("Example", typeof(ExampleSnippet))
|
.AddShortcode("Example", typeof(ExampleSnippet))
|
||||||
.AddPipelines()
|
.AddPipelines()
|
||||||
.BuildPipeline(
|
.BuildPipeline(
|
||||||
"Bootstrap",
|
"Bootstrap",
|
||||||
builder => builder
|
builder => builder
|
||||||
.WithInputReadFiles("../node_modules/asciinema-player/dist/bundle/asciinema-player.js")
|
.WithInputReadFiles("../node_modules/asciinema-player/dist/bundle/asciinema-player.js")
|
||||||
.WithProcessModules(new SetDestination(Config.FromDocument(doc => new NormalizedPath($"./assets/{doc.Source.FileName}")), true))
|
.WithProcessModules(new SetDestination(Config.FromDocument(doc => new NormalizedPath($"./assets/{doc.Source.FileName}")), true))
|
||||||
.WithOutputWriteFiles()
|
.WithOutputWriteFiles()
|
||||||
)
|
)
|
||||||
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("npm", "install --audit false --fund false --progress false")
|
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("npm", "install --audit false --fund false --progress false")
|
||||||
{
|
{
|
||||||
LogErrors = false
|
LogErrors = false
|
||||||
})
|
})
|
||||||
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("dotnet", "playwright install chromium"))
|
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("dotnet", "playwright install chromium"))
|
||||||
.AddProcess(ProcessTiming.BeforeDeployment, _ => new ProcessLauncher("npm", "run build:tailwind")
|
.AddProcess(ProcessTiming.BeforeDeployment, _ => new ProcessLauncher("npm", "run build:tailwind")
|
||||||
{
|
{
|
||||||
LogErrors = false
|
LogErrors = false
|
||||||
})
|
})
|
||||||
.RunAsync();
|
.RunAsync();
|
||||||
|
|
||||||
private static Config<string> ConfigureEditLink()
|
private static Config<string> ConfigureEditLink()
|
||||||
{
|
{
|
||||||
return Config.FromDocument((doc, ctx) =>
|
return Config.FromDocument((doc, ctx) =>
|
||||||
{
|
{
|
||||||
return string.Format("https://github.com/{0}/{1}/edit/{2}/docs/input/{3}",
|
return string.Format("https://github.com/{0}/{1}/edit/{2}/docs/input/{3}",
|
||||||
ctx.GetString(Constants.Site.Owner),
|
ctx.GetString(Constants.Site.Owner),
|
||||||
ctx.GetString(Constants.Site.Repository),
|
ctx.GetString(Constants.Site.Repository),
|
||||||
ctx.GetString(Constants.Site.Branch),
|
ctx.GetString(Constants.Site.Branch),
|
||||||
doc.Source.GetRelativeInputPath());
|
doc.Source.GetRelativeInputPath());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ Description: "*Spectre.Console* makes it easy to write text with different style
|
|||||||
Highlights:
|
Highlights:
|
||||||
- Bold, Italic, Underline, strikethrough
|
- Bold, Italic, Underline, strikethrough
|
||||||
- Dim, Invert
|
- Dim, Invert
|
||||||
- Conceal, slowblink, rapidblink
|
- Conceal, slowblink, rapidblink
|
||||||
- Links
|
- Links
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,9 +46,9 @@ Note that what styles that can be used is defined by the system or your terminal
|
|||||||
<tr>
|
<tr>
|
||||||
<td><code>strikethrough</code></td>
|
<td><code>strikethrough</code></td>
|
||||||
<td>Shows text with a horizontal line through the center</td>
|
<td>Shows text with a horizontal line through the center</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>link</link></td>
|
<td><code>link</link></td>
|
||||||
<td>Creates a clickable link within text</td>
|
<td>Creates a clickable link within text</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</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
|
Title: Command Help
|
||||||
Order: 13
|
Order: 13
|
||||||
Description: "Console applications built with *Spectre.Console.Cli* include automatically generated help command line help."
|
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.
|
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 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,
|
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.
|
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.
|
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`.
|
`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
|
## Styling the help text
|
||||||
|
|
||||||
Whilst it shouldn't be common place to implement your own help provider, it is however possible.
|
Basic styling is applied to the generated help text by default, however this is configurable.
|
||||||
|
|
||||||
You are able to implement your own `IHelpProvider` and configure a `CommandApp` to use that instead of the Spectre.Console help provider.
|
`HelpProviderStyle` is the `Spectre.Console` class that holds the style information for the help text.
|
||||||
|
|
||||||
```csharp
|
The default theme shipped with Spectre.Console is provided by a factory method, `HelpProviderStyle.Default`.
|
||||||
using Spectre.Console.Cli;
|
|
||||||
|
However, you can explicitly set a custom theme when configuring a CommandApp, for example:
|
||||||
namespace Help;
|
|
||||||
|
```csharp
|
||||||
public static class Program
|
config.Settings.HelpProviderStyles = new HelpProviderStyle()
|
||||||
{
|
{
|
||||||
public static int Main(string[] args)
|
Description = new DescriptionStyle()
|
||||||
{
|
{
|
||||||
var app = new CommandApp<DefaultCommand>();
|
Header = "bold",
|
||||||
|
},
|
||||||
app.Configure(config =>
|
};
|
||||||
{
|
```
|
||||||
// Register the custom help provider
|
|
||||||
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
|
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
|
||||||
return app.Run(args);
|
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.
|
||||||
|
|
||||||
There is a working [example of a custom help provider](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Help) demonstrating this.
|
## 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
|
## 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:
|
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.
|
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
|
## 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).
|
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
|
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.
|
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
|
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.
|
the exitCode for the application.
|
||||||
@@ -71,7 +75,7 @@ namespace MyApp
|
|||||||
|
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
config.SetExceptionHandler(ex =>
|
config.SetExceptionHandler((ex, resolver) =>
|
||||||
{
|
{
|
||||||
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
||||||
return -99;
|
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.
|
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`.
|
The exitCode for the application will be `-1`.
|
||||||
|
|
||||||
@@ -103,7 +107,7 @@ namespace MyApp
|
|||||||
|
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
config.SetExceptionHandler(ex =>
|
config.SetExceptionHandler((ex, resolver) =>
|
||||||
{
|
{
|
||||||
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,119 +1,119 @@
|
|||||||
Title: Markup
|
Title: Markup
|
||||||
Order: 30
|
Order: 30
|
||||||
Description: The Markup class allows you to output rich text to the console.
|
Description: The Markup class allows you to output rich text to the console.
|
||||||
Highlights:
|
Highlights:
|
||||||
- Easily add *color*.
|
- Easily add *color*.
|
||||||
- Add hyperlinks to for supported terminals.
|
- Add hyperlinks to for supported terminals.
|
||||||
- Emoji 🚀 parsing.
|
- Emoji 🚀 parsing.
|
||||||
Reference:
|
Reference:
|
||||||
- M:Spectre.Console.AnsiConsole.Markup(System.String)
|
- M:Spectre.Console.AnsiConsole.Markup(System.String)
|
||||||
- M:Spectre.Console.AnsiConsole.MarkupLine(System.String)
|
- M:Spectre.Console.AnsiConsole.MarkupLine(System.String)
|
||||||
- T:Spectre.Console.Markup
|
- T:Spectre.Console.Markup
|
||||||
---
|
---
|
||||||
|
|
||||||
The `Markup` class allows you to output rich text to the console.
|
The `Markup` class allows you to output rich text to the console.
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
Console markup uses a syntax inspired by bbcode. If you write the style (see [Styles](xref:styles))
|
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 `[/]`.
|
in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Write(new Markup("[bold yellow]Hello[/] [red]World![/]"));
|
AnsiConsole.Write(new Markup("[bold yellow]Hello[/] [red]World![/]"));
|
||||||
```
|
```
|
||||||
|
|
||||||
The `Markup` class implements `IRenderable` which means that you
|
The `Markup` class implements `IRenderable` which means that you
|
||||||
can use this in tables, grids, and panels. Most classes that support
|
can use this in tables, grids, and panels. Most classes that support
|
||||||
rendering of `IRenderable` also have overloads for rendering rich text.
|
rendering of `IRenderable` also have overloads for rendering rich text.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var table = new Table();
|
var table = new Table();
|
||||||
table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]")));
|
table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]")));
|
||||||
table.AddColumn(new TableColumn("[blue]Bar[/]"));
|
table.AddColumn(new TableColumn("[blue]Bar[/]"));
|
||||||
AnsiConsole.Write(table);
|
AnsiConsole.Write(table);
|
||||||
```
|
```
|
||||||
|
|
||||||
## Convenience methods
|
## Convenience methods
|
||||||
|
|
||||||
There are also convenience methods on `AnsiConsole` that can be used
|
There are also convenience methods on `AnsiConsole` that can be used
|
||||||
to write markup text to the console without instantiating a new `Markup`
|
to write markup text to the console without instantiating a new `Markup`
|
||||||
instance.
|
instance.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Markup("[underline green]Hello[/] ");
|
AnsiConsole.Markup("[underline green]Hello[/] ");
|
||||||
AnsiConsole.MarkupLine("[bold]World[/]");
|
AnsiConsole.MarkupLine("[bold]World[/]");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Escaping format characters
|
## Escaping format characters
|
||||||
|
|
||||||
To output a `[` you use `[[`, and to output a `]` you use `]]`.
|
To output a `[` you use `[[`, and to output a `]` you use `]]`.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Markup("[[Hello]] "); // [Hello]
|
AnsiConsole.Markup("[[Hello]] "); // [Hello]
|
||||||
AnsiConsole.Markup("[red][[World]][/]"); // [World]
|
AnsiConsole.Markup("[red][[World]][/]"); // [World]
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also use the `EscapeMarkup` extension method.
|
You can also use the `EscapeMarkup` extension method.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".EscapeMarkup());
|
AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".EscapeMarkup());
|
||||||
```
|
```
|
||||||
You can also use the `Markup.Escape` method.
|
You can also use the `Markup.Escape` method.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Markup("[red]{0}[/]", Markup.Escape("Hello [World]"));
|
AnsiConsole.Markup("[red]{0}[/]", Markup.Escape("Hello [World]"));
|
||||||
```
|
```
|
||||||
|
|
||||||
## Escaping Interpolated Strings
|
## 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".
|
When working with interpolated strings, you can use the `MarkupInterpolated` and `MarkupLineInterpolated` methods to automatically escape the values in the interpolated string "holes".
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
string hello = "Hello [World]";
|
string hello = "Hello [World]";
|
||||||
AnsiConsole.MarkupInterpolated($"[red]{hello}[/]");
|
AnsiConsole.MarkupInterpolated($"[red]{hello}[/]");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Setting background color
|
## Setting background color
|
||||||
|
|
||||||
You can set the background color in markup by prefixing the color with `on`.
|
You can set the background color in markup by prefixing the color with `on`.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Markup("[bold yellow on blue]Hello[/]");
|
AnsiConsole.Markup("[bold yellow on blue]Hello[/]");
|
||||||
AnsiConsole.Markup("[default on blue]World[/]");
|
AnsiConsole.Markup("[default on blue]World[/]");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Rendering emojis
|
## Rendering emojis
|
||||||
|
|
||||||
To output an emoji as part of markup, you can use emoji shortcodes.
|
To output an emoji as part of markup, you can use emoji shortcodes.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Markup("Hello :globe_showing_europe_africa:!");
|
AnsiConsole.Markup("Hello :globe_showing_europe_africa:!");
|
||||||
```
|
```
|
||||||
|
|
||||||
For a list of emoji, see the [Emojis](xref:emojis) appendix section.
|
For a list of emoji, see the [Emojis](xref:emojis) appendix section.
|
||||||
|
|
||||||
## Colors
|
## Colors
|
||||||
|
|
||||||
In the examples above, all colors were referenced by their name,
|
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.
|
but you can also use the hex or rgb representation for colors in markdown.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Markup("[red]Foo[/] ");
|
AnsiConsole.Markup("[red]Foo[/] ");
|
||||||
AnsiConsole.Markup("[#ff0000]Bar[/] ");
|
AnsiConsole.Markup("[#ff0000]Bar[/] ");
|
||||||
AnsiConsole.Markup("[rgb(255,0,0)]Baz[/] ");
|
AnsiConsole.Markup("[rgb(255,0,0)]Baz[/] ");
|
||||||
```
|
```
|
||||||
|
|
||||||
For a list of colors, see the [Colors](xref:colors) appendix section.
|
For a list of colors, see the [Colors](xref:colors) appendix section.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
To output a clickable link, you can use the `[link]` style.
|
To output a clickable link, you can use the `[link]` style.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Markup("[link]https://spectreconsole.net[/]");
|
AnsiConsole.Markup("[link]https://spectreconsole.net[/]");
|
||||||
AnsiConsole.Markup("[link=https://spectreconsole.net]Spectre Console Documentation[/]");
|
AnsiConsole.Markup("[link=https://spectreconsole.net]Spectre Console Documentation[/]");
|
||||||
```
|
```
|
||||||
|
|
||||||
## Styles
|
## Styles
|
||||||
|
|
||||||
For a list of styles, see the [Styles](xref:styles) appendix section.
|
For a list of styles, see the [Styles](xref:styles) appendix section.
|
||||||
|
|||||||
@@ -63,22 +63,22 @@ What's the secret number? _
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
Enter password: ************_
|
Enter password: ************_
|
||||||
```
|
```
|
||||||
|
|
||||||
## Masks
|
## Masks
|
||||||
|
|
||||||
<?# Example symbol="M:Prompt.Program.AskPasswordWithCustomMask" project="Prompt" /?>
|
<?# Example symbol="M:Prompt.Program.AskPasswordWithCustomMask" project="Prompt" /?>
|
||||||
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Enter password: ------------_
|
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" /?>
|
<?# Example symbol="M:Prompt.Program.AskPasswordWithNullMask" project="Prompt" /?>
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Enter password: _
|
Enter password: _
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ to install the NuGet package.
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
> dotnet add package Spectre.Console
|
> 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.
|
Once that is done, you can start using all the available features.
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
|
|||||||
@@ -132,3 +132,13 @@ AnsiConsole.Write(new BreakdownChart()
|
|||||||
.AddItem(new Fruit("Mango", 3, Color.Orange4))
|
.AddItem(new Fruit("Mango", 3, Color.Orange4))
|
||||||
.AddItems(items));
|
.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
|
Order: 40
|
||||||
RedirectFrom: calendar
|
RedirectFrom: calendar
|
||||||
Description: "The **Calendar** is used to render a calendar to the terminal."
|
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.Common;
|
||||||
using Statiq.Web;
|
using Statiq.Web;
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Docs.Extensions
|
namespace Docs.Extensions
|
||||||
{
|
{
|
||||||
public static class StringExtensions
|
public static class StringExtensions
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Docs.Models
|
namespace Docs.Models
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using Docs.Utilities;
|
using Docs.Utilities;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Statiq.Common;
|
using Statiq.Common;
|
||||||
using Statiq.Web.GitHub;
|
using Statiq.Web.GitHub;
|
||||||
using Statiq.Web.Netlify;
|
using Statiq.Web.Netlify;
|
||||||
|
|
||||||
|
|||||||
@@ -1,119 +1,119 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.FileProviders;
|
using Microsoft.Extensions.FileProviders;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Playwright;
|
using Microsoft.Playwright;
|
||||||
using Statiq.Common;
|
using Statiq.Common;
|
||||||
using Statiq.Core;
|
using Statiq.Core;
|
||||||
using Statiq.Web;
|
using Statiq.Web;
|
||||||
using Statiq.Web.Modules;
|
using Statiq.Web.Modules;
|
||||||
using Statiq.Web.Pipelines;
|
using Statiq.Web.Pipelines;
|
||||||
|
|
||||||
namespace Docs.Pipelines
|
namespace Docs.Pipelines
|
||||||
{
|
{
|
||||||
public class SocialImages : Pipeline
|
public class SocialImages : Pipeline
|
||||||
{
|
{
|
||||||
public SocialImages()
|
public SocialImages()
|
||||||
{
|
{
|
||||||
Dependencies.AddRange(nameof(Inputs));
|
Dependencies.AddRange(nameof(Inputs));
|
||||||
|
|
||||||
ProcessModules = new ModuleList
|
ProcessModules = new ModuleList
|
||||||
{
|
{
|
||||||
new GetPipelineDocuments(ContentType.Content),
|
new GetPipelineDocuments(ContentType.Content),
|
||||||
|
|
||||||
// Filter to non-archive content
|
// Filter to non-archive content
|
||||||
new FilterDocuments(Config.FromDocument(doc => !Archives.IsArchive(doc))),
|
new FilterDocuments(Config.FromDocument(doc => !Archives.IsArchive(doc))),
|
||||||
|
|
||||||
// Process the content
|
// Process the content
|
||||||
new CacheDocuments
|
new CacheDocuments
|
||||||
{
|
{
|
||||||
new AddTitle(),
|
new AddTitle(),
|
||||||
new SetDestination(true),
|
new SetDestination(true),
|
||||||
new ExecuteIf(Config.FromSetting(WebKeys.OptimizeContentFileNames, true))
|
new ExecuteIf(Config.FromSetting(WebKeys.OptimizeContentFileNames, true))
|
||||||
{
|
{
|
||||||
new OptimizeFileName()
|
new OptimizeFileName()
|
||||||
},
|
},
|
||||||
new GenerateSocialImage(),
|
new GenerateSocialImage(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
OutputModules = new ModuleList { new WriteFiles() };
|
OutputModules = new ModuleList { new WriteFiles() };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenerateSocialImage : ParallelModule
|
class GenerateSocialImage : ParallelModule
|
||||||
{
|
{
|
||||||
private IPlaywright _playwright;
|
private IPlaywright _playwright;
|
||||||
private IBrowser _browser;
|
private IBrowser _browser;
|
||||||
private WebApplication _app;
|
private WebApplication _app;
|
||||||
private IBrowserContext _context;
|
private IBrowserContext _context;
|
||||||
|
|
||||||
protected override async Task BeforeExecutionAsync(IExecutionContext context)
|
protected override async Task BeforeExecutionAsync(IExecutionContext context)
|
||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder();
|
var builder = WebApplication.CreateBuilder();
|
||||||
builder.Logging.ClearProviders();
|
builder.Logging.ClearProviders();
|
||||||
|
|
||||||
builder.Services
|
builder.Services
|
||||||
.AddRazorPages()
|
.AddRazorPages()
|
||||||
.WithRazorPagesRoot("/src/SocialCards/");
|
.WithRazorPagesRoot("/src/SocialCards/");
|
||||||
|
|
||||||
_app = builder.Build();
|
_app = builder.Build();
|
||||||
_app.MapRazorPages();
|
_app.MapRazorPages();
|
||||||
_app.UseStaticFiles(new StaticFileOptions
|
_app.UseStaticFiles(new StaticFileOptions
|
||||||
{
|
{
|
||||||
FileProvider = new PhysicalFileProvider(
|
FileProvider = new PhysicalFileProvider(
|
||||||
Path.Combine(builder.Environment.ContentRootPath, "src/SocialCards")),
|
Path.Combine(builder.Environment.ContentRootPath, "src/SocialCards")),
|
||||||
RequestPath = "/static"
|
RequestPath = "/static"
|
||||||
});
|
});
|
||||||
|
|
||||||
await _app.StartAsync().ConfigureAwait(false);
|
await _app.StartAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
_playwright = await Playwright.CreateAsync().ConfigureAwait(false);
|
_playwright = await Playwright.CreateAsync().ConfigureAwait(false);
|
||||||
_browser = await _playwright.Chromium.LaunchAsync().ConfigureAwait(false);
|
_browser = await _playwright.Chromium.LaunchAsync().ConfigureAwait(false);
|
||||||
_context = await _browser.NewContextAsync(new BrowserNewContextOptions {
|
_context = await _browser.NewContextAsync(new BrowserNewContextOptions {
|
||||||
ViewportSize = new ViewportSize { Width = 1200, Height = 618 },
|
ViewportSize = new ViewportSize { Width = 1200, Height = 618 },
|
||||||
}).ConfigureAwait(false);
|
}).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task FinallyAsync(IExecutionContext context)
|
protected override async Task FinallyAsync(IExecutionContext context)
|
||||||
{
|
{
|
||||||
await _context.DisposeAsync().ConfigureAwait(false);
|
await _context.DisposeAsync().ConfigureAwait(false);
|
||||||
await _browser.DisposeAsync().ConfigureAwait(false);
|
await _browser.DisposeAsync().ConfigureAwait(false);
|
||||||
_playwright.Dispose();
|
_playwright.Dispose();
|
||||||
await _app.DisposeAsync().ConfigureAwait(false);
|
await _app.DisposeAsync().ConfigureAwait(false);
|
||||||
await base.FinallyAsync(context);
|
await base.FinallyAsync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context)
|
protected override async Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context)
|
||||||
{
|
{
|
||||||
var url = _app.Urls.FirstOrDefault(u => u.StartsWith("http://"));
|
var url = _app.Urls.FirstOrDefault(u => u.StartsWith("http://"));
|
||||||
var page = await _context.NewPageAsync().ConfigureAwait(false);
|
var page = await _context.NewPageAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var title = input.GetString("Title");
|
var title = input.GetString("Title");
|
||||||
var description = input.GetString("Description");
|
var description = input.GetString("Description");
|
||||||
var highlights = input.GetList<string>("Highlights") ?? Array.Empty<string>();
|
var highlights = input.GetList<string>("Highlights") ?? Array.Empty<string>();
|
||||||
|
|
||||||
await page.GotoAsync($"{url}/?title={title}&desc={description}&highlights={string.Join("||", highlights)}");
|
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
|
// 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.
|
// chrome a chance to complete rendering of the fonts while the wait timeout completes.
|
||||||
await page.WaitForLoadStateAsync(LoadState.NetworkIdle).ConfigureAwait(false);
|
await page.WaitForLoadStateAsync(LoadState.NetworkIdle).ConfigureAwait(false);
|
||||||
var bytes = await page.ScreenshotAsync().ConfigureAwait(false);
|
var bytes = await page.ScreenshotAsync().ConfigureAwait(false);
|
||||||
await page.CloseAsync().ConfigureAwait(false);
|
await page.CloseAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
var destination = input.Destination.InsertSuffix("-social").ChangeExtension("png");
|
var destination = input.Destination.InsertSuffix("-social").ChangeExtension("png");
|
||||||
var doc = context.CreateDocument(
|
var doc = context.CreateDocument(
|
||||||
input.Source,
|
input.Source,
|
||||||
destination,
|
destination,
|
||||||
new MetadataItems { { "DocId", input.Id }},
|
new MetadataItems { { "DocId", input.Id }},
|
||||||
context.GetContentProvider(bytes));
|
context.GetContentProvider(bytes));
|
||||||
|
|
||||||
return new[] { doc };
|
return new[] { doc };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Docs.Extensions;
|
using Docs.Extensions;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="console">
|
<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"><span style="color:var(--brightBlack)">╰─</span> dotnet run</div>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
<div class="line">╭────────────────────────────────────────────────────────╮</div>
|
<div class="line">╭────────────────────────────────────────────────────────╮</div>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|||||||
@@ -9,10 +9,16 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dotnet-example": {
|
"dotnet-example": {
|
||||||
"version": "2.0.0",
|
"version": "3.1.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-example"
|
"dotnet-example"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"verify.tool": {
|
||||||
|
"version": "0.6.0",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-verify"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15,22 +15,22 @@ public static class Program
|
|||||||
{
|
{
|
||||||
config.SetApplicationName("fake-dotnet");
|
config.SetApplicationName("fake-dotnet");
|
||||||
config.ValidateExamples();
|
config.ValidateExamples();
|
||||||
config.AddExample("run", "--no-build");
|
config.AddExample("run", "--no-build");
|
||||||
|
|
||||||
// Run
|
// Run
|
||||||
config.AddCommand<RunCommand>("run");
|
config.AddCommand<RunCommand>("run");
|
||||||
|
|
||||||
// Add
|
// Add
|
||||||
config.AddBranch<AddSettings>("add", add =>
|
config.AddBranch<AddSettings>("add", add =>
|
||||||
{
|
{
|
||||||
add.SetDescription("Add a package or reference to a .NET project");
|
add.SetDescription("Add a package or reference to a .NET project");
|
||||||
add.AddCommand<AddPackageCommand>("package");
|
add.AddCommand<AddPackageCommand>("package");
|
||||||
add.AddCommand<AddReferenceCommand>("reference");
|
add.AddCommand<AddReferenceCommand>("reference");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Serve
|
// Serve
|
||||||
config.AddCommand<ServeCommand>("serve")
|
config.AddCommand<ServeCommand>("serve")
|
||||||
.WithExample("serve", "-o", "firefox")
|
.WithExample("serve", "-o", "firefox")
|
||||||
.WithExample("serve", "--port", "80", "-o", "firefox");
|
.WithExample("serve", "--port", "80", "-o", "firefox");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,30 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Cli;
|
using Spectre.Console.Cli;
|
||||||
using Spectre.Console.Cli.Help;
|
using Spectre.Console.Cli.Help;
|
||||||
using Spectre.Console.Rendering;
|
using Spectre.Console.Rendering;
|
||||||
|
|
||||||
namespace Help;
|
namespace Help;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Example showing how to extend the built-in Spectre.Console help provider
|
/// Example showing how to extend the built-in Spectre.Console help provider
|
||||||
/// by rendering a custom banner at the top of the help information
|
/// by rendering a custom banner at the top of the help information
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class CustomHelpProvider : HelpProvider
|
internal class CustomHelpProvider : HelpProvider
|
||||||
{
|
{
|
||||||
public CustomHelpProvider(ICommandAppSettings settings)
|
public CustomHelpProvider(ICommandAppSettings settings)
|
||||||
: base(settings)
|
: base(settings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
|
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
|
||||||
{
|
{
|
||||||
return new[]
|
return new[]
|
||||||
{
|
{
|
||||||
new Text("--------------------------------------"), Text.NewLine,
|
new Text("--------------------------------------"), Text.NewLine,
|
||||||
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
|
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
|
||||||
new Text("--------------------------------------"), Text.NewLine,
|
new Text("--------------------------------------"), Text.NewLine,
|
||||||
Text.NewLine,
|
Text.NewLine,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
using Spectre.Console.Cli;
|
using Spectre.Console.Cli;
|
||||||
|
|
||||||
namespace Help;
|
namespace Help;
|
||||||
|
|
||||||
public sealed class DefaultCommand : Command
|
public sealed class DefaultCommand : Command
|
||||||
{
|
{
|
||||||
private IAnsiConsole _console;
|
private IAnsiConsole _console;
|
||||||
|
|
||||||
public DefaultCommand(IAnsiConsole console)
|
public DefaultCommand(IAnsiConsole console)
|
||||||
{
|
{
|
||||||
_console = console;
|
_console = console;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int Execute(CommandContext context)
|
public override int Execute(CommandContext context)
|
||||||
{
|
{
|
||||||
_console.WriteLine("Hello world");
|
_console.WriteLine("Hello world");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,23 @@
|
|||||||
using Spectre.Console.Cli;
|
using Spectre.Console.Cli;
|
||||||
|
using Spectre.Console.Cli.Help;
|
||||||
namespace Help;
|
|
||||||
|
namespace Help;
|
||||||
public static class Program
|
|
||||||
{
|
public static class Program
|
||||||
public static int Main(string[] args)
|
{
|
||||||
{
|
public static int Main(string[] args)
|
||||||
var app = new CommandApp<DefaultCommand>();
|
{
|
||||||
|
var app = new CommandApp<DefaultCommand>();
|
||||||
app.Configure(config =>
|
|
||||||
{
|
app.Configure(config =>
|
||||||
// Register the custom help provider
|
{
|
||||||
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
|
// Register the custom help provider
|
||||||
});
|
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
|
||||||
|
|
||||||
return app.Run(args);
|
// Render an unstyled help text for maximum accessibility
|
||||||
}
|
config.Settings.HelpProviderStyles = null;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
return app.Run(args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -12,9 +12,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog" Version="2.11.0" />
|
<PackageReference Include="Serilog" Version="3.1.1" />
|
||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
|
<PackageReference Include="Serilog.Extensions.Logging" Version="8.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
|
<PackageReference Include="Serilog.Sinks.Map" Version="1.0.2" />
|
||||||
</ItemGroup>
|
</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()
|
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.
|
// Draw an image using CanvasImage powered by ImageSharp.
|
||||||
// This requires the "Spectre.Console.ImageSharp" NuGet package.
|
// This requires the "Spectre.Console.ImageSharp" NuGet package.
|
||||||
var image = new CanvasImage("cake.png");
|
var image = new CanvasImage("cake.png");
|
||||||
@@ -21,7 +17,7 @@ public static class Program
|
|||||||
image.MaxWidth(16);
|
image.MaxWidth(16);
|
||||||
Render(image, "Image from file (16 wide)");
|
Render(image, "Image from file (16 wide)");
|
||||||
|
|
||||||
// Draw image again, but without render width
|
// Draw image again, but without max width
|
||||||
image.NoMaxWidth();
|
image.NoMaxWidth();
|
||||||
image.Mutate(ctx => ctx.Grayscale().Rotate(-45).EntropyCrop());
|
image.Mutate(ctx => ctx.Grayscale().Rotate(-45).EntropyCrop());
|
||||||
Render(image, "Image from file (fit, greyscale, rotated)");
|
Render(image, "Image from file (fit, greyscale, rotated)");
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -33,11 +33,11 @@ namespace Prompt
|
|||||||
var age = AskAge();
|
var age = AskAge();
|
||||||
|
|
||||||
WriteDivider("Secrets");
|
WriteDivider("Secrets");
|
||||||
var password = AskPassword();
|
var password = AskPassword();
|
||||||
|
|
||||||
WriteDivider("Mask");
|
WriteDivider("Mask");
|
||||||
var mask = AskPasswordWithCustomMask();
|
var mask = AskPasswordWithCustomMask();
|
||||||
|
|
||||||
WriteDivider("Null Mask");
|
WriteDivider("Null Mask");
|
||||||
var nullMask = AskPasswordWithNullMask();
|
var nullMask = AskPasswordWithNullMask();
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@ namespace Prompt
|
|||||||
.AddRow("[grey]Favorite fruit[/]", fruit)
|
.AddRow("[grey]Favorite fruit[/]", fruit)
|
||||||
.AddRow("[grey]Favorite sport[/]", sport)
|
.AddRow("[grey]Favorite sport[/]", sport)
|
||||||
.AddRow("[grey]Age[/]", age.ToString())
|
.AddRow("[grey]Age[/]", age.ToString())
|
||||||
.AddRow("[grey]Password[/]", password)
|
.AddRow("[grey]Password[/]", password)
|
||||||
.AddRow("[grey]Mask[/]", mask)
|
.AddRow("[grey]Mask[/]", mask)
|
||||||
.AddRow("[grey]Null Mask[/]", nullMask)
|
.AddRow("[grey]Null Mask[/]", nullMask)
|
||||||
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
|
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
|
||||||
}
|
}
|
||||||
@@ -110,6 +110,7 @@ namespace Prompt
|
|||||||
{
|
{
|
||||||
fruit = AnsiConsole.Prompt(
|
fruit = AnsiConsole.Prompt(
|
||||||
new SelectionPrompt<string>()
|
new SelectionPrompt<string>()
|
||||||
|
.EnableSearch()
|
||||||
.Title("Ok, but if you could only choose [green]one[/]?")
|
.Title("Ok, but if you could only choose [green]one[/]?")
|
||||||
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
|
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
|
||||||
.AddChoices(favorites));
|
.AddChoices(favorites));
|
||||||
@@ -153,22 +154,22 @@ namespace Prompt
|
|||||||
new TextPrompt<string>("Enter [green]password[/]?")
|
new TextPrompt<string>("Enter [green]password[/]?")
|
||||||
.PromptStyle("red")
|
.PromptStyle("red")
|
||||||
.Secret());
|
.Secret());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string AskPasswordWithCustomMask()
|
public static string AskPasswordWithCustomMask()
|
||||||
{
|
{
|
||||||
return AnsiConsole.Prompt(
|
return AnsiConsole.Prompt(
|
||||||
new TextPrompt<string>("Enter [green]password[/]?")
|
new TextPrompt<string>("Enter [green]password[/]?")
|
||||||
.PromptStyle("red")
|
.PromptStyle("red")
|
||||||
.Secret('-'));
|
.Secret('-'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string AskPasswordWithNullMask()
|
public static string AskPasswordWithNullMask()
|
||||||
{
|
{
|
||||||
return AnsiConsole.Prompt(
|
return AnsiConsole.Prompt(
|
||||||
new TextPrompt<string>("Enter [green]password[/]?")
|
new TextPrompt<string>("Enter [green]password[/]?")
|
||||||
.PromptStyle("red")
|
.PromptStyle("red")
|
||||||
.Secret(null));
|
.Secret(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string AskColor()
|
public static string AskColor()
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "http://json.schemastore.org/global",
|
"$schema": "http://json.schemastore.org/global",
|
||||||
"sdk": {
|
"sdk": {
|
||||||
"version": "8.0.100",
|
"version": "8.0.200",
|
||||||
"rollForward": "latestFeature"
|
"rollForward": "latestFeature"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Spectre.Console;
|
using Spectre.Console;
|
||||||
|
|
||||||
namespace Generator.Commands
|
namespace Generator.Commands
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<PropertyGroup Label="Settings">
|
<PropertyGroup Label="Settings">
|
||||||
<Deterministic>true</Deterministic>
|
<Deterministic>true</Deterministic>
|
||||||
<LangVersion>10</LangVersion>
|
<LangVersion>12</LangVersion>
|
||||||
<DebugSymbols>true</DebugSymbols>
|
<DebugSymbols>true</DebugSymbols>
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
|
<MinVerSkip Condition="'$(Configuration)' == 'Debug'">true</MinVerSkip>
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
|
|
||||||
<PropertyGroup Label="Package Information">
|
<PropertyGroup Label="Package Information">
|
||||||
<Description>A library that makes it easier to create beautiful console applications.</Description>
|
<Description>A library that makes it easier to create beautiful console applications.</Description>
|
||||||
<Copyright>Patrik Svensson, Phil Scott, Nils Andresen</Copyright>
|
<Copyright>Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray</Copyright>
|
||||||
<Authors>Patrik Svensson, Phil Scott, Nils Andresen</Authors>
|
<Authors>Patrik Svensson, Phil Scott, Nils Andresen, Cédric Luthi, Frank Ray</Authors>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
<RepositoryUrl>https://github.com/spectreconsole/spectre.console</RepositoryUrl>
|
<RepositoryUrl>https://github.com/spectreconsole/spectre.console</RepositoryUrl>
|
||||||
<PackageIcon>small-logo.png</PackageIcon>
|
<PackageIcon>small-logo.png</PackageIcon>
|
||||||
@@ -30,14 +30,15 @@
|
|||||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup Label="Package References">
|
<!-- Allow folks to build with minimal dependencies (though they will need to provide their own Version data) -->
|
||||||
<PackageReference Include="MinVer" PrivateAssets="All" Version="4.2.0" />
|
<ItemGroup Label="Build Tools Package References" Condition="'$(UseBuildTimeTools)' != 'false'">
|
||||||
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.1.1" />
|
<PackageReference Include="MinVer" PrivateAssets="All" Version="4.3.0" />
|
||||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435">
|
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="8.0.0" />
|
||||||
|
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
|
||||||
<PrivateAssets>All</PrivateAssets>
|
<PrivateAssets>All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Roslynator.Analyzers" Version="4.1.2">
|
<PackageReference Include="Roslynator.Analyzers" Version="4.12.1">
|
||||||
<PrivateAssets>All</PrivateAssets>
|
<PrivateAssets>All</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<Target Name="Versioning" BeforeTargets="MinVer">
|
<Target Name="Versioning" BeforeTargets="MinVer">
|
||||||
<PropertyGroup Label="Build">
|
<PropertyGroup Label="Build">
|
||||||
<MinVerDefaultPreReleasePhase>preview</MinVerDefaultPreReleasePhase>
|
<MinVerDefaultPreReleaseIdentifiers>preview.0</MinVerDefaultPreReleaseIdentifiers>
|
||||||
<MinVerVerbosity>normal</MinVerVerbosity>
|
<MinVerVerbosity>normal</MinVerVerbosity>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Target>
|
</Target>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Microsoft.CodeAnalysis.Editing;
|
using Microsoft.CodeAnalysis.Editing;
|
||||||
using Microsoft.CodeAnalysis.Simplification;
|
using Microsoft.CodeAnalysis.Simplification;
|
||||||
|
|
||||||
namespace Spectre.Console.Analyzer.CodeActions;
|
namespace Spectre.Console.Analyzer.CodeActions;
|
||||||
@@ -32,171 +32,171 @@ public class SwitchToAnsiConsoleAction : CodeAction
|
|||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
|
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
|
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
|
||||||
var compilation = editor.SemanticModel.Compilation;
|
var compilation = editor.SemanticModel.Compilation;
|
||||||
|
|
||||||
var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation;
|
var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation;
|
||||||
if (operation == null)
|
if (operation == null)
|
||||||
{
|
{
|
||||||
return _document;
|
return _document;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is an IAnsiConsole passed into the method then we'll use it.
|
// If there is an IAnsiConsole passed into the method then we'll use it.
|
||||||
// otherwise we'll check for a field level instance.
|
// otherwise we'll check for a field level instance.
|
||||||
// if neither of those exist we'll fall back to the static param.
|
// if neither of those exist we'll fall back to the static param.
|
||||||
var spectreConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
var spectreConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||||
var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole");
|
var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole");
|
||||||
|
|
||||||
ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol;
|
ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol;
|
||||||
if (iansiConsoleSymbol != null)
|
if (iansiConsoleSymbol != null)
|
||||||
{
|
{
|
||||||
var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition);
|
var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition);
|
||||||
|
|
||||||
foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start))
|
foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start))
|
||||||
{
|
{
|
||||||
// LookupSymbols check the accessibility of the symbol, but it can
|
// LookupSymbols check the accessibility of the symbol, but it can
|
||||||
// suggest instance members when the current context is static.
|
// suggest instance members when the current context is static.
|
||||||
var symbolType = symbol switch
|
var symbolType = symbol switch
|
||||||
{
|
{
|
||||||
IParameterSymbol parameter => parameter.Type,
|
IParameterSymbol parameter => parameter.Type,
|
||||||
IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type,
|
IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type,
|
||||||
IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type,
|
IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type,
|
||||||
ILocalSymbol local => local.Type,
|
ILocalSymbol local => local.Type,
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Locals can be returned even if there are not valid in the current context. For instance,
|
// 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
|
// it can return locals declared after the current location. Or it can return locals that
|
||||||
// should not be accessible in a static local function.
|
// should not be accessible in a static local function.
|
||||||
//
|
//
|
||||||
// void Sample()
|
// void Sample()
|
||||||
// {
|
// {
|
||||||
// int local = 0;
|
// int local = 0;
|
||||||
// static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it
|
// 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.
|
// 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)
|
if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter)
|
||||||
{
|
{
|
||||||
var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start;
|
var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start;
|
||||||
|
|
||||||
// The local is not part of the source tree
|
// The local is not part of the source tree
|
||||||
if (localPosition == null)
|
if (localPosition == null)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The local is declared after the current expression
|
// The local is declared after the current expression
|
||||||
if (localPosition > _originalInvocation.Span.Start)
|
if (localPosition > _originalInvocation.Span.Start)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The local is declared outside the static local function
|
// The local is declared outside the static local function
|
||||||
if (isInStaticContext && localPosition < parentStaticMemberStartPosition)
|
if (isInStaticContext && localPosition < parentStaticMemberStartPosition)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol))
|
if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol))
|
||||||
{
|
{
|
||||||
accessibleConsoleSymbol = symbol;
|
accessibleConsoleSymbol = symbol;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accessibleConsoleSymbol == null)
|
if (accessibleConsoleSymbol == null)
|
||||||
{
|
{
|
||||||
return _document;
|
return _document;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the original invocation
|
// Replace the original invocation
|
||||||
var generator = editor.Generator;
|
var generator = editor.Generator;
|
||||||
var consoleExpression = accessibleConsoleSymbol switch
|
var consoleExpression = accessibleConsoleSymbol switch
|
||||||
{
|
{
|
||||||
ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
|
ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
|
||||||
_ => generator.IdentifierName(accessibleConsoleSymbol.Name),
|
_ => generator.IdentifierName(accessibleConsoleSymbol.Name),
|
||||||
};
|
};
|
||||||
|
|
||||||
var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments)
|
var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments)
|
||||||
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia())
|
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia())
|
||||||
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia());
|
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia());
|
||||||
|
|
||||||
editor.ReplaceNode(_originalInvocation, newExpression);
|
editor.ReplaceNode(_originalInvocation, newExpression);
|
||||||
|
|
||||||
return editor.GetChangedDocument();
|
return editor.GetChangedDocument();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol)
|
private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol)
|
||||||
{
|
{
|
||||||
if (symbol == null)
|
if (symbol == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol))
|
if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var iface in symbol.AllInterfaces)
|
foreach (var iface in symbol.AllInterfaces)
|
||||||
{
|
{
|
||||||
if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol))
|
if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition)
|
private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition)
|
||||||
{
|
{
|
||||||
// Local functions can be nested, and an instance local function can be declared
|
// 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
|
// in a static local function. So, you need to continue to check ancestors when a
|
||||||
// local function is not static.
|
// local function is not static.
|
||||||
foreach (var member in operation.Syntax.Ancestors())
|
foreach (var member in operation.Syntax.Ancestors())
|
||||||
{
|
{
|
||||||
if (member is LocalFunctionStatementSyntax localFunction)
|
if (member is LocalFunctionStatementSyntax localFunction)
|
||||||
{
|
{
|
||||||
var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken);
|
var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken);
|
||||||
if (symbol != null && symbol.IsStatic)
|
if (symbol != null && symbol.IsStatic)
|
||||||
{
|
{
|
||||||
parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start;
|
parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (member is LambdaExpressionSyntax lambdaExpression)
|
else if (member is LambdaExpressionSyntax lambdaExpression)
|
||||||
{
|
{
|
||||||
var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol;
|
var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol;
|
||||||
if (symbol != null && symbol.IsStatic)
|
if (symbol != null && symbol.IsStatic)
|
||||||
{
|
{
|
||||||
parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start;
|
parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (member is AnonymousMethodExpressionSyntax anonymousMethod)
|
else if (member is AnonymousMethodExpressionSyntax anonymousMethod)
|
||||||
{
|
{
|
||||||
var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol;
|
var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol;
|
||||||
if (symbol != null && symbol.IsStatic)
|
if (symbol != null && symbol.IsStatic)
|
||||||
{
|
{
|
||||||
parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start;
|
parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (member is MethodDeclarationSyntax methodDeclaration)
|
else if (member is MethodDeclarationSyntax methodDeclaration)
|
||||||
{
|
{
|
||||||
parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start;
|
parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start;
|
||||||
|
|
||||||
var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken);
|
var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken);
|
||||||
return symbol != null && symbol.IsStatic;
|
return symbol != null && symbol.IsStatic;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
parentStaticMemberStartPosition = -1;
|
parentStaticMemberStartPosition = -1;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,20 +8,21 @@
|
|||||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<NoPackageAnalysis>true</NoPackageAnalysis>
|
<NoPackageAnalysis>true</NoPackageAnalysis>
|
||||||
|
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
|
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
|
||||||
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.2.0" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.CodeAnalysis" Version="4.8.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" PrivateAssets="all" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.2.0" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0" PrivateAssets="all" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="bin\Debug\netstandard2.0\\Spectre.Console.Analyzer.dll" />
|
<None Remove="bin\Debug\netstandard2.0\\Spectre.Console.Analyzer.dll" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Spectre.Console.Cli.Internal.Configuration;
|
using Spectre.Console.Cli.Internal.Configuration;
|
||||||
|
|
||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -102,7 +102,7 @@ public sealed class CommandApp : ICommandApp
|
|||||||
|
|
||||||
if (_configurator.Settings.ExceptionHandler != null)
|
if (_configurator.Settings.ExceptionHandler != null)
|
||||||
{
|
{
|
||||||
return _configurator.Settings.ExceptionHandler(ex);
|
return _configurator.Settings.ExceptionHandler(ex, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render the exception.
|
// Render the exception.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Spectre.Console.Cli.Internal.Configuration;
|
using Spectre.Console.Cli.Internal.Configuration;
|
||||||
|
|
||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -49,8 +49,8 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
|
|||||||
public Task<int> RunAsync(IEnumerable<string> args)
|
public Task<int> RunAsync(IEnumerable<string> args)
|
||||||
{
|
{
|
||||||
return _app.RunAsync(args);
|
return _app.RunAsync(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal Configurator GetConfigurator()
|
internal Configurator GetConfigurator()
|
||||||
{
|
{
|
||||||
return _app.GetConfigurator();
|
return _app.GetConfigurator();
|
||||||
|
|||||||
@@ -13,6 +13,11 @@ public sealed class CommandContext
|
|||||||
/// </value>
|
/// </value>
|
||||||
public IRemainingArguments Remaining { get; }
|
public IRemainingArguments Remaining { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all the arguments that were passed to the applicaton.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<string> Arguments { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the command.
|
/// Gets the name of the command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -32,11 +37,17 @@ public sealed class CommandContext
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CommandContext"/> class.
|
/// Initializes a new instance of the <see cref="CommandContext"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="arguments">All arguments that were passed to the application.</param>
|
||||||
/// <param name="remaining">The remaining arguments.</param>
|
/// <param name="remaining">The remaining arguments.</param>
|
||||||
/// <param name="name">The command name.</param>
|
/// <param name="name">The command name.</param>
|
||||||
/// <param name="data">The command data.</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));
|
Remaining = remaining ?? throw new System.ArgumentNullException(nameof(remaining));
|
||||||
Name = name ?? throw new System.ArgumentNullException(nameof(name));
|
Name = name ?? throw new System.ArgumentNullException(nameof(name));
|
||||||
Data = data;
|
Data = data;
|
||||||
|
|||||||
@@ -5,40 +5,40 @@ namespace Spectre.Console.Cli;
|
|||||||
/// and <see cref="IConfigurator{TSettings}"/>.
|
/// and <see cref="IConfigurator{TSettings}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class ConfiguratorExtensions
|
public static class ConfiguratorExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the help provider for the application.
|
/// Sets the help provider for the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configurator">The configurator.</param>
|
/// <param name="configurator">The configurator.</param>
|
||||||
/// <param name="helpProvider">The help provider to use.</param>
|
/// <param name="helpProvider">The help provider to use.</param>
|
||||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||||
public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHelpProvider helpProvider)
|
public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHelpProvider helpProvider)
|
||||||
{
|
{
|
||||||
if (configurator == null)
|
if (configurator == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(configurator));
|
throw new ArgumentNullException(nameof(configurator));
|
||||||
}
|
}
|
||||||
|
|
||||||
configurator.SetHelpProvider(helpProvider);
|
configurator.SetHelpProvider(helpProvider);
|
||||||
return configurator;
|
return configurator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the help provider for the application.
|
/// Sets the help provider for the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configurator">The configurator.</param>
|
/// <param name="configurator">The configurator.</param>
|
||||||
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
|
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
|
||||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||||
public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator)
|
public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator)
|
||||||
where T : IHelpProvider
|
where T : IHelpProvider
|
||||||
{
|
{
|
||||||
if (configurator == null)
|
if (configurator == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(configurator));
|
throw new ArgumentNullException(nameof(configurator));
|
||||||
}
|
}
|
||||||
|
|
||||||
configurator.SetHelpProvider<T>();
|
configurator.SetHelpProvider<T>();
|
||||||
return configurator;
|
return configurator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -82,7 +82,7 @@ public static class ConfiguratorExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overrides the auto-detected version of the application.
|
/// Sets the version of the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configurator">The configurator.</param>
|
/// <param name="configurator">The configurator.</param>
|
||||||
/// <param name="version">The version of application.</param>
|
/// <param name="version">The version of application.</param>
|
||||||
@@ -98,6 +98,25 @@ public static class ConfiguratorExtensions
|
|||||||
return configurator;
|
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>
|
/// <summary>
|
||||||
/// Hides the <c>DEFAULT</c> column that lists default values coming from the
|
/// Hides the <c>DEFAULT</c> column that lists default values coming from the
|
||||||
/// <see cref="DefaultValueAttribute"/> in the options help text.
|
/// <see cref="DefaultValueAttribute"/> in the options help text.
|
||||||
@@ -229,7 +248,7 @@ public static class ConfiguratorExtensions
|
|||||||
throw new ArgumentNullException(nameof(configurator));
|
throw new ArgumentNullException(nameof(configurator));
|
||||||
}
|
}
|
||||||
|
|
||||||
configurator.Settings.Interceptor = interceptor;
|
configurator.Settings.Registrar.RegisterInstance<ICommandInterceptor>(interceptor);
|
||||||
return configurator;
|
return configurator;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,11 +343,16 @@ public static class ConfiguratorExtensions
|
|||||||
/// <param name="func">The delegate to execute as part of command execution.</param>
|
/// <param name="func">The delegate to execute as part of command execution.</param>
|
||||||
/// <returns>A command configurator that can be used to configure the command further.</returns>
|
/// <returns>A command configurator that can be used to configure the command further.</returns>
|
||||||
public static ICommandConfigurator AddDelegate<TSettings>(
|
public static ICommandConfigurator AddDelegate<TSettings>(
|
||||||
this IConfigurator<TSettings> configurator,
|
this IConfigurator<TSettings>? configurator,
|
||||||
string name,
|
string name,
|
||||||
Func<CommandContext, int> func)
|
Func<CommandContext, int> func)
|
||||||
where TSettings : CommandSettings
|
where TSettings : CommandSettings
|
||||||
{
|
{
|
||||||
|
if (typeof(TSettings).IsAbstract)
|
||||||
|
{
|
||||||
|
AddDelegate(configurator as IConfigurator<EmptyCommandSettings>, name, func);
|
||||||
|
}
|
||||||
|
|
||||||
if (configurator == null)
|
if (configurator == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(configurator));
|
throw new ArgumentNullException(nameof(configurator));
|
||||||
@@ -367,11 +391,11 @@ public static class ConfiguratorExtensions
|
|||||||
/// <param name="configurator">The configurator.</param>
|
/// <param name="configurator">The configurator.</param>
|
||||||
/// <param name="exceptionHandler">The Action that handles the exception.</param>
|
/// <param name="exceptionHandler">The Action that handles the exception.</param>
|
||||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
/// <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;
|
return -1;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -382,7 +406,7 @@ public static class ConfiguratorExtensions
|
|||||||
/// <param name="configurator">The configurator.</param>
|
/// <param name="configurator">The configurator.</param>
|
||||||
/// <param name="exceptionHandler">The Action that handles the exception.</param>
|
/// <param name="exceptionHandler">The Action that handles the exception.</param>
|
||||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
/// <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)
|
if (configurator == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using Spectre.Console.Cli.Resources;
|
|
||||||
|
|
||||||
namespace Spectre.Console.Cli.Help;
|
namespace Spectre.Console.Cli.Help;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -10,23 +8,32 @@ namespace Spectre.Console.Cli.Help;
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class HelpProvider : IHelpProvider
|
public class HelpProvider : IHelpProvider
|
||||||
{
|
{
|
||||||
private HelpProviderResources resources;
|
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>
|
||||||
/// </summary>
|
/// Gets a value indicating how many examples from direct children to show in the help text.
|
||||||
protected virtual int MaximumIndirectExamples { get; }
|
/// </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>
|
||||||
/// </summary>
|
/// Gets a value indicating whether any default values for command options are shown in the help text.
|
||||||
protected virtual bool ShowOptionDefaultValues { get; }
|
/// </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>
|
||||||
/// </summary>
|
/// Gets a value indicating whether a trailing period of a command description is trimmed in the help text.
|
||||||
protected virtual bool TrimTrailingPeriod { get; }
|
/// </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
|
private sealed class HelpArgument
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
@@ -34,7 +41,7 @@ public class HelpProvider : IHelpProvider
|
|||||||
public bool Required { get; }
|
public bool Required { get; }
|
||||||
public string? Description { 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;
|
Name = name;
|
||||||
Position = position;
|
Position = position;
|
||||||
@@ -61,7 +68,7 @@ public class HelpProvider : IHelpProvider
|
|||||||
public string? Description { get; }
|
public string? Description { get; }
|
||||||
public object? DefaultValue { 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;
|
Short = @short;
|
||||||
Long = @long;
|
Long = @long;
|
||||||
@@ -71,17 +78,27 @@ public class HelpProvider : IHelpProvider
|
|||||||
DefaultValue = defaultValue;
|
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>();
|
var parameters = new List<HelpOption>
|
||||||
parameters.Add(new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null));
|
{
|
||||||
|
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
|
// Version information applies to the entire application
|
||||||
if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false))
|
// 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
|
||||||
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
|
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 =>
|
parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o =>
|
||||||
@@ -92,55 +109,59 @@ public class HelpProvider : IHelpProvider
|
|||||||
?? Array.Empty<HelpOption>());
|
?? Array.Empty<HelpOption>());
|
||||||
return parameters;
|
return parameters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="HelpProvider"/> class.
|
/// Initializes a new instance of the <see cref="HelpProvider"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="settings">The command line application settings used for configuration.</param>
|
/// <param name="settings">The command line application settings used for configuration.</param>
|
||||||
public HelpProvider(ICommandAppSettings settings)
|
public HelpProvider(ICommandAppSettings settings)
|
||||||
{
|
{
|
||||||
this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues;
|
this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues;
|
||||||
this.MaximumIndirectExamples = settings.MaximumIndirectExamples;
|
this.MaximumIndirectExamples = settings.MaximumIndirectExamples;
|
||||||
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;
|
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;
|
||||||
|
|
||||||
resources = new HelpProviderResources(settings.Culture);
|
// 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;
|
||||||
/// <inheritdoc/>
|
|
||||||
|
resources = new HelpProviderResources(settings.Culture);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
|
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
|
||||||
{
|
{
|
||||||
var result = new List<IRenderable>();
|
var result = new List<IRenderable>();
|
||||||
|
|
||||||
result.AddRange(GetHeader(model, command));
|
result.AddRange(GetHeader(model, command));
|
||||||
result.AddRange(GetDescription(model, command));
|
result.AddRange(GetDescription(model, command));
|
||||||
result.AddRange(GetUsage(model, command));
|
result.AddRange(GetUsage(model, command));
|
||||||
result.AddRange(GetExamples(model, command));
|
result.AddRange(GetExamples(model, command));
|
||||||
result.AddRange(GetArguments(model, command));
|
result.AddRange(GetArguments(model, command));
|
||||||
result.AddRange(GetOptions(model, command));
|
result.AddRange(GetOptions(model, command));
|
||||||
result.AddRange(GetCommands(model, command));
|
result.AddRange(GetCommands(model, command));
|
||||||
result.AddRange(GetFooter(model, command));
|
result.AddRange(GetFooter(model, command));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the header for the help information.
|
/// Gets the header for the help information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The command model to write help for.</param>
|
/// <param name="model">The command model to write help for.</param>
|
||||||
/// <param name="command">The command for which to write help information (optional).</param>
|
/// <param name="command">The command for which to write help information (optional).</param>
|
||||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||||
public virtual IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
|
public virtual IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
|
||||||
{
|
{
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the description section of the help information.
|
/// Gets the description section of the help information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The command model to write help for.</param>
|
/// <param name="model">The command model to write help for.</param>
|
||||||
/// <param name="command">The command for which to write help information (optional).</param>
|
/// <param name="command">The command for which to write help information (optional).</param>
|
||||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||||
public virtual IEnumerable<IRenderable> GetDescription(ICommandModel model, ICommandInfo? command)
|
public virtual IEnumerable<IRenderable> GetDescription(ICommandModel model, ICommandInfo? command)
|
||||||
{
|
{
|
||||||
if (command?.Description == null)
|
if (command?.Description == null)
|
||||||
@@ -148,30 +169,30 @@ public class HelpProvider : IHelpProvider
|
|||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
var composer = new Composer();
|
var composer = NewComposer();
|
||||||
composer.Style("yellow", $"{resources.Description}:").LineBreak();
|
composer.Style(helpStyles?.Description?.Header ?? Style.Plain, $"{resources.Description}:").LineBreak();
|
||||||
composer.Text(command.Description).LineBreak();
|
composer.Text(command.Description).LineBreak();
|
||||||
yield return composer.LineBreak();
|
yield return composer.LineBreak();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the usage section of the help information.
|
/// Gets the usage section of the help information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The command model to write help for.</param>
|
/// <param name="model">The command model to write help for.</param>
|
||||||
/// <param name="command">The command for which to write help information (optional).</param>
|
/// <param name="command">The command for which to write help information (optional).</param>
|
||||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||||
public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandInfo? command)
|
public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandInfo? command)
|
||||||
{
|
{
|
||||||
var composer = new Composer();
|
var composer = NewComposer();
|
||||||
composer.Style("yellow", $"{resources.Usage}:").LineBreak();
|
composer.Style(helpStyles?.Usage?.Header ?? Style.Plain, $"{resources.Usage}:").LineBreak();
|
||||||
composer.Tab().Text(model.ApplicationName);
|
composer.Tab().Text(model.ApplicationName);
|
||||||
|
|
||||||
var parameters = new List<string>();
|
var parameters = new List<Composer>();
|
||||||
|
|
||||||
if (command == null)
|
if (command == null)
|
||||||
{
|
{
|
||||||
parameters.Add($"[grey][[{resources.Options}]][/]");
|
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]"));
|
||||||
parameters.Add($"[aqua]<{resources.Command}>[/]");
|
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.Command}>"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -183,11 +204,11 @@ public class HelpProvider : IHelpProvider
|
|||||||
{
|
{
|
||||||
if (isCurrent)
|
if (isCurrent)
|
||||||
{
|
{
|
||||||
parameters.Add($"[underline]{current.Name.EscapeMarkup()}[/]");
|
parameters.Add(NewComposer().Style(helpStyles?.Usage?.CurrentCommand ?? Style.Plain, $"{current.Name}"));
|
||||||
}
|
}
|
||||||
else
|
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>()
|
foreach (var argument in current.Parameters.OfType<ICommandArgument>()
|
||||||
.Where(a => a.Required).OrderBy(a => a.Position).ToArray())
|
.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)
|
foreach (var optionalArgument in optionalArguments)
|
||||||
{
|
{
|
||||||
parameters.Add($"[silver][[{optionalArgument.Value.EscapeMarkup()}]][/]");
|
parameters.Add(NewComposer().Style(helpStyles?.Usage?.OptionalArgument ?? Style.Plain, $"[{optionalArgument.Value}]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCurrent)
|
if (isCurrent)
|
||||||
{
|
{
|
||||||
parameters.Add($"[grey][[{resources.Options}]][/]");
|
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Options ?? Style.Plain, $"[{resources.Options}]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command.IsBranch && command.DefaultCommand == null)
|
if (command.IsBranch && command.DefaultCommand == null)
|
||||||
{
|
{
|
||||||
// The user must specify the command
|
// The user must specify the command
|
||||||
parameters.Add($"[aqua]<{resources.Command}>[/]");
|
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"<{resources.Command}>"));
|
||||||
}
|
}
|
||||||
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
|
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
|
||||||
{
|
{
|
||||||
// We are on a branch with a default command
|
// We are on a branch with a default command
|
||||||
// The user can optionally specify the command
|
// The user can optionally specify the command
|
||||||
parameters.Add($"[aqua][[{resources.Command}]][/]");
|
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
|
||||||
}
|
}
|
||||||
else if (command.IsDefaultCommand)
|
else if (command.IsDefaultCommand)
|
||||||
{
|
{
|
||||||
var commands = model.Commands.Where(x => !x.IsHidden && !x.IsDefaultCommand).ToList();
|
var commands = model.Commands.Where(x => !x.IsHidden && !x.IsDefaultCommand).ToList();
|
||||||
|
|
||||||
if (commands.Count > 0)
|
if (commands.Count > 0)
|
||||||
{
|
{
|
||||||
// Commands other than the default are present
|
// Commands other than the default are present
|
||||||
// So make these optional in the usage statement
|
// So make these optional in the usage statement
|
||||||
parameters.Add($"[aqua][[{resources.Command}]][/]");
|
parameters.Add(NewComposer().Style(helpStyles?.Usage?.Command ?? Style.Plain, $"[{resources.Command}]"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
composer.Join(" ", parameters);
|
composer.Join(" ", parameters);
|
||||||
composer.LineBreak();
|
composer.LineBreak();
|
||||||
|
|
||||||
return new[]
|
return new[] { composer };
|
||||||
{
|
}
|
||||||
composer,
|
|
||||||
};
|
/// <summary>
|
||||||
}
|
/// Gets the examples section of the help information.
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
/// <param name="model">The command model to write help for.</param>
|
||||||
/// Gets the examples section of the help information.
|
/// <param name="command">The command for which to write help information (optional).</param>
|
||||||
/// </summary>
|
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||||
/// <param name="model">The command model to write help for.</param>
|
/// <remarks>
|
||||||
/// <param name="command">The command for which to write help information (optional).</param>
|
/// Examples from the command's direct children are used
|
||||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
/// if no examples have been set on the specified command or model.
|
||||||
/// <remarks>
|
/// </remarks>
|
||||||
/// Examples from the command's direct children are used
|
public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, ICommandInfo? command)
|
||||||
/// if no examples have been set on the specified command or model.
|
|
||||||
/// </remarks>
|
|
||||||
public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, ICommandInfo? command)
|
|
||||||
{
|
{
|
||||||
var maxExamples = int.MaxValue;
|
var maxExamples = int.MaxValue;
|
||||||
|
|
||||||
@@ -272,12 +290,12 @@ public class HelpProvider : IHelpProvider
|
|||||||
// make sure that we limit the number of examples.
|
// make sure that we limit the number of examples.
|
||||||
maxExamples = MaximumIndirectExamples;
|
maxExamples = MaximumIndirectExamples;
|
||||||
|
|
||||||
// Start at the current command (if exists)
|
// Start at the current command (if exists)
|
||||||
// or alternatively commence at the model.
|
// or alternatively commence at the model.
|
||||||
var commandContainer = command ?? (ICommandContainer)model;
|
var commandContainer = command ?? (ICommandContainer)model;
|
||||||
var queue = new Queue<ICommandContainer>(new[] { commandContainer });
|
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.
|
// As soon as a node contains commands, bail.
|
||||||
while (queue.Count > 0)
|
while (queue.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -302,14 +320,14 @@ public class HelpProvider : IHelpProvider
|
|||||||
|
|
||||||
if (Math.Min(maxExamples, examples.Count) > 0)
|
if (Math.Min(maxExamples, examples.Count) > 0)
|
||||||
{
|
{
|
||||||
var composer = new Composer();
|
var composer = NewComposer();
|
||||||
composer.LineBreak();
|
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++)
|
for (var index = 0; index < Math.Min(maxExamples, examples.Count); index++)
|
||||||
{
|
{
|
||||||
var args = string.Join(" ", examples[index]);
|
var args = string.Join(" ", examples[index]);
|
||||||
composer.Tab().Text(model.ApplicationName).Space().Style("grey", args);
|
composer.Tab().Text(model.ApplicationName).Space().Style(helpStyles?.Examples?.Arguments ?? Style.Plain, args);
|
||||||
composer.LineBreak();
|
composer.LineBreak();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,14 +335,14 @@ public class HelpProvider : IHelpProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Array.Empty<IRenderable>();
|
return Array.Empty<IRenderable>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the arguments section of the help information.
|
/// Gets the arguments section of the help information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The command model to write help for.</param>
|
/// <param name="model">The command model to write help for.</param>
|
||||||
/// <param name="command">The command for which to write help information (optional).</param>
|
/// <param name="command">The command for which to write help information (optional).</param>
|
||||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||||
public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, ICommandInfo? command)
|
public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, ICommandInfo? command)
|
||||||
{
|
{
|
||||||
var arguments = HelpArgument.Get(command);
|
var arguments = HelpArgument.Get(command);
|
||||||
@@ -334,11 +352,9 @@ public class HelpProvider : IHelpProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
var result = new List<IRenderable>
|
var result = new List<IRenderable>
|
||||||
{
|
{
|
||||||
new Markup(Environment.NewLine),
|
NewComposer().LineBreak().Style(helpStyles?.Arguments?.Header ?? Style.Plain, $"{resources.Arguments}:").LineBreak(),
|
||||||
new Markup($"[yellow]{resources.Arguments}:[/]"),
|
};
|
||||||
new Markup(Environment.NewLine),
|
|
||||||
};
|
|
||||||
|
|
||||||
var grid = new Grid();
|
var grid = new Grid();
|
||||||
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
|
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))
|
foreach (var argument in arguments.Where(x => x.Required).OrderBy(x => x.Position))
|
||||||
{
|
{
|
||||||
grid.AddRow(
|
grid.AddRow(
|
||||||
$"[silver]<{argument.Name.EscapeMarkup()}>[/]",
|
NewComposer().Style(helpStyles?.Arguments?.RequiredArgument ?? Style.Plain, $"<{argument.Name}>"),
|
||||||
argument.Description?.TrimEnd('.') ?? " ");
|
NewComposer().Text(argument.Description?.TrimEnd('.') ?? " "));
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var argument in arguments.Where(x => !x.Required).OrderBy(x => x.Position))
|
foreach (var argument in arguments.Where(x => !x.Required).OrderBy(x => x.Position))
|
||||||
{
|
{
|
||||||
grid.AddRow(
|
grid.AddRow(
|
||||||
$"[grey][[{argument.Name.EscapeMarkup()}]][/]",
|
NewComposer().Style(helpStyles?.Arguments?.OptionalArgument ?? Style.Plain, $"[{argument.Name}]"),
|
||||||
argument.Description?.TrimEnd('.') ?? " ");
|
NewComposer().Text(argument.Description?.TrimEnd('.') ?? " "));
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Add(grid);
|
result.Add(grid);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the options section of the help information.
|
/// Gets the options section of the help information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The command model to write help for.</param>
|
/// <param name="model">The command model to write help for.</param>
|
||||||
/// <param name="command">The command for which to write help information (optional).</param>
|
/// <param name="command">The command for which to write help information (optional).</param>
|
||||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||||
public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommandInfo? command)
|
public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommandInfo? command)
|
||||||
{
|
{
|
||||||
// Collect all options into a single structure.
|
// Collect all options into a single structure.
|
||||||
var parameters = HelpOption.Get(command, resources);
|
var parameters = HelpOption.Get(model, command, resources);
|
||||||
if (parameters.Count == 0)
|
if (parameters.Count == 0)
|
||||||
{
|
{
|
||||||
return Array.Empty<IRenderable>();
|
return Array.Empty<IRenderable>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new List<IRenderable>
|
var result = new List<IRenderable>
|
||||||
{
|
{
|
||||||
new Markup(Environment.NewLine),
|
NewComposer().LineBreak().Style(helpStyles?.Options?.Header ?? Style.Plain, $"{resources.Options}:").LineBreak(),
|
||||||
new Markup($"[yellow]{resources.Options}:[/]"),
|
};
|
||||||
new Markup(Environment.NewLine),
|
|
||||||
};
|
|
||||||
|
|
||||||
var helpOptions = parameters.ToArray();
|
var helpOptions = parameters.ToArray();
|
||||||
var defaultValueColumn = ShowOptionDefaultValues && helpOptions.Any(e => e.DefaultValue != null);
|
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) });
|
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)
|
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)
|
foreach (var option in helpOptions)
|
||||||
{
|
{
|
||||||
var columns = new List<string> { GetOptionParts(option) };
|
var columns = new List<IRenderable>() { GetOptionParts(option) };
|
||||||
|
|
||||||
if (defaultValueColumn)
|
if (defaultValueColumn)
|
||||||
{
|
{
|
||||||
static string Bold(object obj) => $"[bold]{obj.ToString().EscapeMarkup()}[/]";
|
columns.Add(GetDefaultValueForOption(option.DefaultValue));
|
||||||
|
|
||||||
var defaultValue = option.DefaultValue switch
|
|
||||||
{
|
|
||||||
null => " ",
|
|
||||||
"" => " ",
|
|
||||||
Array { Length: 0 } => " ",
|
|
||||||
Array array => string.Join(", ", array.Cast<object>().Select(Bold)),
|
|
||||||
_ => Bold(option.DefaultValue),
|
|
||||||
};
|
|
||||||
columns.Add(defaultValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
columns.Add(option.Description?.TrimEnd('.') ?? " ");
|
columns.Add(NewComposer().Text(option.Description?.TrimEnd('.') ?? " "));
|
||||||
|
|
||||||
grid.AddRow(columns.ToArray());
|
grid.AddRow(columns.ToArray());
|
||||||
}
|
}
|
||||||
@@ -469,18 +436,18 @@ public class HelpProvider : IHelpProvider
|
|||||||
result.Add(grid);
|
result.Add(grid);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the commands section of the help information.
|
/// Gets the commands section of the help information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The command model to write help for.</param>
|
/// <param name="model">The command model to write help for.</param>
|
||||||
/// <param name="command">The command for which to write help information (optional).</param>
|
/// <param name="command">The command for which to write help information (optional).</param>
|
||||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||||
public virtual IEnumerable<IRenderable> GetCommands(ICommandModel model, ICommandInfo? command)
|
public virtual IEnumerable<IRenderable> GetCommands(ICommandModel model, ICommandInfo? command)
|
||||||
{
|
{
|
||||||
var commandContainer = command ?? (ICommandContainer)model;
|
var commandContainer = command ?? (ICommandContainer)model;
|
||||||
bool isDefaultCommand = command?.IsDefaultCommand ?? false;
|
bool isDefaultCommand = command?.IsDefaultCommand ?? false;
|
||||||
|
|
||||||
var commands = isDefaultCommand ? model.Commands : commandContainer.Commands;
|
var commands = isDefaultCommand ? model.Commands : commandContainer.Commands;
|
||||||
commands = commands.Where(x => !x.IsHidden).ToList();
|
commands = commands.Where(x => !x.IsHidden).ToList();
|
||||||
@@ -491,11 +458,9 @@ public class HelpProvider : IHelpProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
var result = new List<IRenderable>
|
var result = new List<IRenderable>
|
||||||
{
|
{
|
||||||
new Markup(Environment.NewLine),
|
NewComposer().LineBreak().Style(helpStyles?.Commands?.Header ?? Style.Plain, $"{resources.Commands}:").LineBreak(),
|
||||||
new Markup($"[yellow]{resources.Commands}:[/]"),
|
};
|
||||||
new Markup(Environment.NewLine),
|
|
||||||
};
|
|
||||||
|
|
||||||
var grid = new Grid();
|
var grid = new Grid();
|
||||||
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
|
grid.AddColumn(new GridColumn { Padding = new Padding(4, 4), NoWrap = true });
|
||||||
@@ -503,43 +468,102 @@ public class HelpProvider : IHelpProvider
|
|||||||
|
|
||||||
foreach (var child in commands)
|
foreach (var child in commands)
|
||||||
{
|
{
|
||||||
var arguments = new Composer();
|
var arguments = NewComposer();
|
||||||
arguments.Style("silver", child.Name.EscapeMarkup());
|
arguments.Style(helpStyles?.Commands?.ChildCommand ?? Style.Plain, child.Name);
|
||||||
arguments.Space();
|
arguments.Space();
|
||||||
|
|
||||||
foreach (var argument in HelpArgument.Get(child).Where(a => a.Required))
|
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();
|
arguments.Space();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TrimTrailingPeriod)
|
if (TrimTrailingPeriod)
|
||||||
{
|
{
|
||||||
grid.AddRow(
|
grid.AddRow(
|
||||||
arguments.ToString().TrimEnd(),
|
NewComposer().Text(arguments.ToString().TrimEnd()),
|
||||||
child.Description?.TrimEnd('.') ?? " ");
|
NewComposer().Text(child.Description?.TrimEnd('.') ?? " "));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
grid.AddRow(
|
grid.AddRow(
|
||||||
arguments.ToString().TrimEnd(),
|
NewComposer().Text(arguments.ToString().TrimEnd()),
|
||||||
child.Description ?? " ");
|
NewComposer().Text(child.Description ?? " "));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Add(grid);
|
result.Add(grid);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the footer for the help information.
|
/// Gets the footer for the help information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The command model to write help for.</param>
|
/// <param name="model">The command model to write help for.</param>
|
||||||
/// <param name="command">The command for which to write help information (optional).</param>
|
/// <param name="command">The command for which to write help information (optional).</param>
|
||||||
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
|
||||||
public virtual IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo? command)
|
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.
|
/// Represents a command container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICommandContainer
|
public interface ICommandContainer
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all the examples for the container.
|
/// Gets all the examples for the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,42 +1,42 @@
|
|||||||
namespace Spectre.Console.Cli.Help;
|
namespace Spectre.Console.Cli.Help;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an executable command.
|
/// Represents an executable command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICommandInfo : ICommandContainer
|
public interface ICommandInfo : ICommandContainer
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the command.
|
/// Gets the name of the command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the description of the command.
|
/// Gets the description of the command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string? Description { get; }
|
string? Description { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the command is a branch.
|
/// Gets a value indicating whether the command is a branch.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsBranch { get; }
|
bool IsBranch { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the command is the default command within its container.
|
/// Gets a value indicating whether the command is the default command within its container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsDefaultCommand { get; }
|
bool IsDefaultCommand { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the command is hidden.
|
/// Gets a value indicating whether the command is hidden.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsHidden { get; }
|
bool IsHidden { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the parameters associated with the command.
|
/// Gets the parameters associated with the command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyList<ICommandParameter> Parameters { get; }
|
IReadOnlyList<ICommandParameter> Parameters { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the parent command, if any.
|
/// Gets the parent command, if any.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
ICommandInfo? Parent { get; }
|
ICommandInfo? Parent { get; }
|
||||||
}
|
}
|
||||||
@@ -1,23 +1,23 @@
|
|||||||
namespace Spectre.Console.Cli.Help;
|
namespace Spectre.Console.Cli.Help;
|
||||||
|
|
||||||
internal static class ICommandInfoExtensions
|
internal static class ICommandInfoExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Walks up the command.Parent tree, adding each command into a list as it goes.
|
/// Walks up the command.Parent tree, adding each command into a list as it goes.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>The first command added to the list is the current (ie. this one).</remarks>
|
/// <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>
|
/// <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)
|
public static List<ICommandInfo> Flatten(this ICommandInfo commandInfo)
|
||||||
{
|
{
|
||||||
var result = new Stack<Help.ICommandInfo>();
|
var result = new Stack<Help.ICommandInfo>();
|
||||||
|
|
||||||
var current = commandInfo;
|
var current = commandInfo;
|
||||||
while (current != null)
|
while (current != null)
|
||||||
{
|
{
|
||||||
result.Push(current);
|
result.Push(current);
|
||||||
current = current.Parent;
|
current = current.Parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.ToList();
|
return result.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,4 +9,9 @@ public interface ICommandModel : ICommandContainer
|
|||||||
/// Gets the name of the application.
|
/// Gets the name of the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string ApplicationName { get; }
|
string ApplicationName { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the version of the application.
|
||||||
|
/// </summary>
|
||||||
|
string? ApplicationVersion { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,27 @@
|
|||||||
namespace Spectre.Console.Cli.Help;
|
namespace Spectre.Console.Cli.Help;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a command option.
|
/// Represents a command option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICommandOption : ICommandParameter
|
public interface ICommandOption : ICommandParameter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the long names of the option.
|
/// Gets the long names of the option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyList<string> LongNames { get; }
|
IReadOnlyList<string> LongNames { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the short names of the option.
|
/// Gets the short names of the option.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyList<string> ShortNames { get; }
|
IReadOnlyList<string> ShortNames { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the value name of the option, if applicable.
|
/// Gets the value name of the option, if applicable.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string? ValueName { get; }
|
string? ValueName { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the option value is optional.
|
/// Gets a value indicating whether the option value is optional.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool ValueIsOptional { get; }
|
bool ValueIsOptional { get; }
|
||||||
}
|
}
|
||||||
@@ -1,32 +1,32 @@
|
|||||||
namespace Spectre.Console.Cli.Help;
|
namespace Spectre.Console.Cli.Help;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a command parameter.
|
/// Represents a command parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ICommandParameter
|
public interface ICommandParameter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the parameter is a flag.
|
/// Gets a value indicating whether the parameter is a flag.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsFlag { get; }
|
bool IsFlag { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the parameter is required.
|
/// Gets a value indicating whether the parameter is required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool Required { get; }
|
bool Required { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the description of the parameter.
|
/// Gets the description of the parameter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string? Description { get; }
|
string? Description { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default value of the parameter, if specified.
|
/// Gets the default value of the parameter, if specified.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
DefaultValueAttribute? DefaultValue { get; }
|
DefaultValueAttribute? DefaultValue { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the parameter is hidden.
|
/// Gets a value indicating whether the parameter is hidden.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsHidden { get; }
|
bool IsHidden { get; }
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
namespace Spectre.Console.Cli.Help;
|
namespace Spectre.Console.Cli.Help;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The help provider interface for Spectre.Console.
|
/// The help provider interface for Spectre.Console.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Implementations of this interface are responsbile
|
/// Implementations of this interface are responsbile
|
||||||
/// for writing command help to the terminal when the
|
/// for writing command help to the terminal when the
|
||||||
/// `-h` or `--help` has been specified on the command line.
|
/// `-h` or `--help` has been specified on the command line.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public interface IHelpProvider
|
public interface IHelpProvider
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Writes help information for the application.
|
/// Writes help information for the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The command model to write help for.</param>
|
/// <param name="model">The command model to write help for.</param>
|
||||||
/// <param name="command">The command for which to write help information (optional).</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>
|
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects representing the help information.</returns>
|
||||||
IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command);
|
IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,22 +24,27 @@ public interface ICommandAppSettings
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the application version (use it to override auto-detected value).
|
/// Gets or sets the application version (use it to override auto-detected value).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
string? ApplicationVersion { get; set; }
|
string? ApplicationVersion { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating how many examples from direct children to show in the help text.
|
/// Gets or sets a value indicating how many examples from direct children to show in the help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
int MaximumIndirectExamples { get; set; }
|
int MaximumIndirectExamples { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether any default values for command options are shown in the help text.
|
/// Gets or sets a value indicating whether any default values for command options are shown in the help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool ShowOptionDefaultValues { get; set; }
|
bool ShowOptionDefaultValues { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text.
|
/// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool TrimTrailingPeriod { get; set; }
|
bool TrimTrailingPeriod { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the styles to used when rendering the help text.
|
||||||
|
/// </summary>
|
||||||
|
HelpProviderStyle? HelpProviderStyles { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the <see cref="IAnsiConsole"/>.
|
/// Gets or sets the <see cref="IAnsiConsole"/>.
|
||||||
@@ -50,6 +55,7 @@ public interface ICommandAppSettings
|
|||||||
/// Gets or sets the <see cref="ICommandInterceptor"/> used
|
/// Gets or sets the <see cref="ICommandInterceptor"/> used
|
||||||
/// to intercept settings before it's being sent to the command.
|
/// to intercept settings before it's being sent to the command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[Obsolete("Register the interceptor with the ITypeRegistrar.")]
|
||||||
ICommandInterceptor? Interceptor { get; set; }
|
ICommandInterceptor? Interceptor { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -65,14 +71,14 @@ public interface ICommandAppSettings
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not parsing is strict.
|
/// Gets or sets a value indicating whether or not parsing is strict.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool StrictParsing { get; set; }
|
bool StrictParsing { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not flags found on the commnd line
|
/// 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
|
/// that would normally result in a <see cref="CommandParseException"/> being thrown
|
||||||
/// during parsing with the message "Flags cannot be assigned a value."
|
/// during parsing with the message "Flags cannot be assigned a value."
|
||||||
/// should instead be added to the remaining arguments collection.
|
/// should instead be added to the remaining arguments collection.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool ConvertFlagsToRemainingArguments { get; set; }
|
bool ConvertFlagsToRemainingArguments { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -90,6 +96,8 @@ public interface ICommandAppSettings
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a handler for Exceptions.
|
/// 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>
|
/// <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>
|
/// </summary>
|
||||||
public Func<Exception, int>? ExceptionHandler { get; set; }
|
public Func<Exception, ITypeResolver?, int>? ExceptionHandler { get; set; }
|
||||||
}
|
}
|
||||||
@@ -12,5 +12,25 @@ public interface ICommandInterceptor
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
|
/// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
|
||||||
/// <param name="settings">The intercepted <see cref="CommandSettings"/>.</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.
|
/// Represents a configurator.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public interface IConfigurator
|
public interface IConfigurator
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the help provider for the application.
|
/// Sets the help provider for the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="helpProvider">The help provider to use.</param>
|
/// <param name="helpProvider">The help provider to use.</param>
|
||||||
public void SetHelpProvider(IHelpProvider helpProvider);
|
public void SetHelpProvider(IHelpProvider helpProvider);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the help provider for the application.
|
/// Sets the help provider for the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
|
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
|
||||||
public void SetHelpProvider<T>()
|
public void SetHelpProvider<T>()
|
||||||
where T : IHelpProvider;
|
where T : IHelpProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the command app settings.
|
/// Gets the command app settings.
|
||||||
@@ -66,5 +66,5 @@ public interface IConfigurator
|
|||||||
/// <param name="action">The command branch configurator.</param>
|
/// <param name="action">The command branch configurator.</param>
|
||||||
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||||
IBranchConfigurator AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
|
IBranchConfigurator AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
|
||||||
where TSettings : CommandSettings;
|
where TSettings : CommandSettings;
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,7 @@ public interface IRemainingArguments
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the raw, non-parsed remaining arguments.
|
/// Gets the raw, non-parsed remaining arguments.
|
||||||
|
/// This is normally everything after the `--` delimiter.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IReadOnlyList<string> Raw { get; }
|
IReadOnlyList<string> Raw { get; }
|
||||||
}
|
}
|
||||||
@@ -1,146 +1,179 @@
|
|||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal sealed class CommandExecutor
|
internal sealed class CommandExecutor
|
||||||
{
|
{
|
||||||
private readonly ITypeRegistrar _registrar;
|
private readonly ITypeRegistrar _registrar;
|
||||||
|
|
||||||
public CommandExecutor(ITypeRegistrar registrar)
|
public CommandExecutor(ITypeRegistrar registrar)
|
||||||
{
|
{
|
||||||
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
|
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
|
||||||
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
|
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
|
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
|
||||||
{
|
{
|
||||||
if (configuration == null)
|
if (configuration == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(configuration));
|
throw new ArgumentNullException(nameof(configuration));
|
||||||
}
|
}
|
||||||
|
|
||||||
args ??= new List<string>();
|
var arguments = args.ToSafeReadOnlyList();
|
||||||
|
|
||||||
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
|
||||||
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
|
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
|
||||||
|
|
||||||
// Create the command model.
|
// Create the command model.
|
||||||
var model = CommandModelBuilder.Build(configuration);
|
var model = CommandModelBuilder.Build(configuration);
|
||||||
_registrar.RegisterInstance(typeof(CommandModel), model);
|
_registrar.RegisterInstance(typeof(CommandModel), model);
|
||||||
_registrar.RegisterDependencies(model);
|
_registrar.RegisterDependencies(model);
|
||||||
|
|
||||||
// Asking for version? Kind of a hack, but it's alright.
|
// No default command?
|
||||||
// We should probably make this a bit better in the future.
|
if (model.DefaultCommand == null)
|
||||||
if (args.Contains("-v") || args.Contains("--version"))
|
{
|
||||||
{
|
// Got at least one argument?
|
||||||
var console = configuration.Settings.Console.GetConsole();
|
var firstArgument = arguments.FirstOrDefault();
|
||||||
console.WriteLine(ResolveApplicationVersion(configuration));
|
if (firstArgument != null)
|
||||||
return 0;
|
{
|
||||||
}
|
// Asking for version? Kind of a hack, but it's alright.
|
||||||
|
// We should probably make this a bit better in the future.
|
||||||
// Parse and map the model against the arguments.
|
if (firstArgument.Equals("--version", StringComparison.OrdinalIgnoreCase) ||
|
||||||
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
|
firstArgument.Equals("-v", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
// Register the arguments with the container.
|
if (configuration.Settings.ApplicationVersion != null)
|
||||||
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
{
|
||||||
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
var console = configuration.Settings.Console.GetConsole();
|
||||||
|
console.MarkupLine(configuration.Settings.ApplicationVersion);
|
||||||
// Create the resolver.
|
return 0;
|
||||||
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);
|
// Parse and map the model against the arguments.
|
||||||
|
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, arguments);
|
||||||
// Currently the root?
|
|
||||||
if (parsedResult?.Tree == null)
|
// Register the arguments with the container.
|
||||||
{
|
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
||||||
// Display help.
|
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
||||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
|
|
||||||
return 0;
|
// Create the resolver.
|
||||||
}
|
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
|
||||||
|
{
|
||||||
// Get the command to execute.
|
// Get the registered help provider, falling back to the default provider
|
||||||
var leaf = parsedResult.Tree.GetLeafCommand();
|
// if no custom implementations have been registered.
|
||||||
if (leaf.Command.IsBranch || leaf.ShowHelp)
|
var helpProviders = resolver.Resolve(typeof(IEnumerable<IHelpProvider>)) as IEnumerable<IHelpProvider>;
|
||||||
{
|
var helpProvider = helpProviders?.LastOrDefault() ?? new HelpProvider(configuration.Settings);
|
||||||
// Branches can't be executed. Show help.
|
|
||||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
// Currently the root?
|
||||||
return leaf.ShowHelp ? 0 : 1;
|
if (parsedResult?.Tree == null)
|
||||||
}
|
{
|
||||||
|
// Display help.
|
||||||
// Is this the default and is it called without arguments when there are required arguments?
|
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
|
||||||
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
|
return 0;
|
||||||
{
|
}
|
||||||
// Display help for default command.
|
|
||||||
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
// Get the command to execute.
|
||||||
return 1;
|
var leaf = parsedResult.Tree.GetLeafCommand();
|
||||||
}
|
if (leaf.Command.IsBranch || leaf.ShowHelp)
|
||||||
|
{
|
||||||
// Create the content.
|
// Branches can't be executed. Show help.
|
||||||
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
|
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||||
|
return leaf.ShowHelp ? 0 : 1;
|
||||||
// Execute the command tree.
|
}
|
||||||
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
|
||||||
}
|
// 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))
|
||||||
|
{
|
||||||
#pragma warning disable CS8603 // Possible null reference return.
|
// Display help for default command.
|
||||||
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
|
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
|
||||||
{
|
return 1;
|
||||||
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
|
}
|
||||||
|
|
||||||
var parserContext = new CommandTreeParserContext(args, settings.ParsingMode);
|
// Create the content.
|
||||||
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
var context = new CommandContext(
|
||||||
var parsedResult = parser.Parse(parserContext, tokenizerResult);
|
arguments,
|
||||||
|
parsedResult.Remaining,
|
||||||
var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
|
leaf.Command.Name,
|
||||||
var lastParsedCommand = lastParsedLeaf?.Command;
|
leaf.Command.Data);
|
||||||
if (lastParsedLeaf != null && lastParsedCommand != null &&
|
|
||||||
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
|
// Execute the command tree.
|
||||||
lastParsedCommand.DefaultCommand != null)
|
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
||||||
{
|
}
|
||||||
// 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);
|
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IReadOnlyList<string> args)
|
||||||
|
{
|
||||||
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
|
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
|
||||||
|
|
||||||
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
|
var parserContext = new CommandTreeParserContext(args, settings.ParsingMode);
|
||||||
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
|
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||||
parsedResult = parser.Parse(parserContext, tokenizerResult);
|
var parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||||
}
|
|
||||||
|
var lastParsedLeaf = parsedResult.Tree?.GetLeafCommand();
|
||||||
return parsedResult;
|
var lastParsedCommand = lastParsedLeaf?.Command;
|
||||||
}
|
if (lastParsedLeaf != null && lastParsedCommand != null &&
|
||||||
#pragma warning restore CS8603 // Possible null reference return.
|
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
|
||||||
|
lastParsedCommand.DefaultCommand != null)
|
||||||
private static string ResolveApplicationVersion(IConfiguration configuration)
|
{
|
||||||
{
|
// Insert this branch's default command into the command line
|
||||||
return
|
// arguments and try again to see if it will parse.
|
||||||
configuration.Settings.ApplicationVersion ?? // potential override
|
var argsWithDefaultCommand = new List<string>(args);
|
||||||
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
|
|
||||||
}
|
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
|
||||||
|
|
||||||
private static Task<int> Execute(
|
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
|
||||||
CommandTree leaf,
|
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
|
||||||
CommandTree tree,
|
parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||||
CommandContext context,
|
}
|
||||||
ITypeResolver resolver,
|
|
||||||
IConfiguration configuration)
|
return parsedResult;
|
||||||
{
|
}
|
||||||
// Bind the command tree against the settings.
|
|
||||||
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
|
private static async Task<int> Execute(
|
||||||
configuration.Settings.Interceptor?.Intercept(context, settings);
|
CommandTree leaf,
|
||||||
|
CommandTree tree,
|
||||||
// Create and validate the command.
|
CommandContext context,
|
||||||
var command = leaf.CreateCommand(resolver);
|
ITypeResolver resolver,
|
||||||
var validationResult = command.Validate(context, settings);
|
IConfiguration configuration)
|
||||||
if (!validationResult.Successful)
|
{
|
||||||
{
|
try
|
||||||
throw CommandRuntimeException.ValidationFailed(validationResult);
|
{
|
||||||
}
|
// Bind the command tree against the settings.
|
||||||
|
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
|
||||||
// Execute the command.
|
var interceptors =
|
||||||
return command.Execute(context, settings);
|
((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);
|
node.SetNullableAttribute("Settings", command.SettingsType?.FullName);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(command.Description))
|
||||||
|
{
|
||||||
|
var descriptionNode = doc.CreateElement("Description");
|
||||||
|
descriptionNode.InnerText = command.Description;
|
||||||
|
node.AppendChild(descriptionNode);
|
||||||
|
}
|
||||||
|
|
||||||
// Parameters
|
// Parameters
|
||||||
if (command.Parameters.Count > 0)
|
if (command.Parameters.Count > 0)
|
||||||
{
|
{
|
||||||
@@ -103,6 +110,27 @@ internal sealed class XmlDocCommand : Command<XmlDocCommand.Settings>
|
|||||||
node.AppendChild(CreateCommandNode(doc, childCommand));
|
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;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,43 @@ internal sealed class Composer : IRenderable
|
|||||||
{
|
{
|
||||||
private readonly StringBuilder _content;
|
private readonly StringBuilder _content;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to emit the markup styles, inline, when rendering the content.
|
||||||
|
/// </summary>
|
||||||
|
private readonly bool _renderMarkup = false;
|
||||||
|
|
||||||
public Composer()
|
public Composer()
|
||||||
{
|
{
|
||||||
_content = new StringBuilder();
|
_content = new StringBuilder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Composer(bool renderMarkup)
|
||||||
|
: this()
|
||||||
|
{
|
||||||
|
_renderMarkup = renderMarkup;
|
||||||
|
}
|
||||||
|
|
||||||
public Composer Text(string text)
|
public Composer Text(string text)
|
||||||
{
|
{
|
||||||
_content.Append(text);
|
_content.Append(text);
|
||||||
return this;
|
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)
|
public Composer Style(string style, string text)
|
||||||
{
|
{
|
||||||
_content.Append('[').Append(style).Append(']');
|
_content.Append('[').Append(style).Append(']');
|
||||||
_content.Append(text.EscapeMarkup());
|
_content.Append(text.EscapeMarkup());
|
||||||
_content.Append("[/]");
|
_content.Append("[/]");
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,6 +49,7 @@ internal sealed class Composer : IRenderable
|
|||||||
_content.Append('[').Append(style).Append(']');
|
_content.Append('[').Append(style).Append(']');
|
||||||
action(this);
|
action(this);
|
||||||
_content.Append("[/]");
|
_content.Append("[/]");
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,12 +94,19 @@ internal sealed class Composer : IRenderable
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Composer Join(string separator, IEnumerable<string> composers)
|
public Composer Join(string separator, IEnumerable<Composer> composers)
|
||||||
{
|
{
|
||||||
if (composers != null)
|
if (composers != null)
|
||||||
{
|
{
|
||||||
Space();
|
foreach (var composer in composers)
|
||||||
Text(string.Join(separator, composers));
|
{
|
||||||
|
if (_content.ToString().Length > 0)
|
||||||
|
{
|
||||||
|
Text(separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(composer.ToString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
@@ -85,12 +114,26 @@ internal sealed class Composer : IRenderable
|
|||||||
|
|
||||||
public Measurement Measure(RenderOptions options, int maxWidth)
|
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)
|
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()
|
public override string ToString()
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal sealed class BranchConfigurator : IBranchConfigurator
|
internal sealed class BranchConfigurator : IBranchConfigurator
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,29 +5,34 @@ internal sealed class CommandAppSettings : ICommandAppSettings
|
|||||||
public CultureInfo? Culture { get; set; }
|
public CultureInfo? Culture { get; set; }
|
||||||
public string? ApplicationName { get; set; }
|
public string? ApplicationName { get; set; }
|
||||||
public string? ApplicationVersion { get; set; }
|
public string? ApplicationVersion { get; set; }
|
||||||
public int MaximumIndirectExamples { get; set; }
|
public int MaximumIndirectExamples { get; set; }
|
||||||
public bool ShowOptionDefaultValues { get; set; }
|
public bool ShowOptionDefaultValues { get; set; }
|
||||||
public IAnsiConsole? Console { get; set; }
|
public IAnsiConsole? Console { get; set; }
|
||||||
|
[Obsolete("Register the interceptor with the ITypeRegistrar.")]
|
||||||
public ICommandInterceptor? Interceptor { get; set; }
|
public ICommandInterceptor? Interceptor { get; set; }
|
||||||
public ITypeRegistrarFrontend Registrar { get; set; }
|
public ITypeRegistrarFrontend Registrar { get; set; }
|
||||||
public CaseSensitivity CaseSensitivity { get; set; }
|
public CaseSensitivity CaseSensitivity { get; set; }
|
||||||
public bool PropagateExceptions { get; set; }
|
public bool PropagateExceptions { get; set; }
|
||||||
public bool ValidateExamples { get; set; }
|
public bool ValidateExamples { get; set; }
|
||||||
public bool TrimTrailingPeriod { get; set; } = true;
|
public bool TrimTrailingPeriod { get; set; }
|
||||||
public bool StrictParsing { get; set; }
|
public HelpProviderStyle? HelpProviderStyles { get; set; }
|
||||||
public bool ConvertFlagsToRemainingArguments { get; set; } = false;
|
public bool StrictParsing { get; set; }
|
||||||
|
public bool ConvertFlagsToRemainingArguments { get; set; }
|
||||||
|
|
||||||
public ParsingMode ParsingMode =>
|
public ParsingMode ParsingMode =>
|
||||||
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
|
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
|
||||||
|
|
||||||
public Func<Exception, int>? ExceptionHandler { get; set; }
|
public Func<Exception, ITypeResolver?, int>? ExceptionHandler { get; set; }
|
||||||
|
|
||||||
public CommandAppSettings(ITypeRegistrar registrar)
|
public CommandAppSettings(ITypeRegistrar registrar)
|
||||||
{
|
{
|
||||||
Registrar = new TypeRegistrar(registrar);
|
Registrar = new TypeRegistrar(registrar);
|
||||||
CaseSensitivity = CaseSensitivity.All;
|
CaseSensitivity = CaseSensitivity.All;
|
||||||
ShowOptionDefaultValues = true;
|
ShowOptionDefaultValues = true;
|
||||||
MaximumIndirectExamples = 5;
|
MaximumIndirectExamples = 5;
|
||||||
|
TrimTrailingPeriod = true;
|
||||||
|
HelpProviderStyles = HelpProviderStyle.Default;
|
||||||
|
ConvertFlagsToRemainingArguments = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsTrue(Func<CommandAppSettings, bool> func, string environmentVariableName)
|
public bool IsTrue(Func<CommandAppSettings, bool> func, string environmentVariableName)
|
||||||
|
|||||||
@@ -19,19 +19,19 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
|
|||||||
Settings = new CommandAppSettings(registrar);
|
Settings = new CommandAppSettings(registrar);
|
||||||
Examples = new List<string[]>();
|
Examples = new List<string[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetHelpProvider(IHelpProvider helpProvider)
|
public void SetHelpProvider(IHelpProvider helpProvider)
|
||||||
{
|
{
|
||||||
// Register the help provider
|
// Register the help provider
|
||||||
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
|
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetHelpProvider<T>()
|
public void SetHelpProvider<T>()
|
||||||
where T : IHelpProvider
|
where T : IHelpProvider
|
||||||
{
|
{
|
||||||
// Register the help provider
|
// Register the help provider
|
||||||
_registrar.Register(typeof(IHelpProvider), typeof(T));
|
_registrar.Register(typeof(IHelpProvider), typeof(T));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddExample(params string[] args)
|
public void AddExample(params string[] args)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ internal static class TemplateParser
|
|||||||
foreach (var character in token.Value)
|
foreach (var character in token.Value)
|
||||||
{
|
{
|
||||||
if (!char.IsLetterOrDigit(character) &&
|
if (!char.IsLetterOrDigit(character) &&
|
||||||
character != '=' && character != '-' && character != '_')
|
character != '=' && character != '-' && character != '_' && character != '|')
|
||||||
{
|
{
|
||||||
throw CommandTemplateException.InvalidCharacterInValueName(template, token, 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.");
|
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)
|
if (command.CommandType != null)
|
||||||
{
|
{
|
||||||
registrar?.Register(command.CommandType, command.CommandType);
|
registrar?.Register(command.CommandType, command.CommandType);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
@@ -20,14 +20,14 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
|||||||
|
|
||||||
// only branches can have a default command
|
// only branches can have a default command
|
||||||
public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null;
|
public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null;
|
||||||
public bool IsHidden { get; }
|
public bool IsHidden { get; }
|
||||||
|
|
||||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList();
|
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList();
|
||||||
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
|
||||||
IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList();
|
IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList();
|
||||||
ICommandInfo? ICommandInfo.Parent => Parent;
|
ICommandInfo? ICommandInfo.Parent => Parent;
|
||||||
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||||
|
|
||||||
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
|
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
|
||||||
{
|
{
|
||||||
Parent = parent;
|
Parent = parent;
|
||||||
@@ -54,5 +54,5 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
|
|||||||
Description = description.Description;
|
Description = description.Description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,18 @@
|
|||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal sealed class CommandModel : ICommandContainer, ICommandModel
|
internal sealed class CommandModel : ICommandContainer, ICommandModel
|
||||||
{
|
{
|
||||||
public string? ApplicationName { get; }
|
public string? ApplicationName { get; }
|
||||||
|
public string? ApplicationVersion { get; }
|
||||||
public ParsingMode ParsingMode { get; }
|
public ParsingMode ParsingMode { get; }
|
||||||
public IList<CommandInfo> Commands { get; }
|
public IList<CommandInfo> Commands { get; }
|
||||||
public IList<string[]> Examples { get; }
|
public IList<string[]> Examples { get; }
|
||||||
|
|
||||||
public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);
|
public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);
|
||||||
|
|
||||||
string ICommandModel.ApplicationName => GetApplicationName(ApplicationName);
|
string ICommandModel.ApplicationName => GetApplicationName(ApplicationName);
|
||||||
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Commands.Cast<ICommandInfo>().ToList();
|
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;
|
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
|
||||||
|
|
||||||
public CommandModel(
|
public CommandModel(
|
||||||
@@ -20,20 +21,21 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel
|
|||||||
IEnumerable<string[]> examples)
|
IEnumerable<string[]> examples)
|
||||||
{
|
{
|
||||||
ApplicationName = settings.ApplicationName;
|
ApplicationName = settings.ApplicationName;
|
||||||
|
ApplicationVersion = settings.ApplicationVersion;
|
||||||
ParsingMode = settings.ParsingMode;
|
ParsingMode = settings.ParsingMode;
|
||||||
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
|
Commands = new List<CommandInfo>(commands);
|
||||||
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
|
Examples = new List<string[]>(examples);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the name of the application.
|
/// Gets the name of the application.
|
||||||
/// If the provided <paramref name="applicationName"/> is not null or empty,
|
/// If the provided <paramref name="applicationName"/> is not null or empty,
|
||||||
/// it is returned. Otherwise the name of the current application
|
/// it is returned. Otherwise the name of the current application
|
||||||
/// is determined based on the executable file's name.
|
/// is determined based on the executable file's name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="applicationName">The optional name of the application.</param>
|
/// <param name="applicationName">The optional name of the application.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// The name of the application, or a default value of "?" if no valid application name can be determined.
|
/// The name of the application, or a default value of "?" if no valid application name can be determined.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
private static string GetApplicationName(string? applicationName)
|
private static string GetApplicationName(string? applicationName)
|
||||||
{
|
{
|
||||||
@@ -45,7 +47,7 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel
|
|||||||
|
|
||||||
private static string? GetApplicationFile()
|
private static string? GetApplicationFile()
|
||||||
{
|
{
|
||||||
var location = Assembly.GetEntryAssembly()?.Location;
|
var location = Assembly.GetEntryAssembly()?.Location;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(location))
|
if (string.IsNullOrWhiteSpace(location))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal static class CommandModelBuilder
|
internal static class CommandModelBuilder
|
||||||
{
|
{
|
||||||
// Consider removing this in favor for value tuples at some point.
|
// 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);
|
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
|
||||||
|
|
||||||
// Build the default command.
|
// Build the default command.
|
||||||
var defaultCommand = Build(null, configuration.DefaultCommand);
|
var defaultCommand = Build(null, configuration.DefaultCommand);
|
||||||
|
|
||||||
result.Add(defaultCommand);
|
result.Add(defaultCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ internal static class CommandModelBuilder
|
|||||||
foreach (var childCommand in command.Children)
|
foreach (var childCommand in command.Children)
|
||||||
{
|
{
|
||||||
var child = Build(info, childCommand);
|
var child = Build(info, childCommand);
|
||||||
info.Children.Add(child);
|
info.Children.Add(child);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize argument positions.
|
// Normalize argument positions.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal abstract class CommandParameter : ICommandParameterInfo, ICommandParameter
|
internal abstract class CommandParameter : ICommandParameterInfo, ICommandParameter
|
||||||
{
|
{
|
||||||
public Guid Id { get; }
|
public Guid Id { get; }
|
||||||
@@ -17,10 +17,10 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
|
|||||||
public string PropertyName => Property.Name;
|
public string PropertyName => Property.Name;
|
||||||
|
|
||||||
public virtual bool WantRawValue => ParameterType.IsPairDeconstructable()
|
public virtual bool WantRawValue => ParameterType.IsPairDeconstructable()
|
||||||
&& (PairDeconstructor != null || Converter == null);
|
&& (PairDeconstructor != null || Converter == null);
|
||||||
|
|
||||||
public bool IsFlag => ParameterKind == ParameterKind.Flag;
|
public bool IsFlag => ParameterKind == ParameterKind.Flag;
|
||||||
|
|
||||||
protected CommandParameter(
|
protected CommandParameter(
|
||||||
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
|
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
|
||||||
string? description, TypeConverterAttribute? converter,
|
string? description, TypeConverterAttribute? converter,
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ internal interface ICommandContainer
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets all commands in the container.
|
/// Gets all commands in the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
IList<CommandInfo> Commands { get; }
|
IList<CommandInfo> Commands { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the default command for the container.
|
/// Gets the default command for the container.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Returns null if a default command has not been set.
|
/// Returns null if a default command has not been set.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
CommandInfo? DefaultCommand { get; }
|
CommandInfo? DefaultCommand { get; }
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
using static Spectre.Console.Cli.CommandTreeTokenizer;
|
using static Spectre.Console.Cli.CommandTreeTokenizer;
|
||||||
|
|
||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal class CommandTreeParser
|
internal class CommandTreeParser
|
||||||
{
|
{
|
||||||
private readonly CommandModel _configuration;
|
private readonly CommandModel _configuration;
|
||||||
private readonly ParsingMode _parsingMode;
|
private readonly ParsingMode _parsingMode;
|
||||||
private readonly CommandOptionAttribute _help;
|
private readonly CommandOptionAttribute _help;
|
||||||
private readonly bool _convertFlagsToRemainingArguments;
|
private readonly bool _convertFlagsToRemainingArguments;
|
||||||
|
|
||||||
public CaseSensitivity CaseSensitivity { get; }
|
public CaseSensitivity CaseSensitivity { get; }
|
||||||
@@ -21,20 +21,20 @@ internal class CommandTreeParser
|
|||||||
{
|
{
|
||||||
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||||
_parsingMode = parsingMode ?? _configuration.ParsingMode;
|
_parsingMode = parsingMode ?? _configuration.ParsingMode;
|
||||||
_help = new CommandOptionAttribute("-h|--help");
|
_help = new CommandOptionAttribute("-h|--help");
|
||||||
_convertFlagsToRemainingArguments = convertFlagsToRemainingArguments ?? false;
|
_convertFlagsToRemainingArguments = convertFlagsToRemainingArguments ?? false;
|
||||||
|
|
||||||
CaseSensitivity = caseSensitivity;
|
CaseSensitivity = caseSensitivity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandTreeParserResult Parse(IEnumerable<string> args)
|
public CommandTreeParserResult Parse(IEnumerable<string> args)
|
||||||
{
|
{
|
||||||
var parserContext = new CommandTreeParserContext(args, _parsingMode);
|
var parserContext = new CommandTreeParserContext(args, _parsingMode);
|
||||||
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||||
|
|
||||||
return Parse(parserContext, tokenizerResult);
|
return Parse(parserContext, tokenizerResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandTreeParserResult Parse(CommandTreeParserContext context, CommandTreeTokenizerResult tokenizerResult)
|
public CommandTreeParserResult Parse(CommandTreeParserContext context, CommandTreeTokenizerResult tokenizerResult)
|
||||||
{
|
{
|
||||||
var tokens = tokenizerResult.Tokens;
|
var tokens = tokenizerResult.Tokens;
|
||||||
@@ -258,8 +258,8 @@ internal class CommandTreeParser
|
|||||||
// Find the option.
|
// Find the option.
|
||||||
var option = node.FindOption(token.Value, isLongOption, CaseSensitivity);
|
var option = node.FindOption(token.Value, isLongOption, CaseSensitivity);
|
||||||
if (option != null)
|
if (option != null)
|
||||||
{
|
{
|
||||||
ParseOptionValue(context, stream, token, node, option);
|
ParseOptionValue(context, stream, token, node, option);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,10 +291,10 @@ internal class CommandTreeParser
|
|||||||
CommandTreeParserContext context,
|
CommandTreeParserContext context,
|
||||||
CommandTreeTokenStream stream,
|
CommandTreeTokenStream stream,
|
||||||
CommandTreeToken token,
|
CommandTreeToken token,
|
||||||
CommandTree current,
|
CommandTree current,
|
||||||
CommandParameter? parameter = null)
|
CommandParameter? parameter = null)
|
||||||
{
|
{
|
||||||
bool addToMappedCommandParameters = parameter != null;
|
bool addToMappedCommandParameters = parameter != null;
|
||||||
|
|
||||||
var value = default(string);
|
var value = default(string);
|
||||||
|
|
||||||
@@ -302,59 +302,55 @@ internal class CommandTreeParser
|
|||||||
var valueToken = stream.Peek();
|
var valueToken = stream.Peek();
|
||||||
if (valueToken?.TokenKind == CommandTreeToken.Kind.String)
|
if (valueToken?.TokenKind == CommandTreeToken.Kind.String)
|
||||||
{
|
{
|
||||||
var parseValue = true;
|
bool parseValue = token is not { TokenKind: CommandTreeToken.Kind.ShortOption, IsGrouped: true };
|
||||||
if (token.TokenKind == CommandTreeToken.Kind.ShortOption && token.IsGrouped)
|
|
||||||
{
|
|
||||||
parseValue = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.State == State.Normal && parseValue)
|
if (context.State == State.Normal && parseValue)
|
||||||
{
|
{
|
||||||
// Is this a command?
|
// Is this a command?
|
||||||
if (current.Command.FindCommand(valueToken.Value, CaseSensitivity) == null)
|
if (current.Command.FindCommand(valueToken.Value, CaseSensitivity) == null)
|
||||||
{
|
{
|
||||||
if (parameter != null)
|
if (parameter != null)
|
||||||
{
|
{
|
||||||
if (parameter.ParameterKind == ParameterKind.Flag)
|
if (parameter.ParameterKind == ParameterKind.Flag)
|
||||||
{
|
{
|
||||||
if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase))
|
if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
if (!valueToken.HadSeparator)
|
if (!valueToken.HadSeparator)
|
||||||
{
|
{
|
||||||
// Do nothing
|
// Do nothing
|
||||||
// - assume valueToken is unrelated to the flag parameter (ie. we've parsed it unnecessarily)
|
// - 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
|
// - 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
|
// - valueToken will be handled on the next pass of the parser
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Flags cannot be assigned a value.
|
// Flags cannot be assigned a value.
|
||||||
if (_convertFlagsToRemainingArguments)
|
if (_convertFlagsToRemainingArguments)
|
||||||
{
|
{
|
||||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||||
|
|
||||||
context.AddRemainingArgument(token.Value, value);
|
context.AddRemainingArgument(token.Representation, value);
|
||||||
|
|
||||||
// Prevent the option and it's non-boolean value from being added to
|
// Prevent the option and it's non-boolean value from being added to
|
||||||
// mapped parameters (otherwise an exception will be thrown later
|
// mapped parameters (otherwise an exception will be thrown later
|
||||||
// when binding the value to the flag in the comand settings)
|
// when binding the value to the flag in the comand settings)
|
||||||
addToMappedCommandParameters = false;
|
addToMappedCommandParameters = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
|
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -364,22 +360,22 @@ internal class CommandTreeParser
|
|||||||
// In relaxed parsing mode?
|
// In relaxed parsing mode?
|
||||||
if (context.ParsingMode == ParsingMode.Relaxed)
|
if (context.ParsingMode == ParsingMode.Relaxed)
|
||||||
{
|
{
|
||||||
context.AddRemainingArgument(token.Value, value);
|
context.AddRemainingArgument(token.Representation, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null);
|
context.AddRemainingArgument(token.Representation, parseValue ? valueToken.Value : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
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.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())
|
if (parameter.IsFlagValue())
|
||||||
{
|
{
|
||||||
value = null;
|
value = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -415,9 +411,9 @@ internal class CommandTreeParser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameter != null && addToMappedCommandParameters)
|
if (parameter != null && addToMappedCommandParameters)
|
||||||
{
|
{
|
||||||
current.Mapped.Add(new MappedCommandParameter(parameter, value));
|
current.Mapped.Add(new MappedCommandParameter(parameter, value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,15 +26,15 @@ internal class CommandTreeParserContext
|
|||||||
public void IncreaseArgumentPosition()
|
public void IncreaseArgumentPosition()
|
||||||
{
|
{
|
||||||
CurrentArgumentPosition++;
|
CurrentArgumentPosition++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddRemainingArgument(string key, string? value)
|
public void AddRemainingArgument(string key, string? value)
|
||||||
{
|
{
|
||||||
if (!_remaining.ContainsKey(key))
|
if (!_remaining.ContainsKey(key))
|
||||||
{
|
{
|
||||||
_remaining.Add(key, new List<string?>());
|
_remaining.Add(key, new List<string?>());
|
||||||
}
|
}
|
||||||
|
|
||||||
_remaining[key].Add(value);
|
_remaining[key].Add(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ internal sealed class CommandTreeToken
|
|||||||
public int Position { get; }
|
public int Position { get; }
|
||||||
public string Value { get; }
|
public string Value { get; }
|
||||||
public string Representation { get; }
|
public string Representation { get; }
|
||||||
public bool IsGrouped { get; set; }
|
public bool IsGrouped { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>.
|
/// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool HadSeparator { get; set; }
|
public bool HadSeparator { get; set; }
|
||||||
|
|
||||||
public enum Kind
|
public enum Kind
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ internal sealed class CommandTreeTokenStream : IReadOnlyList<CommandTreeToken>
|
|||||||
private readonly List<CommandTreeToken> _tokens;
|
private readonly List<CommandTreeToken> _tokens;
|
||||||
private int _position;
|
private int _position;
|
||||||
|
|
||||||
public int Count => _tokens.Count;
|
public int Count => _tokens.Count;
|
||||||
public int Position => _position;
|
public int Position => _position;
|
||||||
|
|
||||||
public CommandTreeToken this[int index] => _tokens[index];
|
public CommandTreeToken this[int index] => _tokens[index];
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ internal static class CommandTreeTokenizer
|
|||||||
var context = new CommandTreeTokenizerContext();
|
var context = new CommandTreeTokenizerContext();
|
||||||
|
|
||||||
foreach (var arg in args)
|
foreach (var arg in args)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(arg))
|
if (string.IsNullOrEmpty(arg))
|
||||||
{
|
{
|
||||||
// Null strings in the args array are still represented as tokens
|
// Null strings in the args array are still represented as tokens
|
||||||
tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty));
|
tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var start = position;
|
var start = position;
|
||||||
var reader = new TextBuffer(previousReader, arg);
|
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)
|
private static int ParseToken(CommandTreeTokenizerContext context, TextBuffer reader, int position, int start, List<CommandTreeToken> tokens)
|
||||||
{
|
{
|
||||||
if (!reader.ReachedEnd && reader.Peek() == '-')
|
if (!reader.ReachedEnd && reader.Peek() == '-')
|
||||||
{
|
{
|
||||||
// Option
|
// Option
|
||||||
tokens.AddRange(ScanOptions(context, reader));
|
tokens.AddRange(ScanOptions(context, reader));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Command or argument
|
// Command or argument
|
||||||
while (reader.Peek() != -1)
|
while (reader.Peek() != -1)
|
||||||
{
|
{
|
||||||
if (reader.ReachedEnd)
|
if (reader.ReachedEnd)
|
||||||
{
|
{
|
||||||
position += reader.Position - start;
|
position += reader.Position - start;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
tokens.Add(ScanString(context, reader));
|
tokens.Add(ScanString(context, reader));
|
||||||
|
|
||||||
// Flush remaining tokens
|
// Flush remaining tokens
|
||||||
context.FlushRemaining();
|
context.FlushRemaining();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ internal static class CommandTreeTokenizer
|
|||||||
builder.Append(current);
|
builder.Append(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
var value = builder.ToString();
|
var value = builder.ToString();
|
||||||
return new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value);
|
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, "=", "=");
|
var token = new CommandTreeToken(CommandTreeToken.Kind.String, reader.Position, "=", "=");
|
||||||
throw CommandParseException.OptionValueWasExpected(reader.Original, token);
|
throw CommandParseException.OptionValueWasExpected(reader.Original, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
var tokenValue = ScanString(context, reader);
|
var tokenValue = ScanString(context, reader);
|
||||||
tokenValue.HadSeparator = true;
|
tokenValue.HadSeparator = true;
|
||||||
result.Add(tokenValue);
|
result.Add(tokenValue);
|
||||||
}
|
}
|
||||||
@@ -171,7 +171,7 @@ internal static class CommandTreeTokenizer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Encountered a separator?
|
// Encountered a separator?
|
||||||
if (current == '=' || current == ':')
|
if (current is '=' or ':')
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -184,7 +184,7 @@ internal static class CommandTreeTokenizer
|
|||||||
var value = current.ToString(CultureInfo.InvariantCulture);
|
var value = current.ToString(CultureInfo.InvariantCulture);
|
||||||
result.Add(result.Count == 0
|
result.Add(result.Count == 0
|
||||||
? new CommandTreeToken(CommandTreeToken.Kind.ShortOption, position, value, $"-{value}")
|
? 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))
|
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
|
// 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
|
// when the digit is the first character in the token (i.e. "-a1" is always an error), hence the
|
||||||
// result.Count == 0 check above.
|
// result.Count == 0 check above.
|
||||||
string value = string.Empty;
|
string value = string.Empty;
|
||||||
|
|
||||||
while (!reader.ReachedEnd)
|
while (!reader.ReachedEnd)
|
||||||
{
|
{
|
||||||
@@ -212,12 +212,12 @@ internal static class CommandTreeTokenizer
|
|||||||
result.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value));
|
result.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create a token representing the short option.
|
// Create a token representing the short option.
|
||||||
var representation = current.ToString(CultureInfo.InvariantCulture);
|
var representation = current.ToString(CultureInfo.InvariantCulture);
|
||||||
var tokenPosition = position + 1 + result.Count;
|
var tokenPosition = position + 1 + result.Count;
|
||||||
var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation);
|
var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation);
|
||||||
|
|
||||||
throw CommandParseException.InvalidShortOptionName(reader.Original, token);
|
throw CommandParseException.InvalidShortOptionName(reader.Original, token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ global using System.Linq;
|
|||||||
global using System.Reflection;
|
global using System.Reflection;
|
||||||
global using System.Text;
|
global using System.Text;
|
||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
global using System.Xml;
|
global using System.Xml;
|
||||||
global using Spectre.Console.Cli.Help;
|
global using Spectre.Console.Cli.Help;
|
||||||
global using Spectre.Console.Cli.Unsafe;
|
global using Spectre.Console.Cli.Unsafe;
|
||||||
global using Spectre.Console.Rendering;
|
global using Spectre.Console.Rendering;
|
||||||
@@ -1,153 +1,153 @@
|
|||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// <auto-generated>
|
// <auto-generated>
|
||||||
// This code was generated by a tool.
|
// This code was generated by a tool.
|
||||||
// Runtime Version:4.0.30319.42000
|
// Runtime Version:4.0.30319.42000
|
||||||
//
|
//
|
||||||
// Changes to this file may cause incorrect behavior and will be lost if
|
// Changes to this file may cause incorrect behavior and will be lost if
|
||||||
// the code is regenerated.
|
// the code is regenerated.
|
||||||
// </auto-generated>
|
// </auto-generated>
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
namespace Spectre.Console.Cli.Resources {
|
namespace Spectre.Console.Cli.Resources {
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||||
// class via a tool like ResGen or Visual Studio.
|
// class via a tool like ResGen or Visual Studio.
|
||||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||||
// with the /str option, or rebuild your VS project.
|
// with the /str option, or rebuild your VS project.
|
||||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||||
internal class HelpProvider {
|
internal class HelpProvider {
|
||||||
|
|
||||||
private static global::System.Resources.ResourceManager resourceMan;
|
private static global::System.Resources.ResourceManager resourceMan;
|
||||||
|
|
||||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||||
|
|
||||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||||
internal HelpProvider() {
|
internal HelpProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the cached ResourceManager instance used by this class.
|
/// Returns the cached ResourceManager instance used by this class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||||
get {
|
get {
|
||||||
if (object.ReferenceEquals(resourceMan, null)) {
|
if (object.ReferenceEquals(resourceMan, null)) {
|
||||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
|
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
|
||||||
resourceMan = temp;
|
resourceMan = temp;
|
||||||
}
|
}
|
||||||
return resourceMan;
|
return resourceMan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Overrides the current thread's CurrentUICulture property for all
|
/// Overrides the current thread's CurrentUICulture property for all
|
||||||
/// resource lookups using this strongly typed resource class.
|
/// resource lookups using this strongly typed resource class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||||
internal static global::System.Globalization.CultureInfo Culture {
|
internal static global::System.Globalization.CultureInfo Culture {
|
||||||
get {
|
get {
|
||||||
return resourceCulture;
|
return resourceCulture;
|
||||||
}
|
}
|
||||||
set {
|
set {
|
||||||
resourceCulture = value;
|
resourceCulture = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to ARGUMENTS.
|
/// Looks up a localized string similar to ARGUMENTS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Arguments {
|
internal static string Arguments {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Arguments", resourceCulture);
|
return ResourceManager.GetString("Arguments", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to COMMAND.
|
/// Looks up a localized string similar to COMMAND.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Command {
|
internal static string Command {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Command", resourceCulture);
|
return ResourceManager.GetString("Command", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to COMMANDS.
|
/// Looks up a localized string similar to COMMANDS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Commands {
|
internal static string Commands {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Commands", resourceCulture);
|
return ResourceManager.GetString("Commands", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to DEFAULT.
|
/// Looks up a localized string similar to DEFAULT.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Default {
|
internal static string Default {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Default", resourceCulture);
|
return ResourceManager.GetString("Default", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to DESCRIPTION.
|
/// Looks up a localized string similar to DESCRIPTION.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Description {
|
internal static string Description {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Description", resourceCulture);
|
return ResourceManager.GetString("Description", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to EXAMPLES.
|
/// Looks up a localized string similar to EXAMPLES.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Examples {
|
internal static string Examples {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Examples", resourceCulture);
|
return ResourceManager.GetString("Examples", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to OPTIONS.
|
/// Looks up a localized string similar to OPTIONS.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Options {
|
internal static string Options {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Options", resourceCulture);
|
return ResourceManager.GetString("Options", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Prints help information.
|
/// Looks up a localized string similar to Prints help information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string PrintHelpDescription {
|
internal static string PrintHelpDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("PrintHelpDescription", resourceCulture);
|
return ResourceManager.GetString("PrintHelpDescription", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to Prints version information.
|
/// Looks up a localized string similar to Prints version information.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string PrintVersionDescription {
|
internal static string PrintVersionDescription {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("PrintVersionDescription", resourceCulture);
|
return ResourceManager.GetString("PrintVersionDescription", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Looks up a localized string similar to USAGE.
|
/// Looks up a localized string similar to USAGE.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string Usage {
|
internal static string Usage {
|
||||||
get {
|
get {
|
||||||
return ResourceManager.GetString("Usage", resourceCulture);
|
return ResourceManager.GetString("Usage", resourceCulture);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,19 +31,19 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Update="Resources\HelpProvider.Designer.cs">
|
<Compile Update="Resources\HelpProvider.Designer.cs">
|
||||||
<DesignTime>True</DesignTime>
|
<DesignTime>True</DesignTime>
|
||||||
<AutoGen>True</AutoGen>
|
<AutoGen>True</AutoGen>
|
||||||
<DependentUpon>HelpProvider.resx</DependentUpon>
|
<DependentUpon>HelpProvider.resx</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Update="Resources\HelpProvider.resx">
|
<EmbeddedResource Update="Resources\HelpProvider.resx">
|
||||||
<Generator>ResXFileCodeGenerator</Generator>
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
<LastGenOutput>HelpProvider.Designer.cs</LastGenOutput>
|
<LastGenOutput>HelpProvider.Designer.cs</LastGenOutput>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net8.0;net7.0;net6.0;netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
<Description>A library that extends Spectre.Console with ImageSharp superpowers.</Description>
|
<Description>A library that extends Spectre.Console with ImageSharp superpowers.</Description>
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="2.1.3" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<Compile Include="..\Spectre.Console\Internal\Extensions\EnumerableExtensions.cs" Link="Internal\EnumerableExtensions.cs" />
|
<Compile Include="..\Spectre.Console\Internal\Extensions\EnumerableExtensions.cs" Link="Internal\EnumerableExtensions.cs" />
|
||||||
<Compile Include="..\Spectre.Console\Internal\Text\StringBuffer.cs" Link="Internal\StringBuffer.cs" />
|
<Compile Include="..\Spectre.Console\Internal\Text\StringBuffer.cs" Link="Internal\StringBuffer.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
|
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
|
||||||
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
<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);
|
_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();
|
.Trim();
|
||||||
|
|
||||||
return new CommandAppResult(result, output, context, settings);
|
return new CommandAppResult(result, output, context, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs the command application asynchronously.
|
/// Runs the command application asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>net8.0;net7.0;net6.0;netstandard2.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net7.0;net6.0</TargetFrameworks>
|
||||||
<IsTestProject>false</IsTestProject>
|
<IsTestProject>false</IsTestProject>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>true</IsPackable>
|
<IsPackable>true</IsPackable>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public static partial class AnsiConsoleExtensions
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
var rawKey = await console.Input.ReadKeyAsync(true, cancellationToken).ConfigureAwait(false);
|
var rawKey = await console.Input.ReadKeyAsync(true, cancellationToken).ConfigureAwait(false);
|
||||||
if (rawKey == null)
|
if (rawKey == null)
|
||||||
{
|
{
|
||||||
@@ -52,7 +53,11 @@ public static partial class AnsiConsoleExtensions
|
|||||||
if (text.Length > 0)
|
if (text.Length > 0)
|
||||||
{
|
{
|
||||||
text = text.Substring(0, text.Length - 1);
|
text = text.Substring(0, text.Length - 1);
|
||||||
console.Write("\b \b");
|
|
||||||
|
if (mask != null)
|
||||||
|
{
|
||||||
|
console.Write("\b \b");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
@@ -60,7 +65,7 @@ public static partial class AnsiConsoleExtensions
|
|||||||
|
|
||||||
if (!char.IsControl(key.KeyChar))
|
if (!char.IsControl(key.KeyChar))
|
||||||
{
|
{
|
||||||
text += key.KeyChar.ToString();
|
text += key.KeyChar.ToString();
|
||||||
var output = key.KeyChar.ToString();
|
var output = key.KeyChar.ToString();
|
||||||
console.Write(secret ? output.Mask(mask) : output, style);
|
console.Write(secret ? output.Mask(mask) : output, style);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,6 +116,43 @@ public static class BarChartExtensions
|
|||||||
return chart;
|
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>
|
/// <summary>
|
||||||
/// Sets the width of the bar chart.
|
/// Sets the width of the bar chart.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -185,28 +185,122 @@ public static class StringExtensions
|
|||||||
#else
|
#else
|
||||||
return text.Contains(value, StringComparison.Ordinal);
|
return text.Contains(value, StringComparison.Ordinal);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
#if NETSTANDARD2_0
|
||||||
/// "Masks" every character in a string.
|
internal static bool Contains(this string target, string value, System.StringComparison comparisonType)
|
||||||
/// </summary>
|
{
|
||||||
/// <param name="value">String value to mask.</param>
|
return target.IndexOf(value, comparisonType) != -1;
|
||||||
/// <param name="mask">Character to use for masking.</param>
|
}
|
||||||
/// <returns>Masked string.</returns>
|
#endif
|
||||||
public static string Mask(this string value, char? mask)
|
|
||||||
{
|
/// <summary>
|
||||||
var output = string.Empty;
|
/// "Masks" every character in a string.
|
||||||
|
/// </summary>
|
||||||
if (mask is null)
|
/// <param name="value">String value to mask.</param>
|
||||||
{
|
/// <param name="mask">Character to use for masking.</param>
|
||||||
return output;
|
/// <returns>Masked string.</returns>
|
||||||
}
|
public static string Mask(this string value, char? mask)
|
||||||
|
{
|
||||||
foreach (var c in value)
|
if (mask is null)
|
||||||
{
|
{
|
||||||
output += mask;
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
return output;
|
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>
|
/// <summary>
|
||||||
/// Represents horizontal alignment.
|
/// Represents horizontal alignment.
|
||||||
|
|||||||
@@ -31,12 +31,11 @@ internal static class EnumerableExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static int IndexOf<T>(this IEnumerable<T> source, T item)
|
public static int IndexOf<T>(this IEnumerable<T> source, T item)
|
||||||
where T : class
|
|
||||||
{
|
{
|
||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var candidate in source)
|
foreach (var candidate in source)
|
||||||
{
|
{
|
||||||
if (candidate == item)
|
if (Equals(candidate, item))
|
||||||
{
|
{
|
||||||
return index;
|
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