Compare commits

...

21 Commits

Author SHA1 Message Date
Cédric Luthi
168f35202d Fix exception formatting for generic methods (#639)
The generic parameters were double escaped and would display as `[[T0,T1,TRet]]` instead of `[T0,T1,TRet]`. This is because the `builder.AppendWithStyle` method already escapes its value so the caller must not escape the passed value.
2021-11-26 10:23:20 +01:00
Patrik Svensson
225305f90e Update examples to net6.0
Closes #604
2021-11-23 22:30:45 -05:00
Cédric Luthi
8fed3bc575 Fix type conversion in the default pair deconstructor implementation (#618)
The code using the TypeConverter was written backwards (using `ConvertTo` instead of `ConvertFrom` and would thus throw an exception.
2021-11-22 15:07:58 +01:00
Cédric Luthi
19eb273813 Fix typo in PairDeconstructor class name
PairDeconstuctor → PairDeconstructor (was missing an 'r')

Keeping `PairDeconstuctor` and marking it as obsolete since it's a public type.
2021-11-14 10:36:16 +01:00
Nils Andresen
48df9cf68b (#555) added TypeRegistrarBaseTests
as a very simple test harness to test implementations of
ITypeRegistrar / ITypeResolver
2021-11-14 01:19:27 +01:00
Nils Andresen
2f6b4f53c4 (#555) Clarify ITypeResolver returns null
and does not throw on unresolvable types.
Also changed the TypeResolverAdapter to adhere
to those expectations and removed the now no longer
needed try-catch from CommandPropertyBinder.
2021-11-14 01:19:27 +01:00
Patrik Svensson
21ac952307 Remove Render from docs in favor of Write (#613)
The `Render` method has been obsoleted.
2021-11-09 21:22:18 +01:00
Patrik Svensson
e86f9d3c5a Allow color numbers in markup expressions
Closes #614
2021-11-09 09:32:50 -05:00
Patrik Svensson
9c9eb04f91 Upgrade .NET SDK to 6.0.100 (#616)
* Update to .NET 6.0.100

* Update Cake to 2.0.0-rc0001
2021-11-09 08:19:36 +01:00
Nils Andresen
ba4b7b97f8 Escape any Markup when displaying selected prompt items
If the item contained escaped markup, after the call to RemoveMarkup
the string will contain unescaped markup (that the user explicitly had
escaped before) for those cases we need to escape all remaining markup.
2021-11-02 22:56:54 +01:00
Patrik Svensson
b738187b28 Fix publish workflow error 2021-10-30 01:52:25 +02:00
Nils Andresen
b3ef7d4fa6 fixed documentation for selection and multiselection (#499) 2021-10-30 01:50:18 +02:00
Nils Andresen
c5c1852fc3 (#606) added ExceptionHandler to ICommandAppSettings
So exceptions can be handled in Spectre.Console.Cli.
Also, some documentation was added.
2021-10-30 01:41:29 +02:00
Nils Andresen
045d0922c8 fixed spelling in Exception 2021-10-30 00:05:16 +02:00
Nils Andresen
949f35defd (#502) Added GetParent and GetParents to MultiSelectionPrompt
So it it possible to find the parent(s) of a given item.
2021-10-30 00:05:16 +02:00
GitHubPang
f5a2735501 Fix a typo in code comment (#579) 2021-10-29 21:12:01 +02:00
Patrik Svensson
d02c9e552e Add net6.0 TFM (#603)
Closes #602
2021-10-29 21:05:17 +02:00
Antonio Valentini
a4ae36738b typos 2021-10-23 16:05:56 -04:00
Patrik Svensson
35d2750b68 Internalizes the WcWidth package
This way, there is no need to download the WcWidth package,
and it also becomes easier to debug issues related to
Unicode cell widths.
2021-10-18 15:09:54 -04:00
Phil Scott
4d2d927caa Upgrades Statiq and re-enables docs publishing
Previous versions of Statiq had a bug with .NET 6 RC1. The latest has a workaround that allows publishing
2021-10-15 20:31:57 +02:00
Magnus Lindhe
174d285035 Update sponsors.md
Fixing a typo in October. I should be ashamed :)
2021-10-10 09:40:09 +02:00
85 changed files with 878 additions and 178 deletions

View File

@@ -13,17 +13,15 @@ jobs:
docs:
name: Documentation
if: false # Disable for now
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Setup dotnet
- name: Setup dotnet 6.0.100
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
include-prerelease: true
dotnet-version: 6.0.100
- name: Setup Node.js
uses: actions/setup-node@v2
@@ -86,6 +84,11 @@ jobs:
with:
dotnet-version: 5.0.301
- name: Setup dotnet 6.0.100
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
- name: Integration Tests
shell: bash
run: |

View File

@@ -17,7 +17,6 @@ jobs:
build:
name: Deploy
if: false # Disable for now
runs-on: windows-latest
steps:
- name: Checkout
@@ -25,11 +24,10 @@ jobs:
with:
fetch-depth: 0
- name: Setup dotnet
- name: Setup dotnet 6.0.100
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
include-prerelease: true
dotnet-version: 6.0.100
- name: Setup Node.js
uses: actions/setup-node@v2

View File

@@ -19,17 +19,15 @@ jobs:
docs:
name: Documentation
if: false # Disable for now
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@master
- name: Setup dotnet
- name: Setup dotnet 6.0.100
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
include-prerelease: true
dotnet-version: 6.0.100
- name: Build
shell: bash
@@ -45,7 +43,7 @@ jobs:
build:
name: Build
# needs: [docs]
needs: [docs]
if: "!contains(github.event.head_commit.message, 'skip-ci') || startsWith(github.ref, 'refs/tags/')"
strategy:
matrix:
@@ -74,6 +72,11 @@ jobs:
with:
dotnet-version: 5.0.301
- name: Setup dotnet 6.0.100
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
- name: Build
shell: bash
run: |
@@ -105,6 +108,12 @@ jobs:
with:
dotnet-version: 5.0.301
- name: Setup dotnet 6.0
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.x
include-prerelease: true
- name: Publish
shell: bash
run: |

View File

@@ -14,7 +14,7 @@ Task("Build")
.IsDependentOn("Clean")
.Does(context =>
{
DotNetCoreBuild("./src/Spectre.Console.sln", new DotNetCoreBuildSettings {
DotNetBuild("./src/Spectre.Console.sln", new DotNetBuildSettings {
Configuration = configuration,
NoIncremental = context.HasArgument("rebuild"),
MSBuildSettings = new DotNetCoreMSBuildSettings()
@@ -26,7 +26,7 @@ Task("Build-Analyzer")
.IsDependentOn("Build")
.Does(context =>
{
DotNetCoreBuild("./src/Spectre.Console.Analyzer.sln", new DotNetCoreBuildSettings {
DotNetBuild("./src/Spectre.Console.Analyzer.sln", new DotNetBuildSettings {
Configuration = configuration,
NoIncremental = context.HasArgument("rebuild"),
MSBuildSettings = new DotNetCoreMSBuildSettings()
@@ -38,7 +38,7 @@ Task("Build-Examples")
.IsDependentOn("Build")
.Does(context =>
{
DotNetCoreBuild("./examples/Examples.sln", new DotNetCoreBuildSettings {
DotNetBuild("./examples/Examples.sln", new DotNetBuildSettings {
Configuration = configuration,
NoIncremental = context.HasArgument("rebuild"),
MSBuildSettings = new DotNetCoreMSBuildSettings()
@@ -52,13 +52,13 @@ Task("Test")
.IsDependentOn("Build-Examples")
.Does(context =>
{
DotNetCoreTest("./test/Spectre.Console.Tests/Spectre.Console.Tests.csproj", new DotNetCoreTestSettings {
DotNetTest("./test/Spectre.Console.Tests/Spectre.Console.Tests.csproj", new DotNetTestSettings {
Configuration = configuration,
NoRestore = true,
NoBuild = true,
});
DotNetCoreTest("./test/Spectre.Console.Analyzer.Tests/Spectre.Console.Analyzer.Tests.csproj", new DotNetCoreTestSettings {
DotNetTest("./test/Spectre.Console.Analyzer.Tests/Spectre.Console.Analyzer.Tests.csproj", new DotNetTestSettings {
Configuration = configuration,
NoRestore = true,
NoBuild = true,
@@ -69,7 +69,7 @@ Task("Package")
.IsDependentOn("Test")
.Does(context =>
{
context.DotNetCorePack($"./src/Spectre.Console.sln", new DotNetCorePackSettings {
context.DotNetPack($"./src/Spectre.Console.sln", new DotNetPackSettings {
Configuration = configuration,
NoRestore = true,
NoBuild = true,
@@ -78,7 +78,7 @@ Task("Package")
.TreatAllWarningsAs(MSBuildTreatAllWarningsAs.Error)
});
context.DotNetCorePack($"./src/Spectre.Console.Analyzer.sln", new DotNetCorePackSettings {
context.DotNetPack($"./src/Spectre.Console.Analyzer.sln", new DotNetPackSettings {
Configuration = configuration,
NoRestore = true,
NoBuild = true,
@@ -134,7 +134,7 @@ Task("Publish-NuGet")
foreach(var file in context.GetFiles("./.artifacts/*.nupkg"))
{
context.Information("Publishing {0}...", file.GetFilename().FullPath);
DotNetCoreNuGetPush(file.FullPath, new DotNetCoreNuGetPushSettings
DotNetNuGetPush(file.FullPath, new DotNetNuGetPushSettings
{
Source = "https://api.nuget.org/v3/index.json",
ApiKey = apiKey,

View File

@@ -34,7 +34,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Playwright" Version="1.13.0-next-1" />
<PackageReference Include="Statiq.Web" Version="1.0.0-beta.31" />
<PackageReference Include="Statiq.Web" Version="1.0.0-beta.34" />
<PackageReference Include="MinVer" PrivateAssets="All" Version="2.3.1" />
</ItemGroup>

View File

@@ -74,6 +74,9 @@ return app.Run(args);
`TypeRegistrar` is a custom class that must be created by the user. This [example using `Microsoft.Extensions.DependencyInjection` as the container](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Injection) provides an example `TypeRegistrar` and `TypeResolver` that can be added to your application with small adjustments for your DI container.
Hint: If you do write your own implementation of `TypeRegistrar` and `TypeResolver` and you have some form of unit tests in place for your project,
there is a utility `TypeRegistrarBaseTests` available that can be used to ensure your implementations adhere to the required implementation. Simply call `TypeRegistrarBaseTests.RunAllTests()` and expect no `TypeRegistrarBaseTests.TestFailedException` to be thrown.
## Interception
`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.

View File

@@ -0,0 +1,116 @@
Title: Exceptions
Order: 12
Description: "Handling exceptions in *Spectre.Console.Cli*"
---
Exceptions happen.
`Spectre.Console.Cli` handles exceptions, writes a user friendly message to the console and sets the exitCode
of the application to `-1`.
While this might be enough for the needs of most applications, there are some options to customize this behavior.
## Propagating exceptions
The most basic way is to set `PropagateExceptions()` during configuration and handle everything.
While this option grants the most freedom, it also requires the most work: Setting `PropagateExceptions`
means that `Spectre.Console.Cli` effectively re-throws exceptions.
This means that `app.Run()` should be wrapped in a `try`-`catch`-block which has to handle the exception
(i.e. outputting something useful) and also provide the exitCode (or `return` value) for the application.
```csharp
using Spectre.Console.Cli;
namespace MyApp
{
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<FileSizeCommand>();
app.Configure(config =>
{
config.PropagateExceptions();
});
try
{
return app.Run(args);
}
catch (Exception ex)
{
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
return -99;
}
}
}
}
```
## Using a custom ExceptionHandler
Using the `SetErrorHandler()` during configuration it is possible to handle exceptions in a defined way.
This method comes in two flavours: One that uses the default exitCode (or `return` value) of `-1` and one
where the exitCode needs to be supplied.
### Using `SetErrorHandler(Func<Exception, int> handler)`
Using this method exceptions can be handled in a custom way. The return value of the handler is used as
the exitCode for the application.
```csharp
using Spectre.Console.Cli;
namespace MyApp
{
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<FileSizeCommand>();
app.Configure(config =>
{
config.SetExceptionHandler(ex =>
{
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
return -99;
});
});
return app.Run(args);
}
}
}
```
### Using `SetErrorHandler(Action<Exception> handler)`
Using this method exceptions can be handled in a custom way, much the same as with the `SetErrorHandler(Func<Exception, int> handler)`.
Using 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`.
```csharp
using Spectre.Console.Cli;
namespace MyApp
{
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<FileSizeCommand>();
app.Configure(config =>
{
config.SetExceptionHandler(ex =>
{
AnsiConsole.WriteException(ex, ExceptionFormats.ShortenEverything);
});
});
return app.Run(args);
}
}
}
```

View File

@@ -61,8 +61,8 @@ In our `Main()` method, an instance of `Spectre.Console.Cli`'s `CommandApp` is
This command will have three parameters.
* The main parameter for the command will be the path. This is to be passed in as the first argument without needing to specify any command line flags. To configure that setting, use the `CommandArgument` attribute. The `[searchPath]` parameters of `CommandArgument` drives not only how the built in help display will render the help text, but the square brackets tells `Spectre.Console.Cli` that this argument is optional through convention.
* The second will be specified as a parameter option. The `CommandOption` attribute is used to specify this action along with the option command line flag. In the case of `SearchPattern` both `-p` and `--path` are valid.
* The main parameter for the command will be the path. This is to be passed in as the first argument without needing to specify any command line flags. To configure that setting, use the `CommandArgument` attribute. The `[searchPath]` parameter of `CommandArgument` drives not only how the built in help display will render the help text, but the square brackets tells `Spectre.Console.Cli` that this argument is optional through convention.
* The second will be specified as a parameter option. The `CommandOption` attribute is used to specify this action along with the option command line flag. In the case of `SearchPattern` both `-p` and `--pattern` are valid.
* The third will also be a parameter option. Here `DefaultValue` is used to indicate the default value will be `true`. For boolean parameters these will be interpreted as flags which means the user can just specify `--hidden` rather than `-hidden true`.
When `args` is passed into the `CommandApp`'s run method, `Spectre.Console.Cli` will parse those arguments and populate an instance of your settings. Upon success, it will then pass those settings into an instance of the specified command's `Execute` method.

View File

@@ -15,7 +15,7 @@ Console markup uses a syntax inspired by bbcode. If you write the style (see [St
in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`.
```csharp
AnsiConsole.Render(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
@@ -26,7 +26,7 @@ rendering of `IRenderable` also have overloads for rendering rich text.
var table = new Table();
table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]")));
table.AddColumn(new TableColumn("[blue]Bar[/]"));
AnsiConsole.Render(table);
AnsiConsole.Write(table);
```
## Convenience methods

View File

@@ -29,9 +29,8 @@ var fruits = AnsiConsole.Prompt(
.InstructionsText(
"[grey](Press [blue]<space>[/] to toggle a fruit, " +
"[green]<enter>[/] to accept)[/]")
.AddChoice("Apple")
.AddChoices(new[] {
"Apricot", "Avocado",
"Apple", "Apricot", "Avocado",
"Banana", "Blackcurrant", "Blueberry",
"Cherry", "Cloudberry", "Cocunut",
}));

View File

@@ -22,9 +22,8 @@ var fruit = AnsiConsole.Prompt(
.Title("What's your [green]favorite fruit[/]?")
.PageSize(10)
.MoreChoicesText("[grey](Move up and down to reveal more fruits)[/]")
.AddChoice("Apple")
.AddChoices(new[] {
"Apricot", "Avocado",
"Apple", "Apricot", "Avocado",
"Banana", "Blackcurrant", "Blueberry",
"Cherry", "Cloudberry", "Cocunut",
}));

View File

@@ -9,7 +9,7 @@ Spectre.Console to show their support and to ensure the longevity of the project
* [Rodney Littles II](https://github.com/RLittlesII)
* [Martin Björkström](https://github.com/bjorkstromm)
* [Dave Glick](https://github.com/daveaglick)
* [Kim Gunanrsson](https://github.com/kimgunnarsson)
* [Kim Gunnarsson](https://github.com/kimgunnarsson)
* [Andrew McClenaghan](https://github.com/andymac4182)
* [C. Augusto Proiete](https://github.com/augustoproiete)
* [Viktor Elofsson](https://github.com/vktr)

View File

@@ -17,7 +17,7 @@ Use `BarChart` to render bar charts to the console.
### Basic usage
```csharp
AnsiConsole.Render(new BarChart()
AnsiConsole.Write(new BarChart()
.Width(60)
.Label("[green bold underline]Number of fruits[/]")
.CenterLabel()
@@ -38,7 +38,7 @@ var items = new List<(string Label, double Value)>
};
// Render bar chart
AnsiConsole.Render(new BarChart()
AnsiConsole.Write(new BarChart()
.Width(60)
.Label("[green bold underline]Number of fruits[/]")
.CenterLabel()
@@ -72,7 +72,7 @@ var items = new List<Fruit>
};
// Render bar chart
AnsiConsole.Render(new BarChart()
AnsiConsole.Write(new BarChart()
.Width(60)
.Label("[green bold underline]Number of fruits[/]")
.CenterLabel()

View File

@@ -16,7 +16,7 @@ To render a calendar, create a `Calendar` instance with a target date.
```csharp
var calendar = new Calendar(2020,10);
AnsiConsole.Render(calendar);
AnsiConsole.Write(calendar);
```
<?# AsciiCast cast="calendar" /?>
@@ -27,8 +27,8 @@ You can set the calendar's culture to show localized weekdays.
```csharp
var calendar = new Calendar(2020,10);
calendar.Culture("ja-JP");
AnsiConsole.Render(calendar);
calendar.Culture("sv-SE");
AnsiConsole.Write(calendar);
```
<?# AsciiCast cast="calendar-culture" /?>
@@ -40,7 +40,7 @@ You can hide the calendar header.
```csharp
var calendar = new Calendar(2020,10);
calendar.HideHeader();
AnsiConsole.Render(calendar);
AnsiConsole.Write(calendar);
```
You can set the header style of the calendar.
@@ -48,7 +48,7 @@ You can set the header style of the calendar.
```csharp
var calendar = new Calendar(2020, 10);
calendar.HeaderStyle(Style.Parse("blue bold"));
AnsiConsole.Render(calendar);
AnsiConsole.Write(calendar);
```
<?# AsciiCast cast="calendar-header" /?>

View File

@@ -26,7 +26,7 @@ var image = new CanvasImage("cake.png");
image.MaxWidth(16);
// Render the image to the console
AnsiConsole.Render(image);
AnsiConsole.Write(image);
```
## Result
@@ -50,7 +50,7 @@ image.BilinearResampler();
image.Mutate(ctx => ctx.Grayscale().Rotate(-45).EntropyCrop());
// Render the image to the console
AnsiConsole.Render(image);
AnsiConsole.Write(image);
```
## Result

View File

@@ -28,7 +28,7 @@ for(var i = 0; i < canvas.Width; i++)
}
// Render the canvas
AnsiConsole.Render(canvas);
AnsiConsole.Write(canvas);
```
## Result

View File

@@ -9,7 +9,7 @@ Spectre.Console can render [FIGlet](http://www.figlet.org/) text by using the `F
## Default font
```csharp
AnsiConsole.Render(
AnsiConsole.Write(
new FigletText("Hello")
.LeftAligned()
.Color(Color.Red));
@@ -23,7 +23,7 @@ AnsiConsole.Render(
```csharp
var font = FigletFont.Load("starwars.flf");
AnsiConsole.Render(
AnsiConsole.Write(
new FigletText(font, "Hello")
.LeftAligned()
.Color(Color.Red));

View File

@@ -18,7 +18,7 @@ To render a rule without a title:
```csharp
var rule = new Rule();
AnsiConsole.Render(rule);
AnsiConsole.Write(rule);
```
## Title
@@ -27,7 +27,7 @@ You can set the rule title markup text.
```csharp
var rule = new Rule("[red]Hello[/]");
AnsiConsole.Render(rule);
AnsiConsole.Write(rule);
```
```text
@@ -41,7 +41,7 @@ You can set the rule's title alignment.
```csharp
var rule = new Rule("[red]Hello[/]");
rule.Alignment = Justify.Left;
AnsiConsole.Render(rule);
AnsiConsole.Write(rule);
```
```text
@@ -53,7 +53,7 @@ You can also specify it via an extension method:
```csharp
var rule = new Rule("[red]Hello[/]");
rule.LeftAligned();
AnsiConsole.Render(rule);
AnsiConsole.Write(rule);
```
```text
@@ -66,12 +66,12 @@ AnsiConsole.Render(rule);
```csharp
var rule = new Rule("[red]Hello[/]");
rule.Style = Style.Parse("red dim");
AnsiConsole.Render(rule);
AnsiConsole.Write(rule);
```
You can also specify it via an extension method
```csharp
var rule = new Rule("[red]Hello[/]");
rule.RuleStyle("red dim");
AnsiConsole.Render(rule);
AnsiConsole.Write(rule);
```

View File

@@ -35,7 +35,7 @@ table.AddRow("Baz", "[green]Qux[/]");
table.AddRow(new Markup("[blue]Corgi[/]"), new Panel("Waldo"));
// Render the table to the console
AnsiConsole.Render(table);
AnsiConsole.Write(table);
```
This will render the following output:

View File

@@ -35,7 +35,7 @@ bar.AddNode(new Calendar(2020, 12)
.HideHeader());
// Render the tree
AnsiConsole.Render(root);
AnsiConsole.Write(root);
```
## Collapsing nodes

View File

@@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"cake.tool": {
"version": "1.1.0",
"version": "2.0.0-rc0001",
"commands": [
"dotnet-cake"
]

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Delegates</ExampleName>
<ExampleDescription>Demonstrates how to specify commands as delegates.</ExampleDescription>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Demo</ExampleName>
<ExampleDescription>Demonstrates the most common use cases of Spectre.Cli.</ExampleDescription>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Dynamic</ExampleName>
<ExampleDescription>Demonstrates how to define dynamic commands.</ExampleDescription>

View File

@@ -15,7 +15,12 @@ namespace Spectre.Console.Examples
public object Resolve(Type type)
{
return _provider.GetRequiredService(type);
if (type == null)
{
return null;
}
return _provider.GetService(type);
}
public void Dispose()

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Injection</ExampleName>
<ExampleDescription>Demonstrates how to use dependency injection with Spectre.Cli.</ExampleDescription>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<IsPackable>false</IsPackable>
<ExampleName>Logging</ExampleName>
<ExampleDescription>Demonstrates how to dynamically configure Serilog for logging using parameters from a command.</ExampleDescription>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Borders</ExampleTitle>
<ExampleDescription>Demonstrates the different kind of borders.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Calendars</ExampleTitle>
<ExampleDescription>Demonstrates how to render calendars.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Canvas</ExampleTitle>
<ExampleDescription>Demonstrates how to render pixels and images.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Charts</ExampleTitle>
<ExampleDescription>Demonstrates how to render charts in a console.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Colors</ExampleTitle>
<ExampleDescription>Demonstrates how to use [yellow]c[/][red]o[/][green]l[/][blue]o[/][aqua]r[/][lime]s[/] in the console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Columns</ExampleTitle>
<ExampleDescription>Demonstrates how to render data into columns.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Cursor</ExampleTitle>
<ExampleDescription>Demonstrates how to move the cursor.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Emojis</ExampleTitle>
<ExampleDescription>Demonstrates how to render emojis.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Exceptions</ExampleTitle>
<ExampleDescription>Demonstrates how to render formatted exceptions.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Figlet</ExampleTitle>
<ExampleDescription>Demonstrates how to render FIGlet text.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Grids</ExampleTitle>
<ExampleDescription>Demonstrates how to render grids in a console.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Info</ExampleTitle>
<ExampleDescription>Displays the capabilities of the current console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Links</ExampleTitle>
<ExampleDescription>Demonstrates how to render links in a console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Live</ExampleTitle>
<ExampleDescription>Demonstrates how to do live updates.</ExampleDescription>
<ExampleGroup>Live</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>LiveTable</ExampleTitle>
<ExampleDescription>Demonstrates how to do live updates in a table.</ExampleDescription>
<ExampleGroup>Live</ExampleGroup>

View File

@@ -0,0 +1,2 @@
global using Spectre.Console;
global using static Spectre.Console.AnsiConsole;

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<ExampleTitle>Minimal</ExampleTitle>
<ExampleDescription>Demonstrates a minimal console application.</ExampleDescription>
<ExampleGroup>Live</ExampleGroup>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Shared\Shared.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,13 @@
// Write a markup line to the console
MarkupLine("[yellow]Hello[/], [blue]World[/]!");
// Write text to the console
WriteLine("Hello, World!");
// Write a table to the console
Write(new Table()
.RoundedBorder()
.AddColumns("[red]Greeting[/]", "[red]Subject[/]")
.AddRow("[yellow]Hello[/]", "World")
.AddRow("[green]Oh hi[/]", "[blue u]Mark[/]"));

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Panels</ExampleTitle>
<ExampleDescription>Demonstrates how to render items in panels.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Progress</ExampleTitle>
<ExampleDescription>Demonstrates how to show progress bars.</ExampleDescription>
<ExampleGroup>Status</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>9</LangVersion>
<ExampleTitle>Prompt</ExampleTitle>
<ExampleDescription>Demonstrates how to get input from a user.</ExampleDescription>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Rules</ExampleTitle>
<ExampleDescription>Demonstrates how to render horizontal rules (lines).</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Showcase</ExampleTitle>
<ExampleDescription>Demonstation of Spectre.Console.</ExampleDescription>
<ExampleGroup>Misc</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Status</ExampleTitle>
<ExampleDescription>Demonstrates how to show status updates.</ExampleDescription>
<ExampleGroup>Status</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Tables</ExampleTitle>
<ExampleDescription>Demonstrates how to render tables in a console.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleTitle>Trees</ExampleTitle>
<ExampleDescription>Demonstrates how to render trees in a console.</ExampleDescription>
<ExampleGroup>Widgets</ExampleGroup>

View File

@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{2571F1BD-6556-4F96-B27B-B6190E1BF13A}"
EndProject
@@ -65,6 +65,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Trees", "Console\Trees\Tree
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LiveTable", "Console\LiveTable\LiveTable.csproj", "{E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Minimal", "Console\Minimal\Minimal.csproj", "{1780A30A-397A-4CC3-B2A0-A385D9081FA2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -423,6 +425,18 @@ Global
{E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Release|x64.Build.0 = Release|Any CPU
{E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Release|x86.ActiveCfg = Release|Any CPU
{E5FAAFB4-1D0F-4E29-A94F-A647D64AE64E}.Release|x86.Build.0 = Release|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Debug|x64.ActiveCfg = Debug|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Debug|x64.Build.0 = Debug|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Debug|x86.ActiveCfg = Debug|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Debug|x86.Build.0 = Debug|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|Any CPU.Build.0 = Release|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|x64.ActiveCfg = Release|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|x64.Build.0 = Release|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|x86.ActiveCfg = Release|Any CPU
{1780A30A-397A-4CC3-B2A0-A385D9081FA2}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<TargetFramework>net6.0</TargetFramework>
<ExampleVisible>false</ExampleVisible>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@@ -1,7 +1,7 @@
{
"projects": [ "src", "tests" ],
"sdk": {
"version": "5.0.301",
"version": "6.0.100",
"rollForward": "latestFeature"
}
}

View File

@@ -34,10 +34,6 @@
<ItemGroup Label="Package References">
<PackageReference Include="MinVer" PrivateAssets="All" Version="2.4.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" Version="1.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.3">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.312">
<PrivateAssets>All</PrivateAssets>
</PackageReference>

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net6.0;net5.0;netstandard2.0</TargetFrameworks>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<Description>A library that extends Spectre.Console with ImageSharp superpowers.</Description>

View File

@@ -0,0 +1,191 @@
using System;
using Spectre.Console.Cli;
namespace Spectre.Console.Testing
{
/// <summary>
/// This is a utility class for implementors of
/// <see cref="ITypeRegistrar"/> and corresponding <see cref="ITypeResolver"/>.
/// </summary>
public sealed class TypeRegistrarBaseTests
{
private readonly Func<ITypeRegistrar> _registrarFactory;
/// <summary>
/// Initializes a new instance of the <see cref="TypeRegistrarBaseTests"/> class.
/// </summary>
/// <param name="registrarFactory">The factory to create a new, clean <see cref="ITypeRegistrar"/> to be used for each test.</param>
public TypeRegistrarBaseTests(Func<ITypeRegistrar> registrarFactory)
{
_registrarFactory = registrarFactory;
}
/// <summary>
/// Runs all tests.
/// </summary>
/// <exception cref="TestFailedException">This exception is raised, if a test fails.</exception>
public void RunAllTests()
{
var testCases = new Action<ITypeRegistrar>[]
{
RegistrationsCanBeResolved,
InstanceRegistrationsCanBeResolved,
LazyRegistrationsCanBeResolved,
ResolvingNotRegisteredServiceReturnsNull,
ResolvingNullTypeReturnsNull,
};
foreach (var test in testCases)
{
test(_registrarFactory());
}
}
private static void ResolvingNullTypeReturnsNull(ITypeRegistrar registrar)
{
// Given no registration
var resolver = registrar.Build();
try
{
// When
var actual = resolver.Resolve(null);
// Then
if (actual != null)
{
throw new TestFailedException(
$"Expected the resolver to resolve null, since null was requested as the service type. Actually resolved {actual.GetType().Name}.");
}
}
catch (Exception ex)
{
throw new TestFailedException(
$"Expected the resolver not to throw, but caught {ex.GetType().Name}.", ex);
}
}
private static void ResolvingNotRegisteredServiceReturnsNull(ITypeRegistrar registrar)
{
// Given no registration
var resolver = registrar.Build();
try
{
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (actual != null)
{
throw new TestFailedException(
$"Expected the resolver to resolve null, since no service was registered. Actually resolved {actual.GetType().Name}.");
}
}
catch (Exception ex)
{
throw new TestFailedException(
$"Expected the resolver not to throw, but caught {ex.GetType().Name}.", ex);
}
}
private static void RegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
registrar.Register(typeof(IMockService), typeof(MockService));
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (actual == null)
{
throw new TestFailedException(
$"Expected the resolver to resolve an instance of {nameof(MockService)}. Actually resolved null.");
}
if (actual is not MockService)
{
throw new TestFailedException(
$"Expected the resolver to resolve an instance of {nameof(MockService)}. Actually resolved {actual.GetType().Name}.");
}
}
private static void InstanceRegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
var instance = new MockService();
registrar.RegisterInstance(typeof(IMockService), instance);
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (!ReferenceEquals(actual, instance))
{
throw new TestFailedException(
"Expected the resolver to resolve exactly the registered instance.");
}
}
private static void LazyRegistrationsCanBeResolved(ITypeRegistrar registrar)
{
// Given
var instance = new MockService();
var factoryCalled = false;
registrar.RegisterLazy(typeof(IMockService), () =>
{
factoryCalled = true;
return instance;
});
var resolver = registrar.Build();
// When
var actual = resolver.Resolve(typeof(IMockService));
// Then
if (!factoryCalled)
{
throw new TestFailedException(
"Expected the factory to be called, to resolve the lazy registration.");
}
if (!ReferenceEquals(actual, instance))
{
throw new TestFailedException(
"Expected the resolver to return exactly the result of the lazy-registered factory.");
}
}
/// <summary>
/// internal use only.
/// </summary>
private interface IMockService
{
}
private class MockService : IMockService
{
}
/// <summary>
/// Exception, to be raised when a test fails.
/// </summary>
public sealed class TestFailedException : Exception
{
/// <inheritdoc cref="Exception" />
public TestFailedException(string message)
: base(message)
{
}
/// <inheritdoc cref="Exception" />
public TestFailedException(string message, Exception inner)
: base(message, inner)
{
}
}
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net6.0;net5.0;netstandard2.0</TargetFrameworks>
<IsTestProject>false</IsTestProject>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>

View File

@@ -103,6 +103,11 @@ namespace Spectre.Console.Cli
throw;
}
if (_configurator.Settings.ExceptionHandler != null)
{
return _configurator.Settings.ExceptionHandler(ex);
}
// Render the exception.
var pretty = GetRenderableErrorMessage(ex);
if (pretty != null)

View File

@@ -224,5 +224,39 @@ namespace Spectre.Console.Cli
return configurator.AddDelegate<TSettings>(name, (c, _) => func(c));
}
/// <summary>
/// Sets the ExceptionsHandler.
/// <para>Setting <see cref="ICommandAppSettings.ExceptionHandler"/> this way will use the
/// default exit code of -1.</para>
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Action<Exception> exceptionHandler)
{
return configurator.SetExceptionHandler(ex =>
{
exceptionHandler(ex);
return -1;
});
}
/// <summary>
/// Sets the ExceptionsHandler.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="exceptionHandler">The Action that handles the exception.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetExceptionHandler(this IConfigurator configurator, Func<Exception, int>? exceptionHandler)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.Settings.ExceptionHandler = exceptionHandler;
return configurator;
}
}
}

View File

@@ -1,3 +1,5 @@
using System;
namespace Spectre.Console.Cli
{
/// <summary>
@@ -43,6 +45,8 @@ namespace Spectre.Console.Cli
/// <summary>
/// Gets or sets a value indicating whether or not exceptions should be propagated.
/// <para>Setting this to <c>true</c> will disable default Exception handling and
/// any <see cref="ExceptionHandler"/>, if set.</para>
/// </summary>
bool PropagateExceptions { get; set; }
@@ -50,5 +54,11 @@ namespace Spectre.Console.Cli
/// Gets or sets a value indicating whether or not examples should be validated.
/// </summary>
bool ValidateExamples { get; set; }
/// <summary>
/// Gets or sets a handler for Exceptions.
/// <para>This handler will not be called, if <see cref="PropagateExceptions"/> is set to <c>true</c>.</para>
/// </summary>
public Func<Exception, int>? ExceptionHandler { get; set; }
}
}

View File

@@ -11,7 +11,7 @@ namespace Spectre.Console.Cli
/// Resolves an instance of the specified type.
/// </summary>
/// <param name="type">The type to resolve.</param>
/// <returns>An instance of the specified type.</returns>
/// <returns>An instance of the specified type, or <c>null</c> if no registration for the specified type exists.</returns>
object? Resolve(Type? type);
}
}

View File

@@ -28,16 +28,9 @@ namespace Spectre.Console.Cli
private static CommandSettings CreateSettings(ITypeResolver resolver, Type settingsType)
{
try
if (resolver.Resolve(settingsType) is CommandSettings settings)
{
if (resolver.Resolve(settingsType) is CommandSettings settings)
{
return settings;
}
}
catch
{
// ignored
return settings;
}
if (Activator.CreateInstance(settingsType) is CommandSettings instance)

View File

@@ -17,6 +17,8 @@ namespace Spectre.Console.Cli
public ParsingMode ParsingMode =>
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
public Func<Exception, int>? ExceptionHandler { get; set; }
public CommandAppSettings(ITypeRegistrar registrar)
{
Registrar = new TypeRegistrar(registrar);

View File

@@ -19,9 +19,6 @@ namespace Spectre.Console.Cli
throw new ArgumentNullException(nameof(value));
}
var keyConverter = GetConverter(keyType);
var valueConverter = GetConverter(valueType);
var parts = value.Split(new[] { '=' }, StringSplitOptions.None);
if (parts.Length < 1 || parts.Length > 2)
{
@@ -47,15 +44,16 @@ namespace Spectre.Console.Cli
}
}
return (Parse(keyConverter, keyType, stringkey),
Parse(valueConverter, valueType, stringValue));
return (Parse(stringkey, keyType),
Parse(stringValue, valueType));
}
private static object Parse(TypeConverter converter, Type type, string value)
private static object? Parse(string value, Type targetType)
{
try
{
return converter.ConvertTo(value, type);
var converter = GetConverter(targetType);
return converter.ConvertFrom(value);
}
catch
{

View File

@@ -28,7 +28,7 @@ namespace Spectre.Console.Cli
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
string? description, TypeConverterAttribute? converter,
DefaultValueAttribute? defaultValue,
PairDeconstructorAttribute? deconstuctor,
PairDeconstructorAttribute? deconstructor,
ParameterValueProviderAttribute? valueProvider,
IEnumerable<ParameterValidationAttribute> validators, bool required)
{
@@ -39,7 +39,7 @@ namespace Spectre.Console.Cli
Description = description;
Converter = converter;
DefaultValue = defaultValue;
PairDeconstructor = deconstuctor;
PairDeconstructor = deconstructor;
ValueProvider = valueProvider;
Validators = new List<ParameterValidationAttribute>(validators ?? Array.Empty<ParameterValidationAttribute>());
Required = required;

View File

@@ -20,14 +20,9 @@ namespace Spectre.Console.Cli
try
{
if (_resolver != null)
var obj = _resolver?.Resolve(type);
if (obj != null)
{
var obj = _resolver.Resolve(type);
if (obj == null)
{
throw CommandRuntimeException.CouldNotResolveType(type);
}
return obj;
}

View File

@@ -7,7 +7,7 @@ namespace Spectre.Console.Cli
/// </summary>
/// <typeparam name="TKey">The key type.</typeparam>
/// <typeparam name="TValue">The value type.</typeparam>
public abstract class PairDeconstuctor<TKey, TValue> : IPairDeconstructor
public abstract class PairDeconstructor<TKey, TValue> : IPairDeconstructor
{
/// <summary>
/// Deconstructs the provided <see cref="string"/> into a pair.
@@ -27,4 +27,15 @@ namespace Spectre.Console.Cli
return Deconstruct(value);
}
}
/// <summary>
/// Base class for a pair deconstructor.
/// </summary>
/// <typeparam name="TKey">The key type.</typeparam>
/// <typeparam name="TValue">The value type.</typeparam>
/// <remarks>This class is misspelled, use <see cref="PairDeconstructor{TKey,TValue}"/> instead.</remarks>
[Obsolete("Use PairDeconstructor instead")]
public abstract class PairDeconstuctor<TKey, TValue> : PairDeconstructor<TKey, TValue>
{
}
}

View File

@@ -112,6 +112,41 @@ namespace Spectre.Console
.ToList();
}
/// <summary>
/// Returns all parent items of the given <paramref name="item"/>.
/// </summary>
/// <param name="item">The item for which to find the parents.</param>
/// <returns>The parent items, or an empty list, if the given item has no parents.</returns>
public IEnumerable<T> GetParents(T item)
{
var promptItem = Tree.Find(item);
if (promptItem == null)
{
throw new ArgumentOutOfRangeException(nameof(item), "Item not found in tree.");
}
var parents = new List<ListPromptItem<T>>();
while (promptItem.Parent != null)
{
promptItem = promptItem.Parent;
parents.Add(promptItem);
}
return parents
.ReverseEnumerable()
.Select(x => x.Data);
}
/// <summary>
/// Returns the parent item of the given <paramref name="item"/>.
/// </summary>
/// <param name="item">The item for which to find the parent.</param>
/// <returns>The parent item, or <c>null</c> if the given item has no parent.</returns>
public T? GetParent(T item)
{
return GetParents(item).LastOrDefault();
}
/// <inheritdoc/>
ListPromptInputResult IListPromptStrategy<T>.HandleInput(ConsoleKeyInfo key, ListPromptState<T> state)
{
@@ -218,7 +253,7 @@ namespace Spectre.Console
var text = (Converter ?? TypeConverterHelper.ConvertToString)?.Invoke(item.Node.Data) ?? item.Node.Data.ToString() ?? "?";
if (current)
{
text = text.RemoveMarkup();
text = text.RemoveMarkup().EscapeMarkup();
}
var checkbox = item.Node.IsSelected

View File

@@ -167,7 +167,7 @@ namespace Spectre.Console
var text = (Converter ?? TypeConverterHelper.ConvertToString)?.Invoke(item.Node.Data) ?? item.Node.Data.ToString() ?? "?";
if (current)
{
text = text.RemoveMarkup();
text = text.RemoveMarkup().EscapeMarkup();
}
grid.AddRow(new Markup(indent + prompt + " " + text, style));

View File

@@ -275,7 +275,7 @@ namespace Spectre.Console
}
/// <summary>
/// Replaces prompt user input with asterixes in the console.
/// Replaces prompt user input with asterisks in the console.
/// </summary>
/// <typeparam name="T">The prompt type.</typeparam>
/// <param name="obj">The prompt.</param>

View File

@@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net5.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net6.0;net5.0;netstandard2.0</TargetFrameworks>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
<NoWarn>SA1633</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Wcwidth" Version="0.2.0" />
<PackageReference Include="System.Memory" Version="4.5.0" Condition="'$(TargetFramework)' == 'netstandard2.0'" />
</ItemGroup>
@@ -17,6 +17,7 @@
<None Remove="Widgets\Figlet\Fonts\Standard.flf" />
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
<None Include="..\.editorconfig" Link="Cli\.editorconfig" />
<InternalsVisibleTo Include="$(AssemblyName).Tests" />
</ItemGroup>
<ItemGroup>
@@ -26,6 +27,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Wcwidth.Sources" Version="0.6.0">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
@@ -33,4 +37,8 @@
<GenerateNullableAttributes>False</GenerateNullableAttributes>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants)TRACE;WCWIDTH_VISIBILITY_INTERNAL</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -96,6 +96,21 @@ namespace Spectre.Console
return null;
}
}
else if (int.TryParse(part, out var number))
{
if (number < 0)
{
error = $"Color number must be greater than or equal to 0 (was {number})";
return null;
}
else if (number > 255)
{
error = $"Color number must be less than or equal to 255 (was {number})";
return null;
}
color = number;
}
else
{
error = !foreground

View File

@@ -149,12 +149,12 @@ namespace Spectre.Console
{
builder.AppendWithStyle(
settings.Style.NonEmphasized,
type.Substring(0, index + 1).EscapeMarkup());
type.Substring(0, index + 1));
}
builder.AppendWithStyle(
color,
type.Substring(index + 1, type.Length - index - 1).EscapeMarkup());
type.Substring(index + 1, type.Length - index - 1));
}
else
{

View File

@@ -1,6 +1,6 @@
System.InvalidOperationException: Something threw!
System.InvalidOperationException: Throwing!
at Spectre.Console.Tests.Data.TestExceptions.GenericMethodThatThrows[[T0,T1,TRet]](Nullable`1 number) in /xyz/Exceptions.cs:nn
at Spectre.Console.Tests.Data.TestExceptions.GenericMethodThatThrows[T0,T1,TRet](Nullable`1 number) in /xyz/Exceptions.cs:nn
at Spectre.Console.Tests.Data.TestExceptions.ThrowWithGenericInnerException() in /xyz/Exceptions.cs:nn
at Spectre.Console.Tests.Data.TestExceptions.ThrowWithGenericInnerException() in /xyz/Exceptions.cs:nn
at Spectre.Console.Tests.Unit.ExceptionTests.<>c.<Should_Write_Exceptions_With_Generic_Type_Parameters_In_Callsite_As_Expected>b__4_0() in /xyz/ExceptionTests.cs:nn

View File

@@ -1,15 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">net5.0;net48</TargetFrameworks>
<TargetFramework Condition="!$([MSBuild]::IsOSPlatform('Windows'))">net5.0</TargetFramework>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('Windows'))">net6.0;net5.0;net48</TargetFrameworks>
<TargetFrameworks Condition="!$([MSBuild]::IsOSPlatform('Windows'))">net6.0;net5.0</TargetFrameworks>
<LangVersion>9.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Spectre.Console\Cli\Internal\Constants.cs" Link="Imported\Constants.cs" />
</ItemGroup>
<ItemGroup>
<None Remove="Data\starwars.flf" />
</ItemGroup>
@@ -24,7 +20,6 @@
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing.XUnit" Version="1.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="2.6.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
<PackageReference Include="Nullable" Version="1.3.0" PrivateAssets="all" />
<PackageReference Include="Shouldly" Version="4.0.3" />
<PackageReference Include="Spectre.Verify.Extensions" Version="0.3.0" />
<PackageReference Include="Verify.Xunit" Version="9.0.0-beta.1" />

View File

@@ -0,0 +1,100 @@
using System;
using Shouldly;
using Spectre.Console.Cli;
using Spectre.Console.Tests.Data;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed partial class CommandAppTests
{
public sealed class Exception_Handling
{
[Fact]
public void Should_Not_Propagate_Runtime_Exceptions_If_Not_Explicitly_Told_To_Do_So()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
var result = app.Run(new[] { "animal", "4", "dog", "101", "--name", "Rufus" });
// Then
result.ExitCode.ShouldBe(-1);
}
[Fact]
public void Should_Not_Propagate_Exceptions_If_Not_Explicitly_Told_To_Do_So()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddCommand<ThrowingCommand>("throw");
});
// When
var result = app.Run(new[] { "throw" });
// Then
result.ExitCode.ShouldBe(-1);
}
[Fact]
public void Should_Handle_Exceptions_If_ExceptionHandler_Is_Set_Using_Action()
{
// Given
var exceptionHandled = false;
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddCommand<ThrowingCommand>("throw");
config.SetExceptionHandler(_ =>
{
exceptionHandled = true;
});
});
// When
var result = app.Run(new[] { "throw" });
// Then
result.ExitCode.ShouldBe(-1);
exceptionHandled.ShouldBeTrue();
}
[Fact]
public void Should_Handle_Exceptions_If_ExceptionHandler_Is_Set_Using_Function()
{
// Given
var exceptionHandled = false;
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddCommand<ThrowingCommand>("throw");
config.SetExceptionHandler(_ =>
{
exceptionHandled = true;
return -99;
});
});
// When
var result = app.Run(new[] { "throw" });
// Then
result.ExitCode.ShouldBe(-99);
exceptionHandled.ShouldBeTrue();
}
}
}
}

View File

@@ -35,6 +35,12 @@ namespace Spectre.Console.Tests.Unit.Cli
public IDictionary<string, int> Values { get; set; }
}
public sealed class DefaultPairDeconstructorEnumValueSettings : CommandSettings
{
[CommandOption("--var <VALUE>")]
public IDictionary<string, DayOfWeek> Values { get; set; }
}
public sealed class LookupSettings : CommandSettings
{
[CommandOption("--var <VALUE>")]
@@ -56,7 +62,7 @@ namespace Spectre.Console.Tests.Unit.Cli
public IReadOnlyDictionary<string, string> Values { get; set; }
}
public sealed class StringIntDeconstructor : PairDeconstuctor<string, string>
public sealed class StringIntDeconstructor : PairDeconstructor<string, string>
{
protected override (string Key, string Value) Deconstruct(string value)
{
@@ -153,6 +159,35 @@ namespace Spectre.Console.Tests.Unit.Cli
});
}
[Fact]
public void Should_Map_Pairs_With_Enum_Value_To_Pair_Deconstructable_Collection_Using_Default_Deconstructor()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<GenericCommand<DefaultPairDeconstructorEnumValueSettings>>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = app.Run(new[]
{
"--var", "foo=Monday",
"--var", "bar=Tuesday",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<DefaultPairDeconstructorEnumValueSettings>().And(pair =>
{
pair.Values.ShouldNotBeNull();
pair.Values.Count.ShouldBe(2);
pair.Values["foo"].ShouldBe(DayOfWeek.Monday);
pair.Values["bar"].ShouldBe(DayOfWeek.Tuesday);
});
}
[Theory]
[InlineData("foo=1=2", "Error: The value 'foo=1=2' is not in a correct format")]
[InlineData("foo=1=2=3", "Error: The value 'foo=1=2=3' is not in a correct format")]

View File

@@ -816,46 +816,5 @@ namespace Spectre.Console.Tests.Unit.Cli
result.Context.Remaining.Raw[4].ShouldBe("qux");
}
}
public sealed class Exception_Handling
{
[Fact]
public void Should_Not_Propagate_Runtime_Exceptions_If_Not_Explicitly_Told_To_Do_So()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<HorseCommand>("horse");
});
});
// When
var result = app.Run(new[] { "animal", "4", "dog", "101", "--name", "Rufus" });
// Then
result.ExitCode.ShouldBe(-1);
}
[Fact]
public void Should_Not_Propagate_Exceptions_If_Not_Explicitly_Told_To_Do_So()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.AddCommand<ThrowingCommand>("throw");
});
// When
var result = app.Run(new[] { "throw" });
// Then
result.ExitCode.ShouldBe(-1);
}
}
}
}

View File

@@ -0,0 +1,16 @@
using Spectre.Console.Cli;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit.Cli
{
public sealed class DefaultTypeRegistrarTests
{
[Fact]
public void Should_Pass_Base_Registrar_Tests()
{
var harness = new TypeRegistrarBaseTests(() => new DefaultTypeRegistrar());
harness.RunAllTests();
}
}
}

View File

@@ -82,5 +82,75 @@ namespace Spectre.Console.Tests.Unit
// Then
choice.IsSelected.ShouldBeTrue();
}
[Fact]
public void Should_Get_The_Direct_Parent()
{
// Given
var prompt = new MultiSelectionPrompt<string>();
prompt.AddChoice("root").AddChild("level-1").AddChild("level-2").AddChild("item");
// When
var actual = prompt.GetParent("item");
// Then
actual.ShouldBe("level-2");
}
[Fact]
public void Should_Get_The_List_Of_All_Parents()
{
// Given
var prompt = new MultiSelectionPrompt<string>();
prompt.AddChoice("root").AddChild("level-1").AddChild("level-2").AddChild("item");
// When
var actual = prompt.GetParents("item");
// Then
actual.ShouldBe(new []{"root", "level-1", "level-2"});
}
[Fact]
public void Should_Get_An_Empty_List_Of_Parents_For_Root_Node()
{
// Given
var prompt = new MultiSelectionPrompt<string>();
prompt.AddChoice("root");
// When
var actual = prompt.GetParents("root");
// Then
actual.ShouldBeEmpty();
}
[Fact]
public void Should_Get_Null_As_Direct_Parent_Of_Root_Node()
{
// Given
var prompt = new MultiSelectionPrompt<string>();
prompt.AddChoice("root");
// When
var actual = prompt.GetParent("root");
// Then
actual.ShouldBeNull();
}
[Fact]
public void Should_Throw_When_Getting_Parents_Of_Non_Existing_Node()
{
// Given
var prompt = new MultiSelectionPrompt<string>();
prompt.AddChoice("root").AddChild("level-1").AddChild("level-2").AddChild("item");
// When
Action action = () => prompt.GetParents("non-existing");
// Then
action.ShouldThrow<ArgumentOutOfRangeException>();
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using Shouldly;
using Spectre.Console.Testing;
using Xunit;
namespace Spectre.Console.Tests.Unit
{
public sealed class SelectionPromptTests
{
[Fact]
[GitHubIssue(608)]
public void Should_Not_Throw_When_Selecting_An_Item_With_Escaped_Markup()
{
// Given
var console = new TestConsole();
console.Profile.Capabilities.Interactive = true;
console.Input.PushKey(ConsoleKey.Enter);
var input = "[red]This text will never be red[/]".EscapeMarkup();
// When
var prompt = new SelectionPrompt<string>()
.Title("Select one")
.AddChoices(input);
prompt.Show(console);
// Then
console.Output.ShouldContain(@"[red]This text will never be red[/]");
}
}
}

View File

@@ -275,6 +275,31 @@ namespace Spectre.Console.Tests.Unit
result.Background.ShouldBe(Color.Blue);
}
[Theory]
[InlineData("12 on 24")]
public void Should_Parse_Colors_Numbers_Correctly(string style)
{
// Given, When
var result = Style.Parse(style);
// Then
result.Foreground.ShouldBe(Color.Blue);
result.Background.ShouldBe(Color.DeepSkyBlue4_1);
}
[Theory]
[InlineData("-12", "Color number must be greater than or equal to 0 (was -12)")]
[InlineData("256", "Color number must be less than or equal to 255 (was 256)")]
public void Should_Return_Error_If_Color_Number_Is_Invalid(string style, string expected)
{
// Given, When
var result = Record.Exception(() => Style.Parse(style));
// Then
result.ShouldNotBeNull();
result.Message.ShouldBe(expected);
}
[Theory]
[InlineData("rgb()", "Invalid RGB color 'rgb()'.")]
[InlineData("rgb(", "Invalid RGB color 'rgb('.")]