mirror of
https://github.com/spectreconsole/spectre.console.git
synced 2025-10-25 15:19:23 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
018f4ebd17 | ||
|
|
404b052a5f | ||
|
|
dac2097321 | ||
|
|
6acf9b8c63 | ||
|
|
3ec0fee795 | ||
|
|
5843a4545e | ||
|
|
d64d3ec770 | ||
|
|
4dcbd30285 | ||
|
|
1334319dd1 | ||
|
|
714cf179cb | ||
|
|
70da3f40ff | ||
|
|
5075732564 | ||
|
|
10467a2912 | ||
|
|
a7ab1b690e | ||
|
|
142942b8a2 | ||
|
|
0bab835293 | ||
|
|
a6af9a6842 | ||
|
|
d3f4f5f208 | ||
|
|
cbbdb3369c | ||
|
|
6740f0b02b | ||
|
|
f7f99ec899 | ||
|
|
955fe07bac | ||
|
|
819b948e78 | ||
|
|
baa8220a52 | ||
|
|
9cd7b24e65 | ||
|
|
04610cf492 | ||
|
|
f4183e0462 | ||
|
|
29846ba505 | ||
|
|
92318ce1fb | ||
|
|
720db951f3 | ||
|
|
e042fe15e4 | ||
|
|
62b30d6072 | ||
|
|
beca0b2419 | ||
|
|
de847b90e4 | ||
|
|
7b9553dd22 | ||
|
|
f223f6061c |
@@ -0,0 +1,81 @@
|
|||||||
|
Title: Spectre.Console 0.46 released!
|
||||||
|
Description: .NET 7 support, Layout Widget, JSON rendering
|
||||||
|
Published: 2023-01-10
|
||||||
|
Category: Release Notes
|
||||||
|
Excluded: false
|
||||||
|
---
|
||||||
|
|
||||||
|
Happy new year! 🎉
|
||||||
|
Version 0.46 of Spectre.Console has been released!
|
||||||
|
|
||||||
|
A lot has happened since the last release, but the most notable additions
|
||||||
|
and changes are support for [.NET 7](https://devblogs.microsoft.com/dotnet/announcing-dotnet-7/),
|
||||||
|
the new [Layout](https://spectreconsole.net/widgets/layout) widget, and
|
||||||
|
[rendering of JSON](https://spectreconsole.net/widgets/json). There has also been a lot of long overdue work
|
||||||
|
on the command line argument parser.
|
||||||
|
|
||||||
|
## New Contributors
|
||||||
|
* [@GaryMcD](https://github.com/GaryMcD) made their first contribution in [#961](https://github.com/spectreconsole/spectre.console/pull/961)
|
||||||
|
* [@eduherminio](https://github.com/eduherminio) made their first contribution in [#964](https://github.com/spectreconsole/spectre.console/pull/964)
|
||||||
|
* [@Saalvage](https://github.com/Saalvage) made their first contribution in [#976](https://github.com/spectreconsole/spectre.console/pull/976)
|
||||||
|
* [@BenjaminMichaelis](https://github.com/BenjaminMichaelis) made their first contribution in [#1000](https://github.com/spectreconsole/spectre.console/pull/1000)
|
||||||
|
* [@nilaoda](https://github.com/nilaoda) made their first contribution in [#1012](https://github.com/spectreconsole/spectre.console/pull/1012)
|
||||||
|
* [@picture](https://github.com/picture)-vision made their first contribution in [#1013](https://github.com/spectreconsole/spectre.console/pull/1013)
|
||||||
|
* [@patrickfreilinger](https://github.com/patrickfreilinger) made their first contribution in [#1016](https://github.com/spectreconsole/spectre.console/pull/1016)
|
||||||
|
* [@sowa](https://github.com/sowa)705 made their first contribution in [#1014](https://github.com/spectreconsole/spectre.console/pull/1014)
|
||||||
|
* [@ardalis](https://github.com/ardalis) made their first contribution in [#1021](https://github.com/spectreconsole/spectre.console/pull/1021)
|
||||||
|
* [@Elisha](https://github.com/Elisha)-Aguilera made their first contribution in [#1038](https://github.com/spectreconsole/spectre.console/pull/1038)
|
||||||
|
* [@wguner](https://github.com/wguner) made their first contribution in [#1044](https://github.com/spectreconsole/spectre.console/pull/1044)
|
||||||
|
* [@bcwood](https://github.com/bcwood) made their first contribution in [#1068](https://github.com/spectreconsole/spectre.console/pull/1068)
|
||||||
|
* [@FrankRay](https://github.com/FrankRay)78 made their first contribution in [#1073](https://github.com/spectreconsole/spectre.console/pull/1073)
|
||||||
|
* [@tomkerkhove](https://github.com/tomkerkhove) made their first contribution in [#1089](https://github.com/spectreconsole/spectre.console/pull/1089)
|
||||||
|
* [@ArveSystad](https://github.com/ArveSystad) made their first contribution in [#1090](https://github.com/spectreconsole/spectre.console/pull/1090)
|
||||||
|
* [@maije](https://github.com/maije) made their first contribution in [#1096](https://github.com/spectreconsole/spectre.console/pull/1096)
|
||||||
|
* [@krisrok](https://github.com/krisrok) made their first contribution in [#953](https://github.com/spectreconsole/spectre.console/pull/953)
|
||||||
|
|
||||||
|
## What's changed?
|
||||||
|
* Add support for .NET 7.0 by [@patriksvensson](https://github.com/patriksvensson) in [#1056](https://github.com/spectreconsole/spectre.console/pull/1056)
|
||||||
|
* Add `Layout` widget by [@patriksvensson](https://github.com/patriksvensson) in [#1041](https://github.com/spectreconsole/spectre.console/pull/1041)
|
||||||
|
* Add JSON text renderer by [@patriksvensson](https://github.com/patriksvensson) in [#1086](https://github.com/spectreconsole/spectre.console/pull/1086)
|
||||||
|
* Backward direction of text prompt autocomplete by [@nkochnev](https://github.com/nkochnev) in [#921](https://github.com/spectreconsole/spectre.console/pull/921)
|
||||||
|
* Custom mask for secret by [@GaryMcD](https://github.com/GaryMcD) in [#970](https://github.com/spectreconsole/spectre.console/pull/970)
|
||||||
|
* Allow selections to wrap around by [@Saalvage](https://github.com/Saalvage) in [#976](https://github.com/spectreconsole/spectre.console/pull/976)
|
||||||
|
* Join .NET Foundation by [@patriksvensson](https://github.com/patriksvensson) in [#978](https://github.com/spectreconsole/spectre.console/pull/978)
|
||||||
|
* Adding value: a single semi-colon! by [@johanlindfors](https://github.com/johanlindfors) in [#986](https://github.com/spectreconsole/spectre.console/pull/986)
|
||||||
|
* Fix `@` being used in Figlet font by [@Saalvage](https://github.com/Saalvage) in [#972](https://github.com/spectreconsole/spectre.console/pull/972)
|
||||||
|
* Add new and transferred issues to backlog project by [@patriksvensson](https://github.com/patriksvensson) in [#995](https://github.com/spectreconsole/spectre.console/pull/995)
|
||||||
|
* Pin SDK due to a bug in .NET 6.0.401 by [@patriksvensson](https://github.com/patriksvensson) in [#1011](https://github.com/spectreconsole/spectre.console/pull/1011)
|
||||||
|
* Remove period trimming by [@BenjaminMichaelis](https://github.com/BenjaminMichaelis) in [#1008](https://github.com/spectreconsole/spectre.console/pull/1008)
|
||||||
|
* Allow `PACKET` key on MultiSelectionPrompt by [@nilaoda](https://github.com/nilaoda) in [#1012](https://github.com/spectreconsole/spectre.console/pull/1012)
|
||||||
|
* Added Suckless Simple Terminal to list of ANSI terminals by [@picture](https://github.com/picture)-vision in [#1013](https://github.com/spectreconsole/spectre.console/pull/1013)
|
||||||
|
* Add culture option to `TypeConverterHelper`, `TextPrompt` and `AnsiConsole` by [@sowa](https://github.com/sowa)705 in [#1014](https://github.com/spectreconsole/spectre.console/pull/1014)
|
||||||
|
* Minor typo fixes by [@ardalis](https://github.com/ardalis) in [#1021](https://github.com/spectreconsole/spectre.console/pull/1021)
|
||||||
|
* Alignment fixes by [@patriksvensson](https://github.com/patriksvensson) in [#1066](https://github.com/spectreconsole/spectre.console/pull/1066)
|
||||||
|
* `IndexOf` replaced by Count at Add method - Performance issue #975 fixed by [@maije](https://github.com/maije) in [#1096](https://github.com/spectreconsole/spectre.console/pull/1096)
|
||||||
|
* Modified tokenizer not to break on on `]]]` at the end of a style by [@nils](https://github.com/nils)-a in [#1027](https://github.com/spectreconsole/spectre.console/pull/1027)
|
||||||
|
* Command line argument parsing improvements by [@FrankRay](https://github.com/FrankRay)78 in [#1048](https://github.com/spectreconsole/spectre.console/pull/1048)
|
||||||
|
* Show help for default command by [@krisrok](https://github.com/krisrok) in [#953](https://github.com/spectreconsole/spectre.console/pull/953)
|
||||||
|
* Automatically display default values of options in the help page by @0xced in [#1032](https://github.com/spectreconsole/spectre.console/pull/1032)
|
||||||
|
|
||||||
|
## Documentation updates
|
||||||
|
* Add link to documentation in README by [@ardalis](https://github.com/ardalis) in [#1030](https://github.com/spectreconsole/spectre.console/pull/1030)
|
||||||
|
* Update `.NET 5` references in docs by [@eduherminio](https://github.com/eduherminio) in [#964](https://github.com/spectreconsole/spectre.console/pull/964)
|
||||||
|
* Blog date fix by [@phil](https://github.com/phil)-scott-78 in [#963](https://github.com/spectreconsole/spectre.console/pull/963)
|
||||||
|
* Update sponsors by [@tomkerkhove](https://github.com/tomkerkhove) in [#1089](https://github.com/spectreconsole/spectre.console/pull/1089)
|
||||||
|
* Inline `CommandArgument` required/optional style in template parameter docs by [@ArveSystad](https://github.com/ArveSystad) in [#1090](https://github.com/spectreconsole/spectre.console/pull/1090)
|
||||||
|
* Add documentation for `BreakdownChart` by [@BenjaminMichaelis](https://github.com/BenjaminMichaelis) in [#1000](https://github.com/spectreconsole/spectre.console/pull/1000)
|
||||||
|
* Create `Panel` documentation by [@patrickfreilinger](https://github.com/patrickfreilinger) in [#1016](https://github.com/spectreconsole/spectre.console/pull/1016)
|
||||||
|
* Added details for using links within markup. by [@GaryMcD](https://github.com/GaryMcD) in [#961](https://github.com/spectreconsole/spectre.console/pull/961)
|
||||||
|
* Added documentation for `Rows` widget by [@Elisha](https://github.com/Elisha)-Aguilera in [#1038](https://github.com/spectreconsole/spectre.console/pull/1038)
|
||||||
|
* Added documentation guide for `Grid` widget by [@Elisha](https://github.com/Elisha)-Aguilera in [#1043](https://github.com/spectreconsole/spectre.console/pull/1043)
|
||||||
|
* Added documentation guide for the `Padder` widget by [@Elisha](https://github.com/Elisha)-Aguilera in [#1046](https://github.com/spectreconsole/spectre.console/pull/1046)
|
||||||
|
* Created a `Columns` widget documentation by [@wguner](https://github.com/wguner) in [#1044](https://github.com/spectreconsole/spectre.console/pull/1044)
|
||||||
|
* Fixed typo in `Panel` documentation [@bcwood](https://github.com/bcwood) in [#1068](https://github.com/spectreconsole/spectre.console/pull/1068)
|
||||||
|
* Clarified the license for `SixLabors.ImageSharp` by [@FrankRay](https://github.com/FrankRay)78 in [#1073](https://github.com/spectreconsole/spectre.console/pull/1073)
|
||||||
|
* Add documentation for `Layout` by [@patriksvensson](https://github.com/patriksvensson) in [#1127](https://github.com/spectreconsole/spectre.console/pull/1127)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
* Update dependency `Wcwidth.Sources` to `v1` by [@renovate](https://github.com/renovate) in [#969](https://github.com/spectreconsole/spectre.console/pull/969)
|
||||||
|
* Update `actions/setup-dotnet` action to `v3` by [@renovate](https://github.com/renovate) in [#982](https://github.com/spectreconsole/spectre.console/pull/982)
|
||||||
|
* Update dependency `Microsoft.NET.Test.Sdk` to `v17.3.2` by [@renovate](https://github.com/renovate) in [#977](https://github.com/spectreconsole/spectre.console/pull/977)
|
||||||
|
* Update dependency `cake.tool` to `v2.3.0` by [@renovate](https://github.com/renovate) in [#1015](https://github.com/spectreconsole/spectre.console/pull/1015)
|
||||||
@@ -37,8 +37,8 @@ app.Configure(config =>
|
|||||||
config.AddCommand<HelloCommand>("hello")
|
config.AddCommand<HelloCommand>("hello")
|
||||||
.WithAlias("hola")
|
.WithAlias("hola")
|
||||||
.WithDescription("Say hello")
|
.WithDescription("Say hello")
|
||||||
.WithExample(new []{"hello", "Phil"})
|
.WithExample("hello", "Phil")
|
||||||
.WithExample(new []{"hello", "Phil", "--count", "4"});
|
.WithExample("hello", "Phil", "--count", "4");
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ var fruits = AnsiConsole.Prompt(
|
|||||||
"[grey](Press [blue]<space>[/] to toggle a fruit, " +
|
"[grey](Press [blue]<space>[/] to toggle a fruit, " +
|
||||||
"[green]<enter>[/] to accept)[/]")
|
"[green]<enter>[/] to accept)[/]")
|
||||||
.AddChoices(new[] {
|
.AddChoices(new[] {
|
||||||
"Apple", "Apricot", "Avocado",
|
"Apple", "Apricot", "Avocado",
|
||||||
"Banana", "Blackcurrant", "Blueberry",
|
"Banana", "Blackcurrant", "Blueberry",
|
||||||
"Cherry", "Cloudberry", "Cocunut",
|
"Cherry", "Cloudberry", "Coconut",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Write the selected fruits to the terminal
|
// Write the selected fruits to the terminal
|
||||||
foreach (string fruit in fruits)
|
foreach (string fruit in fruits)
|
||||||
{
|
{
|
||||||
AnsiConsole.WriteLine(fruit);
|
AnsiConsole.WriteLine(fruit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ Spectre.Console can render [FIGlet](http://www.figlet.org/) text by using the `F
|
|||||||
```csharp
|
```csharp
|
||||||
AnsiConsole.Write(
|
AnsiConsole.Write(
|
||||||
new FigletText("Hello")
|
new FigletText("Hello")
|
||||||
.LeftAligned()
|
.LeftJustified()
|
||||||
.Color(Color.Red));
|
.Color(Color.Red));
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -26,6 +26,6 @@ var font = FigletFont.Load("starwars.flf");
|
|||||||
|
|
||||||
AnsiConsole.Write(
|
AnsiConsole.Write(
|
||||||
new FigletText(font, "Hello")
|
new FigletText(font, "Hello")
|
||||||
.LeftAligned()
|
.LeftJustified()
|
||||||
.Color(Color.Red));
|
.Color(Color.Red));
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -45,16 +45,16 @@ grid.AddColumn();
|
|||||||
|
|
||||||
// Add header row
|
// Add header row
|
||||||
grid.AddRow(new Text[]{
|
grid.AddRow(new Text[]{
|
||||||
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftAligned(),
|
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftJustified(),
|
||||||
new Text("Header 2", new Style(Color.Green, Color.Black)).Centered(),
|
new Text("Header 2", new Style(Color.Green, Color.Black)).Centered(),
|
||||||
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightAligned()
|
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightJustified()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add content row
|
// Add content row
|
||||||
grid.AddRow(new Text[]{
|
grid.AddRow(new Text[]{
|
||||||
new Text("Row 1").LeftAligned(),
|
new Text("Row 1").LeftJustified(),
|
||||||
new Text("Row 2").Centered(),
|
new Text("Row 2").Centered(),
|
||||||
new Text("Row 3").RightAligned()
|
new Text("Row 3").RightJustified()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Write centered cell grid contents to Console
|
// Write centered cell grid contents to Console
|
||||||
@@ -73,9 +73,9 @@ grid.AddColumn();
|
|||||||
|
|
||||||
// Add header row
|
// Add header row
|
||||||
grid.AddRow(new Text[]{
|
grid.AddRow(new Text[]{
|
||||||
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftAligned(),
|
new Text("Header 1", new Style(Color.Red, Color.Black)).LeftJustified(),
|
||||||
new Text("Header 2", new Style(Color.Green, Color.Black)).Centered(),
|
new Text("Header 2", new Style(Color.Green, Color.Black)).Centered(),
|
||||||
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightAligned()
|
new Text("Header 3", new Style(Color.Blue, Color.Black)).RightJustified()
|
||||||
});
|
});
|
||||||
|
|
||||||
var embedded = new Grid();
|
var embedded = new Grid();
|
||||||
@@ -88,7 +88,7 @@ embedded.AddRow(new Text("Embedded III"), new Text("Embedded IV"));
|
|||||||
|
|
||||||
// Add content row
|
// Add content row
|
||||||
grid.AddRow(
|
grid.AddRow(
|
||||||
new Text("Row 1").LeftAligned(),
|
new Text("Row 1").LeftJustified(),
|
||||||
new Text("Row 2").Centered(),
|
new Text("Row 2").Centered(),
|
||||||
embedded
|
embedded
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ You can also specify it via an extension method:
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var rule = new Rule("[red]Hello[/]");
|
var rule = new Rule("[red]Hello[/]");
|
||||||
rule.LeftAligned();
|
rule.LeftJustified();
|
||||||
AnsiConsole.Write(rule);
|
AnsiConsole.Write(rule);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ You can also specify styles via extension methods:
|
|||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
var path = new TextPath("C:/This/Path/Is/Too/Long/To/Fit/In/The/Area.txt")
|
var path = new TextPath("C:/This/Path/Is/Too/Long/To/Fit/In/The/Area.txt")
|
||||||
.RightAligned();
|
.RightJustified();
|
||||||
```
|
```
|
||||||
|
|
||||||
## Styling
|
## Styling
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ namespace Prompt
|
|||||||
.AddChoices(favorites));
|
.AddChoices(favorites));
|
||||||
}
|
}
|
||||||
|
|
||||||
AnsiConsole.MarkupLine("Your selected: [yellow]{0}[/]", fruit);
|
AnsiConsole.MarkupLine("You selected: [yellow]{0}[/]", fruit);
|
||||||
return fruit;
|
return fruit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,11 +16,15 @@ public class FavorInstanceAnsiConsoleOverStaticAnalyzer : SpectreAnalyzer
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
||||||
{
|
{
|
||||||
|
var ansiConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||||
|
if (ansiConsoleType == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
compilationStartContext.RegisterOperationAction(
|
compilationStartContext.RegisterOperationAction(
|
||||||
context =>
|
context =>
|
||||||
{
|
{
|
||||||
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
|
||||||
|
|
||||||
// if this operation isn't an invocation against one of the System.Console methods
|
// if this operation isn't an invocation against one of the System.Console methods
|
||||||
// defined in _methods then we can safely stop analyzing and return;
|
// defined in _methods then we can safely stop analyzing and return;
|
||||||
var invocationOperation = (IInvocationOperation)context.Operation;
|
var invocationOperation = (IInvocationOperation)context.Operation;
|
||||||
|
|||||||
@@ -17,6 +17,16 @@ public class NoConcurrentLiveRenderablesAnalyzer : SpectreAnalyzer
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
||||||
{
|
{
|
||||||
|
var liveTypes = Constants.LiveRenderables
|
||||||
|
.Select(i => compilationStartContext.Compilation.GetTypeByMetadataName(i))
|
||||||
|
.Where(i => i != null)
|
||||||
|
.ToImmutableArray();
|
||||||
|
|
||||||
|
if (liveTypes.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
compilationStartContext.RegisterOperationAction(
|
compilationStartContext.RegisterOperationAction(
|
||||||
context =>
|
context =>
|
||||||
{
|
{
|
||||||
@@ -29,27 +39,21 @@ public class NoConcurrentLiveRenderablesAnalyzer : SpectreAnalyzer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var liveTypes = Constants.LiveRenderables
|
|
||||||
.Select(i => context.Compilation.GetTypeByMetadataName(i))
|
|
||||||
.ToImmutableArray();
|
|
||||||
|
|
||||||
if (liveTypes.All(i => !SymbolEqualityComparer.Default.Equals(i, methodSymbol.ContainingType)))
|
if (liveTypes.All(i => !SymbolEqualityComparer.Default.Equals(i, methodSymbol.ContainingType)))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
|
var model = context.Operation.SemanticModel!;
|
||||||
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
|
|
||||||
#pragma warning restore RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
|
|
||||||
var parentInvocations = invocationOperation
|
var parentInvocations = invocationOperation
|
||||||
.Syntax.Ancestors()
|
.Syntax.Ancestors()
|
||||||
.OfType<InvocationExpressionSyntax>()
|
.OfType<InvocationExpressionSyntax>()
|
||||||
.Select(i => model.GetOperation(i))
|
.Select(i => model.GetOperation(i, context.CancellationToken))
|
||||||
.OfType<IInvocationOperation>()
|
.OfType<IInvocationOperation>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (parentInvocations.All(parent =>
|
if (parentInvocations.All(parent =>
|
||||||
parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType)))
|
parent.TargetMethod.Name != StartMethod || !liveTypes.Contains(parent.TargetMethod.ContainingType, SymbolEqualityComparer.Default)))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,14 @@ public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
||||||
{
|
{
|
||||||
|
var ansiConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||||
|
var ansiConsoleExtensionsType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions");
|
||||||
|
|
||||||
|
if (ansiConsoleType is null && ansiConsoleExtensionsType is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
compilationStartContext.RegisterOperationAction(
|
compilationStartContext.RegisterOperationAction(
|
||||||
context =>
|
context =>
|
||||||
{
|
{
|
||||||
@@ -31,22 +39,17 @@ public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ansiConsoleType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
|
||||||
var ansiConsoleExtensionsType = context.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsoleExtensions");
|
|
||||||
|
|
||||||
if (!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleType) &&
|
if (!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleType) &&
|
||||||
!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleExtensionsType))
|
!SymbolEqualityComparer.Default.Equals(methodSymbol.ContainingType, ansiConsoleExtensionsType))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
|
var model = context.Operation.SemanticModel!;
|
||||||
var model = context.Compilation.GetSemanticModel(context.Operation.Syntax.SyntaxTree);
|
|
||||||
#pragma warning restore RS1030 // Do not invoke Compilation.GetSemanticModel() method within a diagnostic analyzer
|
|
||||||
var parentInvocations = invocationOperation
|
var parentInvocations = invocationOperation
|
||||||
.Syntax.Ancestors()
|
.Syntax.Ancestors()
|
||||||
.OfType<InvocationExpressionSyntax>()
|
.OfType<InvocationExpressionSyntax>()
|
||||||
.Select(i => model.GetOperation(i))
|
.Select(i => model.GetOperation(i, context.CancellationToken))
|
||||||
.OfType<IInvocationOperation>()
|
.OfType<IInvocationOperation>()
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@@ -56,7 +59,7 @@ public class NoPromptsDuringLiveRenderablesAnalyzer : SpectreAnalyzer
|
|||||||
|
|
||||||
if (parentInvocations.All(parent =>
|
if (parentInvocations.All(parent =>
|
||||||
parent.TargetMethod.Name != "Start" ||
|
parent.TargetMethod.Name != "Start" ||
|
||||||
!liveTypes.Contains(parent.TargetMethod.ContainingType)))
|
!liveTypes.Contains(parent.TargetMethod.ContainingType, SymbolEqualityComparer.Default)))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,13 @@ public class UseSpectreInsteadOfSystemConsoleAnalyzer : SpectreAnalyzer
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
protected override void AnalyzeCompilation(CompilationStartAnalysisContext compilationStartContext)
|
||||||
{
|
{
|
||||||
|
var systemConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("System.Console");
|
||||||
|
var spectreConsoleType = compilationStartContext.Compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
|
||||||
|
if (systemConsoleType == null || spectreConsoleType == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
compilationStartContext.RegisterOperationAction(
|
compilationStartContext.RegisterOperationAction(
|
||||||
context =>
|
context =>
|
||||||
{
|
{
|
||||||
@@ -31,8 +38,6 @@ public class UseSpectreInsteadOfSystemConsoleAnalyzer : SpectreAnalyzer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var systemConsoleType = context.Compilation.GetTypeByMetadataName("System.Console");
|
|
||||||
|
|
||||||
if (!SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.ContainingType, systemConsoleType))
|
if (!SymbolEqualityComparer.Default.Equals(invocationOperation.TargetMethod.ContainingType, systemConsoleType))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
|
using Microsoft.CodeAnalysis.Editing;
|
||||||
|
using Microsoft.CodeAnalysis.Simplification;
|
||||||
|
|
||||||
namespace Spectre.Console.Analyzer.CodeActions;
|
namespace Spectre.Console.Analyzer.CodeActions;
|
||||||
|
|
||||||
@@ -31,83 +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 originalCaller = ((MemberAccessExpressionSyntax)_originalInvocation.Expression).Name.ToString();
|
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
|
||||||
|
var compilation = editor.SemanticModel.Compilation;
|
||||||
var syntaxTree = await _document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
if (syntaxTree == null)
|
var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation;
|
||||||
{
|
if (operation == null)
|
||||||
return _document;
|
{
|
||||||
}
|
return _document;
|
||||||
|
}
|
||||||
var root = (CompilationUnitSyntax)await syntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
|
// If there is an IAnsiConsole passed into the method then we'll use it.
|
||||||
// If there is an ansiConsole 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 ansiConsoleParameterDeclaration = GetAnsiConsoleParameterDeclaration();
|
var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole");
|
||||||
var ansiConsoleFieldIdentifier = GetAnsiConsoleFieldDeclaration();
|
|
||||||
var ansiConsoleIdentifier = ansiConsoleParameterDeclaration ??
|
ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol;
|
||||||
ansiConsoleFieldIdentifier ??
|
if (iansiConsoleSymbol != null)
|
||||||
Constants.StaticInstance;
|
{
|
||||||
|
var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition);
|
||||||
// Replace the System.Console call with a call to the identifier above.
|
|
||||||
var newRoot = root.ReplaceNode(
|
foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start))
|
||||||
_originalInvocation,
|
{
|
||||||
GetImportedSpectreCall(originalCaller, ansiConsoleIdentifier));
|
// LookupSymbols check the accessibility of the symbol, but it can
|
||||||
|
// suggest instance members when the current context is static.
|
||||||
// If we are calling the static instance and Spectre isn't imported yet we should do so.
|
var symbolType = symbol switch
|
||||||
if (ansiConsoleIdentifier == Constants.StaticInstance && root.Usings.ToList().All(i => i.Name.ToString() != Constants.SpectreConsole))
|
{
|
||||||
{
|
IParameterSymbol parameter => parameter.Type,
|
||||||
newRoot = newRoot.AddUsings(Syntax.SpectreUsing);
|
IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type,
|
||||||
}
|
IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type,
|
||||||
|
ILocalSymbol local => local.Type,
|
||||||
return _document.WithSyntaxRoot(newRoot);
|
_ => null,
|
||||||
}
|
};
|
||||||
|
|
||||||
private string? GetAnsiConsoleParameterDeclaration()
|
// 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
|
||||||
return _originalInvocation
|
// should not be accessible in a static local function.
|
||||||
.Ancestors().OfType<MethodDeclarationSyntax>()
|
//
|
||||||
.First()
|
// void Sample()
|
||||||
.ParameterList.Parameters
|
// {
|
||||||
.FirstOrDefault(i => i.Type?.NormalizeWhitespace()?.ToString() == "IAnsiConsole")
|
// int local = 0;
|
||||||
?.Identifier.Text;
|
// static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
private string? GetAnsiConsoleFieldDeclaration()
|
// 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)
|
||||||
// let's look to see if our call is in a static method.
|
{
|
||||||
// if so we'll only want to look for static IAnsiConsoles
|
var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start;
|
||||||
// and vice-versa if we aren't.
|
|
||||||
var isStatic = _originalInvocation
|
// The local is not part of the source tree
|
||||||
.Ancestors()
|
if (localPosition == null)
|
||||||
.OfType<MethodDeclarationSyntax>()
|
{
|
||||||
.First()
|
break;
|
||||||
.Modifiers.Any(i => i.IsKind(SyntaxKind.StaticKeyword));
|
}
|
||||||
|
|
||||||
return _originalInvocation
|
// The local is declared after the current expression
|
||||||
.Ancestors().OfType<ClassDeclarationSyntax>()
|
if (localPosition > _originalInvocation.Span.Start)
|
||||||
.First()
|
{
|
||||||
.Members
|
break;
|
||||||
.OfType<FieldDeclarationSyntax>()
|
}
|
||||||
.FirstOrDefault(i =>
|
|
||||||
i.Declaration.Type.NormalizeWhitespace().ToString() == "IAnsiConsole" &&
|
// The local is declared outside the static local function
|
||||||
(!isStatic ^ i.Modifiers.Any(modifier => modifier.IsKind(SyntaxKind.StaticKeyword))))
|
if (isInStaticContext && localPosition < parentStaticMemberStartPosition)
|
||||||
?.Declaration.Variables.First().Identifier.Text;
|
{
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
private ExpressionSyntax GetImportedSpectreCall(string originalCaller, string ansiConsoleIdentifier)
|
}
|
||||||
{
|
|
||||||
return ExpressionStatement(
|
if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol))
|
||||||
InvocationExpression(
|
{
|
||||||
MemberAccessExpression(
|
accessibleConsoleSymbol = symbol;
|
||||||
SyntaxKind.SimpleMemberAccessExpression,
|
break;
|
||||||
IdentifierName(ansiConsoleIdentifier),
|
}
|
||||||
IdentifierName(originalCaller)))
|
}
|
||||||
.WithArgumentList(_originalInvocation.ArgumentList)
|
}
|
||||||
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia())
|
|
||||||
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia()))
|
if (accessibleConsoleSymbol == null)
|
||||||
.Expression;
|
{
|
||||||
|
return _document;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace the original invocation
|
||||||
|
var generator = editor.Generator;
|
||||||
|
var consoleExpression = accessibleConsoleSymbol switch
|
||||||
|
{
|
||||||
|
ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
|
||||||
|
_ => generator.IdentifierName(accessibleConsoleSymbol.Name),
|
||||||
|
};
|
||||||
|
|
||||||
|
var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments)
|
||||||
|
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia())
|
||||||
|
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia());
|
||||||
|
|
||||||
|
editor.ReplaceNode(_originalInvocation, newExpression);
|
||||||
|
|
||||||
|
return editor.GetChangedDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol)
|
||||||
|
{
|
||||||
|
if (symbol == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var iface in symbol.AllInterfaces)
|
||||||
|
{
|
||||||
|
if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition)
|
||||||
|
{
|
||||||
|
// Local functions can be nested, and an instance local function can be declared
|
||||||
|
// in a static local function. So, you need to continue to check ancestors when a
|
||||||
|
// local function is not static.
|
||||||
|
foreach (var member in operation.Syntax.Ancestors())
|
||||||
|
{
|
||||||
|
if (member is LocalFunctionStatementSyntax localFunction)
|
||||||
|
{
|
||||||
|
var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken);
|
||||||
|
if (symbol != null && symbol.IsStatic)
|
||||||
|
{
|
||||||
|
parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (member is LambdaExpressionSyntax lambdaExpression)
|
||||||
|
{
|
||||||
|
var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol;
|
||||||
|
if (symbol != null && symbol.IsStatic)
|
||||||
|
{
|
||||||
|
parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (member is AnonymousMethodExpressionSyntax anonymousMethod)
|
||||||
|
{
|
||||||
|
var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol;
|
||||||
|
if (symbol != null && symbol.IsStatic)
|
||||||
|
{
|
||||||
|
parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (member is MethodDeclarationSyntax methodDeclaration)
|
||||||
|
{
|
||||||
|
parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start;
|
||||||
|
|
||||||
|
var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken);
|
||||||
|
return symbol != null && symbol.IsStatic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parentStaticMemberStartPosition = -1;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -20,7 +20,7 @@ public class StaticAnsiConsoleToInstanceFix : CodeFixProvider
|
|||||||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
|
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
|
||||||
if (root != null)
|
if (root != null)
|
||||||
{
|
{
|
||||||
var methodDeclaration = root.FindNode(context.Span).FirstAncestorOrSelf<InvocationExpressionSyntax>();
|
var methodDeclaration = root.FindNode(context.Span, getInnermostNodeForTie: true).FirstAncestorOrSelf<InvocationExpressionSyntax>();
|
||||||
if (methodDeclaration != null)
|
if (methodDeclaration != null)
|
||||||
{
|
{
|
||||||
context.RegisterCodeFix(
|
context.RegisterCodeFix(
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Spectre.Console.Cli.Internal.Configuration;
|
||||||
|
|
||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -39,10 +41,11 @@ public sealed class CommandApp : ICommandApp
|
|||||||
/// Sets the default command.
|
/// Sets the default command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TCommand">The command type.</typeparam>
|
/// <typeparam name="TCommand">The command type.</typeparam>
|
||||||
public void SetDefaultCommand<TCommand>()
|
/// <returns>A <see cref="DefaultCommandConfigurator"/> that can be used to configure the default command.</returns>
|
||||||
|
public DefaultCommandConfigurator SetDefaultCommand<TCommand>()
|
||||||
where TCommand : class, ICommand
|
where TCommand : class, ICommand
|
||||||
{
|
{
|
||||||
GetConfigurator().SetDefaultCommand<TCommand>();
|
return new DefaultCommandConfigurator(GetConfigurator().SetDefaultCommand<TCommand>());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using Spectre.Console.Cli.Internal.Configuration;
|
||||||
|
|
||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -8,6 +10,7 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
|
|||||||
where TDefaultCommand : class, ICommand
|
where TDefaultCommand : class, ICommand
|
||||||
{
|
{
|
||||||
private readonly CommandApp _app;
|
private readonly CommandApp _app;
|
||||||
|
private readonly DefaultCommandConfigurator _defaultCommandConfigurator;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="CommandApp{TDefaultCommand}"/> class.
|
/// Initializes a new instance of the <see cref="CommandApp{TDefaultCommand}"/> class.
|
||||||
@@ -16,7 +19,7 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
|
|||||||
public CommandApp(ITypeRegistrar? registrar = null)
|
public CommandApp(ITypeRegistrar? registrar = null)
|
||||||
{
|
{
|
||||||
_app = new CommandApp(registrar);
|
_app = new CommandApp(registrar);
|
||||||
_app.GetConfigurator().SetDefaultCommand<TDefaultCommand>();
|
_defaultCommandConfigurator = _app.SetDefaultCommand<TDefaultCommand>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -46,5 +49,32 @@ 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()
|
||||||
|
{
|
||||||
|
return _app.GetConfigurator();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the description of the default command.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="description">The default command description.</param>
|
||||||
|
/// <returns>The same <see cref="CommandApp{TDefaultCommand}"/> instance so that multiple calls can be chained.</returns>
|
||||||
|
public CommandApp<TDefaultCommand> WithDescription(string description)
|
||||||
|
{
|
||||||
|
_defaultCommandConfigurator.WithDescription(description);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets data that will be passed to the command via the <see cref="CommandContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data to pass to the default command.</param>
|
||||||
|
/// <returns>The same <see cref="CommandApp{TDefaultCommand}"/> instance so that multiple calls can be chained.</returns>
|
||||||
|
public CommandApp<TDefaultCommand> WithData(object data)
|
||||||
|
{
|
||||||
|
_defaultCommandConfigurator.WithData(data);
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,6 +42,13 @@ public class CommandRuntimeException : CommandAppException
|
|||||||
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
|
return new CommandRuntimeException($"Could not find converter for type '{parameter.ParameterType.FullName}'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static CommandRuntimeException ConversionFailed(MappedCommandParameter parameter, TypeConverter typeConverter, Exception exception)
|
||||||
|
{
|
||||||
|
var standardValues = typeConverter.GetStandardValuesSupported() ? typeConverter.GetStandardValues() : null;
|
||||||
|
var validValues = standardValues == null ? string.Empty : $" Valid values are '{string.Join("', '", standardValues.Cast<object>().Select(Convert.ToString))}'";
|
||||||
|
return new CommandRuntimeException($"Failed to convert '{parameter.Value}' to {parameter.Parameter.ParameterType.Name}.{validValues}", exception);
|
||||||
|
}
|
||||||
|
|
||||||
internal static CommandRuntimeException ValidationFailed(ValidationResult result)
|
internal static CommandRuntimeException ValidationFailed(ValidationResult result)
|
||||||
{
|
{
|
||||||
return new CommandRuntimeException(result.Message ?? "Unknown validation error.");
|
return new CommandRuntimeException(result.Message ?? "Unknown validation error.");
|
||||||
|
|||||||
@@ -88,12 +88,12 @@ public static class ConfiguratorExtensions
|
|||||||
|
|
||||||
configurator.Settings.StrictParsing = true;
|
configurator.Settings.StrictParsing = true;
|
||||||
return configurator;
|
return configurator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tells the help writer whether or not to trim trailing period.
|
/// Tells the help writer whether or not to trim trailing period.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configurator">The configurator.</param>
|
/// <param name="configurator">The configurator.</param>
|
||||||
/// <param name="trimTrailingPeriods">True to trim trailing period (default), false to not.</param>
|
/// <param name="trimTrailingPeriods">True to trim trailing period (default), false to not.</param>
|
||||||
/// <returns>A configurator that can be used to configure the application further.</returns>
|
/// <returns>A configurator that can be used to configure the application further.</returns>
|
||||||
public static IConfigurator TrimTrailingPeriods(this IConfigurator configurator, bool trimTrailingPeriods)
|
public static IConfigurator TrimTrailingPeriods(this IConfigurator configurator, bool trimTrailingPeriods)
|
||||||
@@ -181,7 +181,8 @@ public static class ConfiguratorExtensions
|
|||||||
/// <param name="configurator">The configurator.</param>
|
/// <param name="configurator">The configurator.</param>
|
||||||
/// <param name="name">The name of the command branch.</param>
|
/// <param name="name">The name of the command branch.</param>
|
||||||
/// <param name="action">The command branch configuration.</param>
|
/// <param name="action">The command branch configuration.</param>
|
||||||
public static void AddBranch(
|
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||||
|
public static IBranchConfigurator AddBranch(
|
||||||
this IConfigurator configurator,
|
this IConfigurator configurator,
|
||||||
string name,
|
string name,
|
||||||
Action<IConfigurator<CommandSettings>> action)
|
Action<IConfigurator<CommandSettings>> action)
|
||||||
@@ -191,7 +192,7 @@ public static class ConfiguratorExtensions
|
|||||||
throw new ArgumentNullException(nameof(configurator));
|
throw new ArgumentNullException(nameof(configurator));
|
||||||
}
|
}
|
||||||
|
|
||||||
configurator.AddBranch(name, action);
|
return configurator.AddBranch(name, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -201,7 +202,8 @@ public static class ConfiguratorExtensions
|
|||||||
/// <param name="configurator">The configurator.</param>
|
/// <param name="configurator">The configurator.</param>
|
||||||
/// <param name="name">The name of the command branch.</param>
|
/// <param name="name">The name of the command branch.</param>
|
||||||
/// <param name="action">The command branch configuration.</param>
|
/// <param name="action">The command branch configuration.</param>
|
||||||
public static void AddBranch<TSettings>(
|
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||||
|
public static IBranchConfigurator AddBranch<TSettings>(
|
||||||
this IConfigurator<TSettings> configurator,
|
this IConfigurator<TSettings> configurator,
|
||||||
string name,
|
string name,
|
||||||
Action<IConfigurator<TSettings>> action)
|
Action<IConfigurator<TSettings>> action)
|
||||||
@@ -212,7 +214,7 @@ public static class ConfiguratorExtensions
|
|||||||
throw new ArgumentNullException(nameof(configurator));
|
throw new ArgumentNullException(nameof(configurator));
|
||||||
}
|
}
|
||||||
|
|
||||||
configurator.AddBranch(name, action);
|
return configurator.AddBranch(name, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
14
src/Spectre.Console.Cli/IBranchConfigurator.cs
Normal file
14
src/Spectre.Console.Cli/IBranchConfigurator.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a branch configurator.
|
||||||
|
/// </summary>
|
||||||
|
public interface IBranchConfigurator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds an alias (an alternative name) to the branch being configured.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The alias to add to the branch being configured.</param>
|
||||||
|
/// <returns>The same <see cref="IBranchConfigurator"/> instance so that multiple calls can be chained.</returns>
|
||||||
|
IBranchConfigurator WithAlias(string name);
|
||||||
|
}
|
||||||
@@ -49,7 +49,15 @@ 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>
|
||||||
|
/// Gets or sets a value indicating whether or not flags found on the commnd line
|
||||||
|
/// that would normally result in a <see cref="CommandParseException"/> being thrown
|
||||||
|
/// during parsing with the message "Flags cannot be assigned a value."
|
||||||
|
/// should instead be added to the remaining arguments collection.
|
||||||
|
/// </summary>
|
||||||
|
bool ConvertFlagsToRemainingArguments { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not exceptions should be propagated.
|
/// Gets or sets a value indicating whether or not exceptions should be propagated.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public interface ICommandConfigurator
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="args">The example arguments.</param>
|
/// <param name="args">The example arguments.</param>
|
||||||
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
|
/// <returns>The same <see cref="ICommandConfigurator"/> instance so that multiple calls can be chained.</returns>
|
||||||
ICommandConfigurator WithExample(string[] args);
|
ICommandConfigurator WithExample(params string[] args);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds an alias (an alternative name) to the command being configured.
|
/// Adds an alias (an alternative name) to the command being configured.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ public interface IConfigurator
|
|||||||
/// Adds an example of how to use the application.
|
/// Adds an example of how to use the application.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="args">The example arguments.</param>
|
/// <param name="args">The example arguments.</param>
|
||||||
void AddExample(string[] args);
|
void AddExample(params string[] args);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a command.
|
/// Adds a command.
|
||||||
@@ -41,6 +41,7 @@ public interface IConfigurator
|
|||||||
/// <typeparam name="TSettings">The command setting type.</typeparam>
|
/// <typeparam name="TSettings">The command setting type.</typeparam>
|
||||||
/// <param name="name">The name of the command branch.</param>
|
/// <param name="name">The name of the command branch.</param>
|
||||||
/// <param name="action">The command branch configurator.</param>
|
/// <param name="action">The command branch configurator.</param>
|
||||||
void AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
|
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||||
|
IBranchConfigurator AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
|
||||||
where TSettings : CommandSettings;
|
where TSettings : CommandSettings;
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,18 @@ public interface IConfigurator<in TSettings>
|
|||||||
/// <param name="args">The example arguments.</param>
|
/// <param name="args">The example arguments.</param>
|
||||||
void AddExample(string[] args);
|
void AddExample(string[] args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a default command.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is the command that will run if the user doesn't specify one on the command line.
|
||||||
|
/// It must be able to execute successfully by itself ie. without requiring any command line
|
||||||
|
/// arguments, flags or option values.
|
||||||
|
/// </remarks>
|
||||||
|
/// <typeparam name="TDefaultCommand">The default command type.</typeparam>
|
||||||
|
void SetDefaultCommand<TDefaultCommand>()
|
||||||
|
where TDefaultCommand : class, ICommandLimiter<TSettings>;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Marks the branch as hidden.
|
/// Marks the branch as hidden.
|
||||||
/// Hidden branches do not show up in help documentation or
|
/// Hidden branches do not show up in help documentation or
|
||||||
@@ -51,6 +63,7 @@ public interface IConfigurator<in TSettings>
|
|||||||
/// <typeparam name="TDerivedSettings">The derived command setting type.</typeparam>
|
/// <typeparam name="TDerivedSettings">The derived command setting type.</typeparam>
|
||||||
/// <param name="name">The name of the command branch.</param>
|
/// <param name="name">The name of the command branch.</param>
|
||||||
/// <param name="action">The command branch configuration.</param>
|
/// <param name="action">The command branch configuration.</param>
|
||||||
void AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
|
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||||
|
IBranchConfigurator AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
|
||||||
where TDerivedSettings : TSettings;
|
where TDerivedSettings : TSettings;
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,11 @@ internal sealed class CommandValueBinder
|
|||||||
|
|
||||||
private object GetArray(CommandParameter parameter, object? value)
|
private object GetArray(CommandParameter parameter, object? value)
|
||||||
{
|
{
|
||||||
|
if (value is Array)
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
// Add a new item to the array
|
// Add a new item to the array
|
||||||
var array = (Array?)_lookup.GetValue(parameter);
|
var array = (Array?)_lookup.GetValue(parameter);
|
||||||
Array newArray;
|
Array newArray;
|
||||||
|
|||||||
@@ -78,14 +78,32 @@ internal static class CommandValueResolver
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var converter = GetConverter(lookup, binder, resolver, mapped.Parameter);
|
var (converter, stringConstructor) = GetConverter(lookup, binder, resolver, mapped.Parameter);
|
||||||
if (converter == null)
|
if (converter == null)
|
||||||
{
|
{
|
||||||
throw CommandRuntimeException.NoConverterFound(mapped.Parameter);
|
throw CommandRuntimeException.NoConverterFound(mapped.Parameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
object? value;
|
||||||
|
var mappedValue = mapped.Value ?? string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
value = converter.ConvertFromInvariantString(mappedValue);
|
||||||
|
}
|
||||||
|
catch (NotSupportedException) when (stringConstructor != null)
|
||||||
|
{
|
||||||
|
value = stringConstructor.Invoke(new object[] { mappedValue });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception exception) when (exception is not CommandRuntimeException)
|
||||||
|
{
|
||||||
|
throw CommandRuntimeException.ConversionFailed(mapped, converter, exception);
|
||||||
|
}
|
||||||
|
|
||||||
// Assign the value to the parameter.
|
// Assign the value to the parameter.
|
||||||
binder.Bind(mapped.Parameter, resolver, converter.ConvertFromInvariantString(mapped.Value ?? string.Empty));
|
binder.Bind(mapped.Parameter, resolver, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,19 +130,45 @@ internal static class CommandValueResolver
|
|||||||
{
|
{
|
||||||
if (result != null && result.GetType() != parameter.ParameterType)
|
if (result != null && result.GetType() != parameter.ParameterType)
|
||||||
{
|
{
|
||||||
var converter = GetConverter(lookup, binder, resolver, parameter);
|
var (converter, _) = GetConverter(lookup, binder, resolver, parameter);
|
||||||
if (converter != null)
|
if (converter != null)
|
||||||
{
|
{
|
||||||
result = converter.ConvertFrom(result);
|
result = result is Array array ? ConvertArray(array, converter) : converter.ConvertFrom(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "It's OK")]
|
private static Array ConvertArray(Array sourceArray, TypeConverter converter)
|
||||||
private static TypeConverter? GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter)
|
|
||||||
{
|
{
|
||||||
|
Array? targetArray = null;
|
||||||
|
for (var i = 0; i < sourceArray.Length; i++)
|
||||||
|
{
|
||||||
|
var item = sourceArray.GetValue(i);
|
||||||
|
if (item != null)
|
||||||
|
{
|
||||||
|
var converted = converter.ConvertFrom(item);
|
||||||
|
if (converted != null)
|
||||||
|
{
|
||||||
|
targetArray ??= Array.CreateInstance(converted.GetType(), sourceArray.Length);
|
||||||
|
targetArray.SetValue(converted, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targetArray ?? sourceArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Style", "IDE0019:Use pattern matching", Justification = "It's OK")]
|
||||||
|
private static (TypeConverter? Converter, ConstructorInfo? StringConstructor) GetConverter(CommandValueLookup lookup, CommandValueBinder binder, ITypeResolver resolver, CommandParameter parameter)
|
||||||
|
{
|
||||||
|
static ConstructorInfo? GetStringConstructor(Type type)
|
||||||
|
{
|
||||||
|
var constructor = type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(string) }, null);
|
||||||
|
return constructor?.GetParameters()[0].ParameterType == typeof(string) ? constructor : null;
|
||||||
|
}
|
||||||
|
|
||||||
if (parameter.Converter == null)
|
if (parameter.Converter == null)
|
||||||
{
|
{
|
||||||
if (parameter.ParameterType.IsArray)
|
if (parameter.ParameterType.IsArray)
|
||||||
@@ -136,12 +180,12 @@ internal static class CommandValueResolver
|
|||||||
throw new InvalidOperationException("Could not get element type");
|
throw new InvalidOperationException("Could not get element type");
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypeDescriptor.GetConverter(elementType);
|
return (TypeDescriptor.GetConverter(elementType), GetStringConstructor(elementType));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parameter.IsFlagValue())
|
if (parameter.IsFlagValue())
|
||||||
{
|
{
|
||||||
// Is the optional value instanciated?
|
// Is the optional value instantiated?
|
||||||
var value = lookup.GetValue(parameter) as IFlagValue;
|
var value = lookup.GetValue(parameter) as IFlagValue;
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
@@ -151,18 +195,18 @@ internal static class CommandValueResolver
|
|||||||
value = lookup.GetValue(parameter) as IFlagValue;
|
value = lookup.GetValue(parameter) as IFlagValue;
|
||||||
if (value == null)
|
if (value == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Could not intialize optional value.");
|
throw new InvalidOperationException("Could not initialize optional value.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return a converter for the flag element type.
|
// Return a converter for the flag element type.
|
||||||
return TypeDescriptor.GetConverter(value.Type);
|
return (TypeDescriptor.GetConverter(value.Type), GetStringConstructor(value.Type));
|
||||||
}
|
}
|
||||||
|
|
||||||
return TypeDescriptor.GetConverter(parameter.ParameterType);
|
return (TypeDescriptor.GetConverter(parameter.ParameterType), GetStringConstructor(parameter.ParameterType));
|
||||||
}
|
}
|
||||||
|
|
||||||
var type = Type.GetType(parameter.Converter.ConverterTypeName);
|
var type = Type.GetType(parameter.Converter.ConverterTypeName);
|
||||||
return resolver.Resolve(type) as TypeConverter;
|
return (resolver.Resolve(type) as TypeConverter, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,12 +45,10 @@ internal sealed class CommandExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse and map the model against the arguments.
|
// Parse and map the model against the arguments.
|
||||||
var parser = new CommandTreeParser(model, configuration.Settings);
|
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
|
||||||
var parsedResult = parser.Parse(args);
|
|
||||||
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
|
||||||
|
|
||||||
// Currently the root?
|
// Currently the root?
|
||||||
if (parsedResult.Tree == null)
|
if (parsedResult?.Tree == null)
|
||||||
{
|
{
|
||||||
// Display help.
|
// Display help.
|
||||||
configuration.Settings.Console.SafeRender(HelpWriter.Write(model, configuration.Settings.ShowOptionDefaultValues));
|
configuration.Settings.Console.SafeRender(HelpWriter.Write(model, configuration.Settings.ShowOptionDefaultValues));
|
||||||
@@ -75,6 +73,7 @@ internal sealed class CommandExecutor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Register the arguments with the container.
|
// Register the arguments with the container.
|
||||||
|
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
|
||||||
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
|
||||||
|
|
||||||
// Create the resolver and the context.
|
// Create the resolver and the context.
|
||||||
@@ -86,6 +85,34 @@ internal sealed class CommandExecutor
|
|||||||
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CommandTreeParserResult? ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
|
||||||
|
{
|
||||||
|
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
|
||||||
|
|
||||||
|
var parserContext = new CommandTreeParserContext(args, settings.ParsingMode);
|
||||||
|
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||||
|
var parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||||
|
|
||||||
|
var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
|
||||||
|
var lastParsedCommand = lastParsedLeaf?.Command;
|
||||||
|
if (lastParsedLeaf != null && lastParsedCommand != null &&
|
||||||
|
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
|
||||||
|
lastParsedCommand.DefaultCommand != null)
|
||||||
|
{
|
||||||
|
// Insert this branch's default command into the command line
|
||||||
|
// arguments and try again to see if it will parse.
|
||||||
|
var argsWithDefaultCommand = new List<string>(args);
|
||||||
|
|
||||||
|
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
|
||||||
|
|
||||||
|
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
|
||||||
|
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
|
||||||
|
parsedResult = parser.Parse(parserContext, tokenizerResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedResult;
|
||||||
|
}
|
||||||
|
|
||||||
private static string ResolveApplicationVersion(IConfiguration configuration)
|
private static string ResolveApplicationVersion(IConfiguration configuration)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
|
internal sealed class BranchConfigurator : IBranchConfigurator
|
||||||
|
{
|
||||||
|
public ConfiguredCommand Command { get; }
|
||||||
|
|
||||||
|
public BranchConfigurator(ConfiguredCommand command)
|
||||||
|
{
|
||||||
|
Command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IBranchConfigurator WithAlias(string alias)
|
||||||
|
{
|
||||||
|
Command.Aliases.Add(alias);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,8 @@ internal sealed class CommandAppSettings : ICommandAppSettings
|
|||||||
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; } = true;
|
||||||
public bool StrictParsing { get; set; }
|
public bool StrictParsing { get; set; }
|
||||||
|
public bool ConvertFlagsToRemainingArguments { get; set; } = false;
|
||||||
|
|
||||||
public ParsingMode ParsingMode =>
|
public ParsingMode ParsingMode =>
|
||||||
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
|
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ internal sealed class CommandConfigurator : ICommandConfigurator
|
|||||||
Command = command;
|
Command = command;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommandConfigurator WithExample(string[] args)
|
public ICommandConfigurator WithExample(params string[] args)
|
||||||
{
|
{
|
||||||
Command.Examples.Add(args);
|
Command.Examples.Add(args);
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -20,22 +20,23 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
|
|||||||
Examples = new List<string[]>();
|
Examples = new List<string[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddExample(string[] args)
|
public void AddExample(params string[] args)
|
||||||
{
|
{
|
||||||
Examples.Add(args);
|
Examples.Add(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDefaultCommand<TDefaultCommand>()
|
public ConfiguredCommand SetDefaultCommand<TDefaultCommand>()
|
||||||
where TDefaultCommand : class, ICommand
|
where TDefaultCommand : class, ICommand
|
||||||
{
|
{
|
||||||
DefaultCommand = ConfiguredCommand.FromType<TDefaultCommand>(
|
DefaultCommand = ConfiguredCommand.FromType<TDefaultCommand>(
|
||||||
CliConstants.DefaultCommandName, isDefaultCommand: true);
|
CliConstants.DefaultCommandName, isDefaultCommand: true);
|
||||||
|
return DefaultCommand;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICommandConfigurator AddCommand<TCommand>(string name)
|
public ICommandConfigurator AddCommand<TCommand>(string name)
|
||||||
where TCommand : class, ICommand
|
where TCommand : class, ICommand
|
||||||
{
|
{
|
||||||
var command = Commands.AddAndReturn(ConfiguredCommand.FromType<TCommand>(name, false));
|
var command = Commands.AddAndReturn(ConfiguredCommand.FromType<TCommand>(name, isDefaultCommand: false));
|
||||||
return new CommandConfigurator(command);
|
return new CommandConfigurator(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,12 +48,13 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
|
|||||||
return new CommandConfigurator(command);
|
return new CommandConfigurator(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
|
public IBranchConfigurator AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
|
||||||
where TSettings : CommandSettings
|
where TSettings : CommandSettings
|
||||||
{
|
{
|
||||||
var command = ConfiguredCommand.FromBranch<TSettings>(name);
|
var command = ConfiguredCommand.FromBranch<TSettings>(name);
|
||||||
action(new Configurator<TSettings>(command, _registrar));
|
action(new Configurator<TSettings>(command, _registrar));
|
||||||
Commands.Add(command);
|
var added = Commands.AddAndReturn(command);
|
||||||
|
return new BranchConfigurator(added);
|
||||||
}
|
}
|
||||||
|
|
||||||
ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command)
|
ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command)
|
||||||
@@ -73,7 +75,7 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action)
|
IBranchConfigurator IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action)
|
||||||
{
|
{
|
||||||
var command = ConfiguredCommand.FromBranch(settings, name);
|
var command = ConfiguredCommand.FromBranch(settings, name);
|
||||||
|
|
||||||
@@ -85,6 +87,7 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
|
|||||||
}
|
}
|
||||||
|
|
||||||
action(configurator);
|
action(configurator);
|
||||||
Commands.Add(command);
|
var added = Commands.AddAndReturn(command);
|
||||||
|
return new BranchConfigurator(added);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,90 +1,100 @@
|
|||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConfigurator<TSettings>
|
internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConfigurator<TSettings>
|
||||||
where TSettings : CommandSettings
|
where TSettings : CommandSettings
|
||||||
{
|
{
|
||||||
private readonly ConfiguredCommand _command;
|
private readonly ConfiguredCommand _command;
|
||||||
private readonly ITypeRegistrar? _registrar;
|
private readonly ITypeRegistrar? _registrar;
|
||||||
|
|
||||||
public Configurator(ConfiguredCommand command, ITypeRegistrar? registrar)
|
public Configurator(ConfiguredCommand command, ITypeRegistrar? registrar)
|
||||||
{
|
{
|
||||||
_command = command;
|
_command = command;
|
||||||
_registrar = registrar;
|
_registrar = registrar;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetDescription(string description)
|
public void SetDescription(string description)
|
||||||
{
|
{
|
||||||
_command.Description = description;
|
_command.Description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddExample(string[] args)
|
public void AddExample(string[] args)
|
||||||
{
|
{
|
||||||
_command.Examples.Add(args);
|
_command.Examples.Add(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HideBranch()
|
public void SetDefaultCommand<TDefaultCommand>()
|
||||||
{
|
where TDefaultCommand : class, ICommandLimiter<TSettings>
|
||||||
_command.IsHidden = true;
|
{
|
||||||
}
|
var defaultCommand = ConfiguredCommand.FromType<TDefaultCommand>(
|
||||||
|
CliConstants.DefaultCommandName, isDefaultCommand: true);
|
||||||
public ICommandConfigurator AddCommand<TCommand>(string name)
|
|
||||||
where TCommand : class, ICommandLimiter<TSettings>
|
_command.Children.Add(defaultCommand);
|
||||||
{
|
}
|
||||||
var command = ConfiguredCommand.FromType<TCommand>(name);
|
|
||||||
var configurator = new CommandConfigurator(command);
|
public void HideBranch()
|
||||||
|
{
|
||||||
_command.Children.Add(command);
|
_command.IsHidden = true;
|
||||||
return configurator;
|
}
|
||||||
}
|
|
||||||
|
public ICommandConfigurator AddCommand<TCommand>(string name)
|
||||||
public ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func)
|
where TCommand : class, ICommandLimiter<TSettings>
|
||||||
where TDerivedSettings : TSettings
|
{
|
||||||
{
|
var command = ConfiguredCommand.FromType<TCommand>(name, isDefaultCommand: false);
|
||||||
var command = ConfiguredCommand.FromDelegate<TDerivedSettings>(
|
var configurator = new CommandConfigurator(command);
|
||||||
name, (context, settings) => func(context, (TDerivedSettings)settings));
|
|
||||||
|
_command.Children.Add(command);
|
||||||
_command.Children.Add(command);
|
return configurator;
|
||||||
return new CommandConfigurator(command);
|
}
|
||||||
}
|
|
||||||
|
public ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, int> func)
|
||||||
public void AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
|
where TDerivedSettings : TSettings
|
||||||
where TDerivedSettings : TSettings
|
{
|
||||||
{
|
var command = ConfiguredCommand.FromDelegate<TDerivedSettings>(
|
||||||
var command = ConfiguredCommand.FromBranch<TDerivedSettings>(name);
|
name, (context, settings) => func(context, (TDerivedSettings)settings));
|
||||||
action(new Configurator<TDerivedSettings>(command, _registrar));
|
|
||||||
_command.Children.Add(command);
|
_command.Children.Add(command);
|
||||||
}
|
return new CommandConfigurator(command);
|
||||||
|
}
|
||||||
ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command)
|
|
||||||
{
|
public IBranchConfigurator AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
|
||||||
var method = GetType().GetMethod("AddCommand");
|
where TDerivedSettings : TSettings
|
||||||
if (method == null)
|
{
|
||||||
{
|
var command = ConfiguredCommand.FromBranch<TDerivedSettings>(name);
|
||||||
throw new CommandConfigurationException("Could not find AddCommand by reflection.");
|
action(new Configurator<TDerivedSettings>(command, _registrar));
|
||||||
}
|
var added = _command.Children.AddAndReturn(command);
|
||||||
|
return new BranchConfigurator(added);
|
||||||
method = method.MakeGenericMethod(command);
|
}
|
||||||
|
|
||||||
if (!(method.Invoke(this, new object[] { name }) is ICommandConfigurator result))
|
ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command)
|
||||||
{
|
{
|
||||||
throw new CommandConfigurationException("Invoking AddCommand returned null.");
|
var method = GetType().GetMethod("AddCommand");
|
||||||
}
|
if (method == null)
|
||||||
|
{
|
||||||
return result;
|
throw new CommandConfigurationException("Could not find AddCommand by reflection.");
|
||||||
}
|
}
|
||||||
|
|
||||||
void IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action)
|
method = method.MakeGenericMethod(command);
|
||||||
{
|
|
||||||
var command = ConfiguredCommand.FromBranch(settings, name);
|
if (!(method.Invoke(this, new object[] { name }) is ICommandConfigurator result))
|
||||||
|
{
|
||||||
// Create the configurator.
|
throw new CommandConfigurationException("Invoking AddCommand returned null.");
|
||||||
var configuratorType = typeof(Configurator<>).MakeGenericType(settings);
|
}
|
||||||
if (!(Activator.CreateInstance(configuratorType, new object?[] { command, _registrar }) is IUnsafeBranchConfigurator configurator))
|
|
||||||
{
|
return result;
|
||||||
throw new CommandConfigurationException("Could not create configurator by reflection.");
|
}
|
||||||
}
|
|
||||||
|
IBranchConfigurator IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action)
|
||||||
action(configurator);
|
{
|
||||||
_command.Children.Add(command);
|
var command = ConfiguredCommand.FromBranch(settings, name);
|
||||||
}
|
|
||||||
|
// Create the configurator.
|
||||||
|
var configuratorType = typeof(Configurator<>).MakeGenericType(settings);
|
||||||
|
if (!(Activator.CreateInstance(configuratorType, new object?[] { command, _registrar }) is IUnsafeBranchConfigurator configurator))
|
||||||
|
{
|
||||||
|
throw new CommandConfigurationException("Could not create configurator by reflection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
action(configurator);
|
||||||
|
var added = _command.Children.AddAndReturn(command);
|
||||||
|
return new BranchConfigurator(added);
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,10 @@ internal sealed class ConfiguredCommand
|
|||||||
CommandType = commandType;
|
CommandType = commandType;
|
||||||
SettingsType = settingsType;
|
SettingsType = settingsType;
|
||||||
Delegate = @delegate;
|
Delegate = @delegate;
|
||||||
IsDefaultCommand = isDefaultCommand;
|
IsDefaultCommand = isDefaultCommand;
|
||||||
|
|
||||||
|
// Default commands are always created as hidden.
|
||||||
|
IsHidden = IsDefaultCommand;
|
||||||
|
|
||||||
Children = new List<ConfiguredCommand>();
|
Children = new List<ConfiguredCommand>();
|
||||||
Examples = new List<string[]>();
|
Examples = new List<string[]>();
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
namespace Spectre.Console.Cli.Internal.Configuration;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fluent configurator for the default command.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DefaultCommandConfigurator
|
||||||
|
{
|
||||||
|
private readonly ConfiguredCommand _defaultCommand;
|
||||||
|
|
||||||
|
internal DefaultCommandConfigurator(ConfiguredCommand defaultCommand)
|
||||||
|
{
|
||||||
|
_defaultCommand = defaultCommand;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the description of the default command.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="description">The default command description.</param>
|
||||||
|
/// <returns>The same <see cref="DefaultCommandConfigurator"/> instance so that multiple calls can be chained.</returns>
|
||||||
|
public DefaultCommandConfigurator WithDescription(string description)
|
||||||
|
{
|
||||||
|
_defaultCommand.Description = description;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets data that will be passed to the command via the <see cref="CommandContext"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The data to pass to the default command.</param>
|
||||||
|
/// <returns>The same <see cref="DefaultCommandConfigurator"/> instance so that multiple calls can be chained.</returns>
|
||||||
|
public DefaultCommandConfigurator WithData(object data)
|
||||||
|
{
|
||||||
|
_defaultCommand.Data = data;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,10 +6,6 @@ internal static class TypeRegistrarExtensions
|
|||||||
{
|
{
|
||||||
var stack = new Stack<CommandInfo>();
|
var stack = new Stack<CommandInfo>();
|
||||||
model.Commands.ForEach(c => stack.Push(c));
|
model.Commands.ForEach(c => stack.Push(c));
|
||||||
if (model.DefaultCommand != null)
|
|
||||||
{
|
|
||||||
stack.Push(model.DefaultCommand);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (stack.Count > 0)
|
while (stack.Count > 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -348,7 +348,17 @@ internal static class HelpWriter
|
|||||||
var columns = new List<string> { GetOptionParts(option) };
|
var columns = new List<string> { GetOptionParts(option) };
|
||||||
if (defaultValueColumn)
|
if (defaultValueColumn)
|
||||||
{
|
{
|
||||||
columns.Add(option.DefaultValue == null ? " " : $"[bold]{option.DefaultValue.ToString().EscapeMarkup()}[/]");
|
static string Bold(object obj) => $"[bold]{obj.ToString().EscapeMarkup()}[/]";
|
||||||
|
|
||||||
|
var defaultValue = option.DefaultValue switch
|
||||||
|
{
|
||||||
|
null => " ",
|
||||||
|
"" => " ",
|
||||||
|
Array { Length: 0 } => " ",
|
||||||
|
Array array => string.Join(", ", array.Cast<object>().Select(Bold)),
|
||||||
|
_ => Bold(option.DefaultValue),
|
||||||
|
};
|
||||||
|
columns.Add(defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
columns.Add(option.Description?.TrimEnd('.') ?? " ");
|
columns.Add(option.Description?.TrimEnd('.') ?? " ");
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
namespace Spectre.Console.Cli;
|
namespace Spectre.Console.Cli;
|
||||||
|
|
||||||
internal sealed class CommandInfo : ICommandContainer
|
internal sealed class CommandInfo : ICommandContainer
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public HashSet<string> Aliases { get; }
|
public HashSet<string> Aliases { get; }
|
||||||
public string? Description { get; }
|
public string? Description { get; }
|
||||||
@@ -10,14 +10,17 @@ internal sealed class CommandInfo : ICommandContainer
|
|||||||
public Type SettingsType { get; }
|
public Type SettingsType { get; }
|
||||||
public Func<CommandContext, CommandSettings, int>? Delegate { get; }
|
public Func<CommandContext, CommandSettings, int>? Delegate { get; }
|
||||||
public bool IsDefaultCommand { get; }
|
public bool IsDefaultCommand { get; }
|
||||||
public bool IsHidden { get; }
|
|
||||||
public CommandInfo? Parent { get; }
|
public CommandInfo? Parent { get; }
|
||||||
public IList<CommandInfo> Children { get; }
|
public IList<CommandInfo> Children { get; }
|
||||||
public IList<CommandParameter> Parameters { get; }
|
public IList<CommandParameter> Parameters { get; }
|
||||||
public IList<string[]> Examples { get; }
|
public IList<string[]> Examples { get; }
|
||||||
|
|
||||||
public bool IsBranch => CommandType == null && Delegate == null;
|
public bool IsBranch => CommandType == null && Delegate == null;
|
||||||
IList<CommandInfo> ICommandContainer.Commands => Children;
|
IList<CommandInfo> ICommandContainer.Commands => Children;
|
||||||
|
|
||||||
|
// only branches can have a default command
|
||||||
|
public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null;
|
||||||
|
public bool IsHidden { get; }
|
||||||
|
|
||||||
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
|
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,21 +4,20 @@ internal sealed class CommandModel : ICommandContainer
|
|||||||
{
|
{
|
||||||
public string? ApplicationName { get; }
|
public string? ApplicationName { get; }
|
||||||
public ParsingMode ParsingMode { get; }
|
public ParsingMode ParsingMode { get; }
|
||||||
public CommandInfo? DefaultCommand { get; }
|
|
||||||
public IList<CommandInfo> Commands { get; }
|
public IList<CommandInfo> Commands { get; }
|
||||||
public IList<string[]> Examples { get; }
|
public IList<string[]> Examples { get; }
|
||||||
public bool TrimTrailingPeriod { get; }
|
public bool TrimTrailingPeriod { get; }
|
||||||
|
|
||||||
|
public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);
|
||||||
|
|
||||||
public CommandModel(
|
public CommandModel(
|
||||||
CommandAppSettings settings,
|
CommandAppSettings settings,
|
||||||
CommandInfo? defaultCommand,
|
|
||||||
IEnumerable<CommandInfo> commands,
|
IEnumerable<CommandInfo> commands,
|
||||||
IEnumerable<string[]> examples)
|
IEnumerable<string[]> examples)
|
||||||
{
|
{
|
||||||
ApplicationName = settings.ApplicationName;
|
ApplicationName = settings.ApplicationName;
|
||||||
ParsingMode = settings.ParsingMode;
|
ParsingMode = settings.ParsingMode;
|
||||||
TrimTrailingPeriod = settings.TrimTrailingPeriod;
|
TrimTrailingPeriod = settings.TrimTrailingPeriod;
|
||||||
DefaultCommand = defaultCommand;
|
|
||||||
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
|
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
|
||||||
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
|
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -25,18 +25,19 @@ internal static class CommandModelBuilder
|
|||||||
result.Add(Build(null, command));
|
result.Add(Build(null, command));
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultCommand = default(CommandInfo);
|
|
||||||
if (configuration.DefaultCommand != null)
|
if (configuration.DefaultCommand != null)
|
||||||
{
|
{
|
||||||
// Add the examples from the configuration to the default command.
|
// Add the examples from the configuration to the default command.
|
||||||
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
|
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
|
||||||
|
|
||||||
// Build the default command.
|
// Build the default command.
|
||||||
defaultCommand = Build(null, configuration.DefaultCommand);
|
var defaultCommand = Build(null, configuration.DefaultCommand);
|
||||||
|
|
||||||
|
result.Add(defaultCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the command model and validate it.
|
// Create the command model and validate it.
|
||||||
var model = new CommandModel(configuration.Settings, defaultCommand, result, configuration.Examples);
|
var model = new CommandModel(configuration.Settings, result, configuration.Examples);
|
||||||
CommandModelValidator.Validate(model, configuration.Settings);
|
CommandModelValidator.Validate(model, configuration.Settings);
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
@@ -54,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.
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ internal static class CommandModelValidator
|
|||||||
throw new ArgumentNullException(nameof(settings));
|
throw new ArgumentNullException(nameof(settings));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (model.Commands.Count == 0 && model.DefaultCommand == null)
|
if (model.Commands.Count == 0)
|
||||||
{
|
{
|
||||||
throw CommandConfigurationException.NoCommandConfigured();
|
throw CommandConfigurationException.NoCommandConfigured();
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,6 @@ internal static class CommandModelValidator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Validate(model.DefaultCommand);
|
|
||||||
foreach (var command in model.Commands)
|
foreach (var command in model.Commands)
|
||||||
{
|
{
|
||||||
Validate(command);
|
Validate(command);
|
||||||
@@ -147,7 +146,7 @@ internal static class CommandModelValidator
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var parser = new CommandTreeParser(model, settings, ParsingMode.Strict);
|
var parser = new CommandTreeParser(model, settings.CaseSensitivity, ParsingMode.Strict);
|
||||||
parser.Parse(example);
|
parser.Parse(example);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
|||||||
@@ -8,5 +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>
|
||||||
|
/// Gets the default command for the container.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Returns null if a default command has not been set.
|
||||||
|
/// </remarks>
|
||||||
|
CommandInfo? DefaultCommand { get; }
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
|
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;
|
||||||
|
|
||||||
public CaseSensitivity CaseSensitivity { get; }
|
public CaseSensitivity CaseSensitivity { get; }
|
||||||
|
|
||||||
@@ -14,25 +17,26 @@ internal class CommandTreeParser
|
|||||||
Remaining = 1,
|
Remaining = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandTreeParser(CommandModel configuration, ICommandAppSettings settings, ParsingMode? parsingMode = null)
|
public CommandTreeParser(CommandModel configuration, CaseSensitivity caseSensitivity, ParsingMode? parsingMode = null, bool? convertFlagsToRemainingArguments = null)
|
||||||
{
|
{
|
||||||
if (settings is null)
|
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(settings));
|
|
||||||
}
|
|
||||||
|
|
||||||
_configuration = configuration;
|
|
||||||
_parsingMode = parsingMode ?? _configuration.ParsingMode;
|
_parsingMode = parsingMode ?? _configuration.ParsingMode;
|
||||||
_help = new CommandOptionAttribute("-h|--help");
|
_help = new CommandOptionAttribute("-h|--help");
|
||||||
|
_convertFlagsToRemainingArguments = convertFlagsToRemainingArguments ?? false;
|
||||||
CaseSensitivity = settings.CaseSensitivity;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
CaseSensitivity = caseSensitivity;
|
||||||
|
}
|
||||||
|
|
||||||
public CommandTreeParserResult Parse(IEnumerable<string> args)
|
public CommandTreeParserResult Parse(IEnumerable<string> args)
|
||||||
|
{
|
||||||
|
var parserContext = new CommandTreeParserContext(args, _parsingMode);
|
||||||
|
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
|
||||||
|
|
||||||
|
return Parse(parserContext, tokenizerResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandTreeParserResult Parse(CommandTreeParserContext context, CommandTreeTokenizerResult tokenizerResult)
|
||||||
{
|
{
|
||||||
var context = new CommandTreeParserContext(args, _parsingMode);
|
|
||||||
|
|
||||||
var tokenizerResult = CommandTreeTokenizer.Tokenize(context.Arguments);
|
|
||||||
var tokens = tokenizerResult.Tokens;
|
var tokens = tokenizerResult.Tokens;
|
||||||
var rawRemaining = tokenizerResult.Remaining;
|
var rawRemaining = tokenizerResult.Remaining;
|
||||||
|
|
||||||
@@ -254,10 +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)
|
||||||
{
|
{
|
||||||
node.Mapped.Add(new MappedCommandParameter(
|
ParseOptionValue(context, stream, token, node, option);
|
||||||
option, ParseOptionValue(context, stream, token, node, option)));
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +273,7 @@ internal class CommandTreeParser
|
|||||||
|
|
||||||
if (context.State == State.Remaining)
|
if (context.State == State.Remaining)
|
||||||
{
|
{
|
||||||
ParseOptionValue(context, stream, token, node, null);
|
ParseOptionValue(context, stream, token, node);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,17 +283,19 @@ internal class CommandTreeParser
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ParseOptionValue(context, stream, token, node, null);
|
ParseOptionValue(context, stream, token, node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string? ParseOptionValue(
|
private void ParseOptionValue(
|
||||||
CommandTreeParserContext context,
|
CommandTreeParserContext context,
|
||||||
CommandTreeTokenStream stream,
|
CommandTreeTokenStream stream,
|
||||||
CommandTreeToken token,
|
CommandTreeToken token,
|
||||||
CommandTree current,
|
CommandTree current,
|
||||||
CommandParameter? parameter)
|
CommandParameter? parameter = null)
|
||||||
{
|
{
|
||||||
|
bool addToMappedCommandParameters = parameter != null;
|
||||||
|
|
||||||
var value = default(string);
|
var value = default(string);
|
||||||
|
|
||||||
// Parse the value of the token (if any).
|
// Parse the value of the token (if any).
|
||||||
@@ -325,7 +329,21 @@ internal class CommandTreeParser
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Flags cannot be assigned a value.
|
// Flags cannot be assigned a value.
|
||||||
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
|
if (_convertFlagsToRemainingArguments)
|
||||||
|
{
|
||||||
|
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
|
||||||
|
|
||||||
|
context.AddRemainingArgument(token.Value, value);
|
||||||
|
|
||||||
|
// Prevent the option and it's non-boolean value from being added to
|
||||||
|
// mapped parameters (otherwise an exception will be thrown later
|
||||||
|
// when binding the value to the flag in the comand settings)
|
||||||
|
addToMappedCommandParameters = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -352,13 +370,14 @@ internal class CommandTreeParser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null);
|
context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (context.State == State.Remaining || context.ParsingMode == ParsingMode.Relaxed)
|
if (parameter == null && // Only add tokens which have not been matched to a command parameter
|
||||||
|
(context.State == State.Remaining || context.ParsingMode == ParsingMode.Relaxed))
|
||||||
{
|
{
|
||||||
context.AddRemainingArgument(token.Value, null);
|
context.AddRemainingArgument(token.Value, null);
|
||||||
}
|
}
|
||||||
@@ -379,10 +398,12 @@ internal class CommandTreeParser
|
|||||||
{
|
{
|
||||||
if (parameter.IsFlagValue())
|
if (parameter.IsFlagValue())
|
||||||
{
|
{
|
||||||
return null;
|
value = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -394,6 +415,9 @@ internal class CommandTreeParser
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value;
|
if (parameter != null && addToMappedCommandParameters)
|
||||||
|
{
|
||||||
|
current.Mapped.Add(new MappedCommandParameter(parameter, value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,19 +26,16 @@ 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 (State == CommandTreeParser.State.Remaining || ParsingMode == ParsingMode.Relaxed)
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Style", "IDE0004:Remove Unnecessary Cast", Justification = "Bug in analyzer?")]
|
[SuppressMessage("Style", "IDE0004:Remove Unnecessary Cast", Justification = "Bug in analyzer?")]
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ 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 CommandTreeToken this[int index] => _tokens[index];
|
public CommandTreeToken this[int index] => _tokens[index];
|
||||||
|
|
||||||
|
|||||||
@@ -19,5 +19,6 @@ public interface IUnsafeConfigurator
|
|||||||
/// <param name="name">The name of the command branch.</param>
|
/// <param name="name">The name of the command branch.</param>
|
||||||
/// <param name="settings">The command setting type.</param>
|
/// <param name="settings">The command setting type.</param>
|
||||||
/// <param name="action">The command branch configurator.</param>
|
/// <param name="action">The command branch configurator.</param>
|
||||||
void AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action);
|
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
|
||||||
|
IBranchConfigurator AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action);
|
||||||
}
|
}
|
||||||
@@ -89,15 +89,15 @@ public sealed class JsonText : JustInTimeRenderable
|
|||||||
|
|
||||||
var context = new JsonBuilderContext(new JsonTextStyles
|
var context = new JsonBuilderContext(new JsonTextStyles
|
||||||
{
|
{
|
||||||
BracesStyle = BracesStyle ?? new Style(Color.Grey),
|
BracesStyle = BracesStyle ?? Color.Grey,
|
||||||
BracketsStyle = BracketsStyle ?? new Style(Color.Grey),
|
BracketsStyle = BracketsStyle ?? Color.Grey,
|
||||||
MemberStyle = MemberStyle ?? new Style(Color.Blue),
|
MemberStyle = MemberStyle ?? Color.Blue,
|
||||||
ColonStyle = ColonStyle ?? new Style(Color.Yellow),
|
ColonStyle = ColonStyle ?? Color.Yellow,
|
||||||
CommaStyle = CommaStyle ?? new Style(Color.Grey),
|
CommaStyle = CommaStyle ?? Color.Grey,
|
||||||
StringStyle = StringStyle ?? new Style(Color.Red),
|
StringStyle = StringStyle ?? Color.Red,
|
||||||
NumberStyle = NumberStyle ?? new Style(Color.Green),
|
NumberStyle = NumberStyle ?? Color.Green,
|
||||||
BooleanStyle = BooleanStyle ?? new Style(Color.Green),
|
BooleanStyle = BooleanStyle ?? Color.Green,
|
||||||
NullStyle = NullStyle ?? new Style(Color.Grey),
|
NullStyle = NullStyle ?? Color.Grey,
|
||||||
});
|
});
|
||||||
|
|
||||||
_syntax.Accept(JsonBuilder.Shared, context);
|
_syntax.Accept(JsonBuilder.Shared, context);
|
||||||
|
|||||||
@@ -25,11 +25,25 @@ public sealed class CommandAppTester
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Sets the default command.
|
/// Sets the default command.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="description">The optional default command description.</param>
|
||||||
|
/// <param name="data">The optional default command data.</param>
|
||||||
/// <typeparam name="T">The default command type.</typeparam>
|
/// <typeparam name="T">The default command type.</typeparam>
|
||||||
public void SetDefaultCommand<T>()
|
public void SetDefaultCommand<T>(string? description = null, object? data = null)
|
||||||
where T : class, ICommand
|
where T : class, ICommand
|
||||||
{
|
{
|
||||||
_appConfiguration = (app) => app.SetDefaultCommand<T>();
|
_appConfiguration = (app) =>
|
||||||
|
{
|
||||||
|
var defaultCommandBuilder = app.SetDefaultCommand<T>();
|
||||||
|
if (description != null)
|
||||||
|
{
|
||||||
|
defaultCommandBuilder.WithDescription(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data != null)
|
||||||
|
{
|
||||||
|
defaultCommandBuilder.WithData(data);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public static class CalendarExtensions
|
|||||||
throw new ArgumentNullException(nameof(calendar));
|
throw new ArgumentNullException(nameof(calendar));
|
||||||
}
|
}
|
||||||
|
|
||||||
calendar.HightlightStyle = style ?? Style.Plain;
|
calendar.HighlightStyle = style ?? Style.Plain;
|
||||||
return calendar;
|
return calendar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,24 +7,25 @@ namespace Spectre.Console;
|
|||||||
|
|
||||||
internal static class AnsiDetector
|
internal static class AnsiDetector
|
||||||
{
|
{
|
||||||
private static readonly Regex[] _regexes = new[]
|
private static readonly Regex[] _regexes =
|
||||||
{
|
{
|
||||||
new Regex("^xterm"), // xterm, PuTTY, Mintty
|
new("^xterm"), // xterm, PuTTY, Mintty
|
||||||
new Regex("^rxvt"), // RXVT
|
new("^rxvt"), // RXVT
|
||||||
new Regex("^eterm"), // Eterm
|
new("^eterm"), // Eterm
|
||||||
new Regex("^screen"), // GNU screen, tmux
|
new("^screen"), // GNU screen, tmux
|
||||||
new Regex("tmux"), // tmux
|
new("tmux"), // tmux
|
||||||
new Regex("^vt100"), // DEC VT series
|
new("^vt100"), // DEC VT series
|
||||||
new Regex("^vt102"), // DEC VT series
|
new("^vt102"), // DEC VT series
|
||||||
new Regex("^vt220"), // DEC VT series
|
new("^vt220"), // DEC VT series
|
||||||
new Regex("^vt320"), // DEC VT series
|
new("^vt320"), // DEC VT series
|
||||||
new Regex("ansi"), // ANSI
|
new("ansi"), // ANSI
|
||||||
new Regex("scoansi"), // SCO ANSI
|
new("scoansi"), // SCO ANSI
|
||||||
new Regex("cygwin"), // Cygwin, MinGW
|
new("cygwin"), // Cygwin, MinGW
|
||||||
new Regex("linux"), // Linux console
|
new("linux"), // Linux console
|
||||||
new Regex("konsole"), // Konsole
|
new("konsole"), // Konsole
|
||||||
new Regex("bvterm"), // Bitvise SSH Client
|
new("bvterm"), // Bitvise SSH Client
|
||||||
new Regex("^st-256color"), // Suckless Simple Terminal, st
|
new("^st-256color"), // Suckless Simple Terminal, st
|
||||||
|
new("alacritty"), // Alacritty
|
||||||
};
|
};
|
||||||
|
|
||||||
public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool stdError, bool upgrade)
|
public static (bool SupportsAnsi, bool LegacyConsole) Detect(bool stdError, bool upgrade)
|
||||||
@@ -61,7 +62,7 @@ internal static class AnsiDetector
|
|||||||
return (false, true);
|
return (false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class Windows
|
private static class Windows
|
||||||
{
|
{
|
||||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
|
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1310:Field names should not contain underscore")]
|
||||||
private const int STD_OUTPUT_HANDLE = -11;
|
private const int STD_OUTPUT_HANDLE = -11;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public sealed class ElapsedTimeColumn : ProgressColumn
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the style of the remaining time text.
|
/// Gets or sets the style of the remaining time text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style Style { get; set; } = new Style(foreground: Color.Blue);
|
public Style Style { get; set; } = Color.Blue;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
|
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public sealed class PercentageColumn : ProgressColumn
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the style for a completed task.
|
/// Gets or sets the style for a completed task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style CompletedStyle { get; set; } = new Style(foreground: Color.Green);
|
public Style CompletedStyle { get; set; } = Color.Green;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
|
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
|
||||||
|
|||||||
@@ -13,17 +13,17 @@ public sealed class ProgressBarColumn : ProgressColumn
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the style of completed portions of the progress bar.
|
/// Gets or sets the style of completed portions of the progress bar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style CompletedStyle { get; set; } = new Style(foreground: Color.Yellow);
|
public Style CompletedStyle { get; set; } = Color.Yellow;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the style of a finished progress bar.
|
/// Gets or sets the style of a finished progress bar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style FinishedStyle { get; set; } = new Style(foreground: Color.Green);
|
public Style FinishedStyle { get; set; } = Color.Green;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the style of remaining portions of the progress bar.
|
/// Gets or sets the style of remaining portions of the progress bar.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style RemainingStyle { get; set; } = new Style(foreground: Color.Grey);
|
public Style RemainingStyle { get; set; } = Color.Grey;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the style of an indeterminate progress bar.
|
/// Gets or sets the style of an indeterminate progress bar.
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public sealed class RemainingTimeColumn : ProgressColumn
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the style of the remaining time text.
|
/// Gets or sets the style of the remaining time text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style Style { get; set; } = new Style(foreground: Color.Blue);
|
public Style Style { get; set; } = Color.Blue;
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
|
public override IRenderable Render(RenderOptions options, ProgressTask task, TimeSpan deltaTime)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public sealed class SpinnerColumn : ProgressColumn
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the style of the spinner.
|
/// Gets or sets the style of the spinner.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style? Style { get; set; } = new Style(foreground: Color.Yellow);
|
public Style? Style { get; set; } = Color.Yellow;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SpinnerColumn"/> class.
|
/// Initializes a new instance of the <see cref="SpinnerColumn"/> class.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public sealed class Status
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the spinner style.
|
/// Gets or sets the spinner style.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style? SpinnerStyle { get; set; } = new Style(foreground: Color.Yellow);
|
public Style? SpinnerStyle { get; set; } = Color.Yellow;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a value indicating whether or not status
|
/// Gets or sets a value indicating whether or not status
|
||||||
|
|||||||
@@ -39,6 +39,15 @@ public sealed class ConfirmationPrompt : IPrompt<bool>
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool ShowDefaultValue { get; set; } = true;
|
public bool ShowDefaultValue { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the string comparer to use when comparing user input
|
||||||
|
/// against Yes/No choices.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Defaults to <see cref="StringComparer.CurrentCultureIgnoreCase"/>.
|
||||||
|
/// </remarks>
|
||||||
|
public StringComparer Comparer { get; set; } = StringComparer.CurrentCultureIgnoreCase;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ConfirmationPrompt"/> class.
|
/// Initializes a new instance of the <see cref="ConfirmationPrompt"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -57,7 +66,9 @@ public sealed class ConfirmationPrompt : IPrompt<bool>
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<bool> ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
|
public async Task<bool> ShowAsync(IAnsiConsole console, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var prompt = new TextPrompt<char>(_prompt)
|
var comparer = Comparer ?? StringComparer.CurrentCultureIgnoreCase;
|
||||||
|
|
||||||
|
var prompt = new TextPrompt<char>(_prompt, comparer)
|
||||||
.InvalidChoiceMessage(InvalidChoiceMessage)
|
.InvalidChoiceMessage(InvalidChoiceMessage)
|
||||||
.ValidationErrorMessage(InvalidChoiceMessage)
|
.ValidationErrorMessage(InvalidChoiceMessage)
|
||||||
.ShowChoices(ShowChoices)
|
.ShowChoices(ShowChoices)
|
||||||
@@ -67,6 +78,7 @@ public sealed class ConfirmationPrompt : IPrompt<bool>
|
|||||||
.AddChoice(No);
|
.AddChoice(No);
|
||||||
|
|
||||||
var result = await prompt.ShowAsync(console, cancellationToken).ConfigureAwait(false);
|
var result = await prompt.ShowAsync(console, cancellationToken).ConfigureAwait(false);
|
||||||
return result == Yes;
|
|
||||||
|
return comparer.Compare(Yes.ToString(), result.ToString()) == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,7 +225,7 @@ public sealed class MultiSelectionPrompt<T> : IPrompt<List<T>>, IListPromptStrat
|
|||||||
IRenderable IListPromptStrategy<T>.Render(IAnsiConsole console, bool scrollable, int cursorIndex, IEnumerable<(int Index, ListPromptItem<T> Node)> items)
|
IRenderable IListPromptStrategy<T>.Render(IAnsiConsole console, bool scrollable, int cursorIndex, IEnumerable<(int Index, ListPromptItem<T> Node)> items)
|
||||||
{
|
{
|
||||||
var list = new List<IRenderable>();
|
var list = new List<IRenderable>();
|
||||||
var highlightStyle = HighlightStyle ?? new Style(foreground: Color.Blue);
|
var highlightStyle = HighlightStyle ?? Color.Blue;
|
||||||
|
|
||||||
if (Title != null)
|
if (Title != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -137,8 +137,8 @@ public sealed class SelectionPrompt<T> : IPrompt<T>, IListPromptStrategy<T>
|
|||||||
IRenderable IListPromptStrategy<T>.Render(IAnsiConsole console, bool scrollable, int cursorIndex, IEnumerable<(int Index, ListPromptItem<T> Node)> items)
|
IRenderable IListPromptStrategy<T>.Render(IAnsiConsole console, bool scrollable, int cursorIndex, IEnumerable<(int Index, ListPromptItem<T> Node)> items)
|
||||||
{
|
{
|
||||||
var list = new List<IRenderable>();
|
var list = new List<IRenderable>();
|
||||||
var disabledStyle = DisabledStyle ?? new Style(foreground: Color.Grey);
|
var disabledStyle = DisabledStyle ?? Color.Grey;
|
||||||
var highlightStyle = HighlightStyle ?? new Style(foreground: Color.Blue);
|
var highlightStyle = HighlightStyle ?? Color.Blue;
|
||||||
|
|
||||||
if (Title != null)
|
if (Title != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -132,6 +132,15 @@ public sealed class Style : IEquatable<Style>
|
|||||||
return Parse(style);
|
return Parse(style);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Implicitly converts <see cref="Color"/> into a <see cref="Style"/> with a foreground color.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="color">The foreground color.</param>
|
||||||
|
public static implicit operator Style(Color color)
|
||||||
|
{
|
||||||
|
return new Style(foreground: color);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Converts the string representation of a style to its <see cref="Style"/> equivalent.
|
/// Converts the string representation of a style to its <see cref="Style"/> equivalent.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the calendar's highlight <see cref="Style"/>.
|
/// Gets or sets the calendar's highlight <see cref="Style"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style HightlightStyle
|
public Style HighlightStyle
|
||||||
{
|
{
|
||||||
get => _highlightStyle;
|
get => _highlightStyle;
|
||||||
set => MarkAsDirty(() => _highlightStyle = value);
|
set => MarkAsDirty(() => _highlightStyle = value);
|
||||||
@@ -153,9 +153,9 @@ public sealed class Calendar : JustInTimeRenderable, IHasCulture, IHasTableBorde
|
|||||||
_useSafeBorder = true;
|
_useSafeBorder = true;
|
||||||
_borderStyle = null;
|
_borderStyle = null;
|
||||||
_culture = CultureInfo.InvariantCulture;
|
_culture = CultureInfo.InvariantCulture;
|
||||||
_highlightStyle = new Style(foreground: Color.Blue);
|
_highlightStyle = Color.Blue;
|
||||||
_showHeader = true;
|
_showHeader = true;
|
||||||
_calendarEvents = new ListWithCallback<CalendarEvent>(() => MarkAsDirty());
|
_calendarEvents = new ListWithCallback<CalendarEvent>(MarkAsDirty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
|
|||||||
@@ -13,27 +13,27 @@ public sealed class ExceptionStyle
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the exception color.
|
/// Gets or sets the exception color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style Exception { get; set; } = new Style(Color.White);
|
public Style Exception { get; set; } = Color.White;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the method color.
|
/// Gets or sets the method color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style Method { get; set; } = new Style(Color.Yellow);
|
public Style Method { get; set; } = Color.Yellow;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the parameter type color.
|
/// Gets or sets the parameter type color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style ParameterType { get; set; } = new Style(Color.Blue);
|
public Style ParameterType { get; set; } = Color.Blue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the parameter name color.
|
/// Gets or sets the parameter name color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style ParameterName { get; set; } = new Style(Color.Silver);
|
public Style ParameterName { get; set; } = Color.Silver;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the parenthesis color.
|
/// Gets or sets the parenthesis color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style Parenthesis { get; set; } = new Style(Color.Silver);
|
public Style Parenthesis { get; set; } = Color.Silver;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the path color.
|
/// Gets or sets the path color.
|
||||||
@@ -43,15 +43,15 @@ public sealed class ExceptionStyle
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the line number color.
|
/// Gets or sets the line number color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style LineNumber { get; set; } = new Style(Color.Blue);
|
public Style LineNumber { get; set; } = Color.Blue;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the color for dimmed text such as "at" or "in".
|
/// Gets or sets the color for dimmed text such as "at" or "in".
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style Dimmed { get; set; } = new Style(Color.Grey);
|
public Style Dimmed { get; set; } = Color.Grey;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the color for non emphasized items.
|
/// Gets or sets the color for non emphasized items.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Style NonEmphasized { get; set; } = new Style(Color.Silver);
|
public Style NonEmphasized { get; set; } = Color.Silver;
|
||||||
}
|
}
|
||||||
@@ -49,8 +49,8 @@ public sealed class Grid : JustInTimeRenderable, IExpandable, IAlignable
|
|||||||
{
|
{
|
||||||
_expand = false;
|
_expand = false;
|
||||||
_alignment = null;
|
_alignment = null;
|
||||||
_columns = new ListWithCallback<GridColumn>(() => MarkAsDirty());
|
_columns = new ListWithCallback<GridColumn>(MarkAsDirty);
|
||||||
_rows = new ListWithCallback<GridRow>(() => MarkAsDirty());
|
_rows = new ListWithCallback<GridRow>(MarkAsDirty);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -68,8 +68,6 @@ public sealed class Paragraph : Renderable, IHasJustification, IOverflowable
|
|||||||
|
|
||||||
foreach (var (_, first, last, part) in text.SplitLines().Enumerate())
|
foreach (var (_, first, last, part) in text.SplitLines().Enumerate())
|
||||||
{
|
{
|
||||||
var current = part;
|
|
||||||
|
|
||||||
if (first)
|
if (first)
|
||||||
{
|
{
|
||||||
var line = _lines.LastOrDefault();
|
var line = _lines.LastOrDefault();
|
||||||
@@ -79,13 +77,13 @@ public sealed class Paragraph : Renderable, IHasJustification, IOverflowable
|
|||||||
line = _lines.Last();
|
line = _lines.Last();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(current))
|
if (string.IsNullOrEmpty(part))
|
||||||
{
|
{
|
||||||
line.Add(Segment.Empty);
|
line.Add(Segment.Empty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var span in current.SplitWords())
|
foreach (var span in part.SplitWords())
|
||||||
{
|
{
|
||||||
line.Add(new Segment(span, style ?? Style.Plain));
|
line.Add(new Segment(span, style ?? Style.Plain));
|
||||||
}
|
}
|
||||||
@@ -95,13 +93,13 @@ public sealed class Paragraph : Renderable, IHasJustification, IOverflowable
|
|||||||
{
|
{
|
||||||
var line = new SegmentLine();
|
var line = new SegmentLine();
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(current))
|
if (string.IsNullOrEmpty(part))
|
||||||
{
|
{
|
||||||
line.Add(Segment.Empty);
|
line.Add(Segment.Empty);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
foreach (var span in current.SplitWords())
|
foreach (var span in part.SplitWords())
|
||||||
{
|
{
|
||||||
line.Add(new Segment(span, style ?? Style.Plain));
|
line.Add(new Segment(span, style ?? Style.Plain));
|
||||||
}
|
}
|
||||||
@@ -198,13 +196,11 @@ public sealed class Paragraph : Renderable, IHasJustification, IOverflowable
|
|||||||
var lines = new List<SegmentLine>();
|
var lines = new List<SegmentLine>();
|
||||||
var line = new SegmentLine();
|
var line = new SegmentLine();
|
||||||
|
|
||||||
var newLine = true;
|
|
||||||
|
|
||||||
using var iterator = new SegmentLineIterator(_lines);
|
using var iterator = new SegmentLineIterator(_lines);
|
||||||
var queue = new Queue<Segment>();
|
var queue = new Queue<Segment>();
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
var current = (Segment?)null;
|
Segment? current;
|
||||||
if (queue.Count == 0)
|
if (queue.Count == 0)
|
||||||
{
|
{
|
||||||
if (!iterator.MoveNext())
|
if (!iterator.MoveNext())
|
||||||
@@ -224,13 +220,12 @@ public sealed class Paragraph : Renderable, IHasJustification, IOverflowable
|
|||||||
throw new InvalidOperationException("Iterator returned empty segment.");
|
throw new InvalidOperationException("Iterator returned empty segment.");
|
||||||
}
|
}
|
||||||
|
|
||||||
newLine = false;
|
var newLine = false;
|
||||||
|
|
||||||
if (current.IsLineBreak)
|
if (current.IsLineBreak)
|
||||||
{
|
{
|
||||||
lines.Add(line);
|
lines.Add(line);
|
||||||
line = new SegmentLine();
|
line = new SegmentLine();
|
||||||
newLine = true;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,7 +241,6 @@ public sealed class Paragraph : Renderable, IHasJustification, IOverflowable
|
|||||||
{
|
{
|
||||||
lines.Add(line);
|
lines.Add(line);
|
||||||
line = new SegmentLine();
|
line = new SegmentLine();
|
||||||
newLine = true;
|
|
||||||
|
|
||||||
segments.ForEach(s => queue.Enqueue(s));
|
segments.ForEach(s => queue.Enqueue(s));
|
||||||
continue;
|
continue;
|
||||||
@@ -276,8 +270,6 @@ public sealed class Paragraph : Renderable, IHasJustification, IOverflowable
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
newLine = false;
|
|
||||||
|
|
||||||
line.Add(current);
|
line.Add(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ internal sealed class ProgressBar : Renderable, IHasCulture
|
|||||||
public bool IsIndeterminate { get; set; }
|
public bool IsIndeterminate { get; set; }
|
||||||
public CultureInfo? Culture { get; set; }
|
public CultureInfo? Culture { get; set; }
|
||||||
|
|
||||||
public Style CompletedStyle { get; set; } = new Style(foreground: Color.Yellow);
|
public Style CompletedStyle { get; set; } = Color.Yellow;
|
||||||
public Style FinishedStyle { get; set; } = new Style(foreground: Color.Green);
|
public Style FinishedStyle { get; set; } = Color.Green;
|
||||||
public Style RemainingStyle { get; set; } = new Style(foreground: Color.Grey);
|
public Style RemainingStyle { get; set; } = Color.Grey;
|
||||||
public Style IndeterminateStyle { get; set; } = DefaultPulseStyle;
|
public Style IndeterminateStyle { get; set; } = DefaultPulseStyle;
|
||||||
|
|
||||||
internal static Style DefaultPulseStyle { get; } = new Style(foreground: Color.DodgerBlue1, background: Color.Grey23);
|
internal static Style DefaultPulseStyle { get; } = new Style(foreground: Color.DodgerBlue1, background: Color.Grey23);
|
||||||
@@ -57,11 +57,6 @@ internal sealed class ProgressBar : Renderable, IHasCulture
|
|||||||
barCount = Math.Max(0, barCount);
|
barCount = Math.Max(0, barCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (barCount < 0)
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield return new Segment(new string(bar, barCount), style);
|
yield return new Segment(new string(bar, barCount), style);
|
||||||
|
|
||||||
if (ShowValue)
|
if (ShowValue)
|
||||||
@@ -99,14 +94,13 @@ internal sealed class ProgressBar : Renderable, IHasCulture
|
|||||||
{
|
{
|
||||||
// For 1-bit and 3-bit colors, fall back to
|
// For 1-bit and 3-bit colors, fall back to
|
||||||
// a simpler versions with only two colors.
|
// a simpler versions with only two colors.
|
||||||
if (options.ColorSystem == ColorSystem.NoColors ||
|
if (options.ColorSystem is ColorSystem.NoColors or ColorSystem.Legacy)
|
||||||
options.ColorSystem == ColorSystem.Legacy)
|
|
||||||
{
|
{
|
||||||
// First half of the pulse
|
// First half of the pulse
|
||||||
var segments = Enumerable.Repeat(new Segment(bar, new Style(style.Foreground)), PULSESIZE / 2);
|
var segments = Enumerable.Repeat(new Segment(bar, new Style(style.Foreground)), PULSESIZE / 2);
|
||||||
|
|
||||||
// Second half of the pulse
|
// Second half of the pulse
|
||||||
var legacy = options.ColorSystem == ColorSystem.NoColors || options.ColorSystem == ColorSystem.Legacy;
|
var legacy = options.ColorSystem is ColorSystem.NoColors or ColorSystem.Legacy;
|
||||||
var bar2 = legacy ? " " : bar;
|
var bar2 = legacy ? " " : bar;
|
||||||
segments = segments.Concat(Enumerable.Repeat(new Segment(bar2, new Style(style.Background)), PULSESIZE - (PULSESIZE / 2)));
|
segments = segments.Concat(Enumerable.Repeat(new Segment(bar2, new Style(style.Background)), PULSESIZE - (PULSESIZE / 2)));
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ namespace Spectre.Console;
|
|||||||
|
|
||||||
internal static class TableRenderer
|
internal static class TableRenderer
|
||||||
{
|
{
|
||||||
private static readonly Style _defaultHeadingStyle = new Style(Color.Silver);
|
private static readonly Style _defaultHeadingStyle = Color.Silver;
|
||||||
private static readonly Style _defaultCaptionStyle = new Style(Color.Grey);
|
private static readonly Style _defaultCaptionStyle = Color.Grey;
|
||||||
|
|
||||||
public static List<Segment> Render(TableRendererContext context, List<int> columnWidths)
|
public static List<Segment> Render(TableRendererContext context, List<int> columnWidths)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,12 +52,12 @@ public sealed class TextPath : IRenderable, IHasJustification
|
|||||||
_parts = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
_parts = path.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
// Rooted Unix path?
|
// Rooted Unix path?
|
||||||
if (path.StartsWith("/"))
|
if (path.StartsWith("/", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
_rooted = true;
|
_rooted = true;
|
||||||
_parts = new[] { "/" }.Concat(_parts).ToArray();
|
_parts = new[] { "/" }.Concat(_parts).ToArray();
|
||||||
}
|
}
|
||||||
else if (_parts.Length > 0 && _parts[0].EndsWith(":"))
|
else if (_parts.Length > 0 && _parts[0].EndsWith(":", StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
// Rooted Windows path
|
// Rooted Windows path
|
||||||
_rooted = true;
|
_rooted = true;
|
||||||
|
|||||||
@@ -4,13 +4,20 @@ public static class SpectreAnalyzerVerifier<TAnalyzer>
|
|||||||
where TAnalyzer : DiagnosticAnalyzer, new()
|
where TAnalyzer : DiagnosticAnalyzer, new()
|
||||||
{
|
{
|
||||||
public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource)
|
public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string fixedSource)
|
||||||
=> VerifyCodeFixAsync(source, new[] { expected }, fixedSource);
|
=> VerifyCodeFixAsync(source, OutputKind.DynamicallyLinkedLibrary, new[] { expected }, fixedSource);
|
||||||
|
|
||||||
|
public static Task VerifyCodeFixAsync(string source, OutputKind outputKind, DiagnosticResult expected, string fixedSource)
|
||||||
|
=> VerifyCodeFixAsync(source, outputKind, new[] { expected }, fixedSource);
|
||||||
|
|
||||||
private static Task VerifyCodeFixAsync(string source, IEnumerable<DiagnosticResult> expected, string fixedSource)
|
private static Task VerifyCodeFixAsync(string source, OutputKind outputKind, IEnumerable<DiagnosticResult> expected, string fixedSource)
|
||||||
{
|
{
|
||||||
var test = new Test
|
var test = new Test
|
||||||
{
|
{
|
||||||
TestCode = source,
|
TestCode = source,
|
||||||
|
TestState =
|
||||||
|
{
|
||||||
|
OutputKind = outputKind,
|
||||||
|
},
|
||||||
FixedCode = fixedSource,
|
FixedCode = fixedSource,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,74 @@ class TestClass
|
|||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SystemConsole_replaced_with_local_variable_AnsiConsole()
|
||||||
|
{
|
||||||
|
const string Source = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
void TestMethod()
|
||||||
|
{
|
||||||
|
IAnsiConsole ansiConsole = null;
|
||||||
|
Console.WriteLine(""Hello, World"");
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
const string FixedSource = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
void TestMethod()
|
||||||
|
{
|
||||||
|
IAnsiConsole ansiConsole = null;
|
||||||
|
ansiConsole.WriteLine(""Hello, World"");
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
await SpectreAnalyzerVerifier<UseSpectreInsteadOfSystemConsoleAnalyzer>
|
||||||
|
.VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(10, 9), FixedSource)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SystemConsole_not_replaced_with_local_variable_declared_after_the_call()
|
||||||
|
{
|
||||||
|
const string Source = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
void TestMethod()
|
||||||
|
{
|
||||||
|
Console.WriteLine(""Hello, World"");
|
||||||
|
IAnsiConsole ansiConsole;
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
const string FixedSource = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
void TestMethod()
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteLine(""Hello, World"");
|
||||||
|
IAnsiConsole ansiConsole;
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
await SpectreAnalyzerVerifier<UseSpectreInsteadOfSystemConsoleAnalyzer>
|
||||||
|
.VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(9, 9), FixedSource)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task SystemConsole_replaced_with_static_field_AnsiConsole()
|
public async Task SystemConsole_replaced_with_static_field_AnsiConsole()
|
||||||
{
|
{
|
||||||
@@ -139,4 +207,127 @@ class TestClass
|
|||||||
.VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(11, 9), FixedSource)
|
.VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(11, 9), FixedSource)
|
||||||
.ConfigureAwait(false);
|
.ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SystemConsole_replaced_with_AnsiConsole_when_field_is_not_static()
|
||||||
|
{
|
||||||
|
const string Source = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
IAnsiConsole _ansiConsole;
|
||||||
|
|
||||||
|
static void TestMethod()
|
||||||
|
{
|
||||||
|
Console.WriteLine(""Hello, World"");
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
const string FixedSource = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
IAnsiConsole _ansiConsole;
|
||||||
|
|
||||||
|
static void TestMethod()
|
||||||
|
{
|
||||||
|
AnsiConsole.WriteLine(""Hello, World"");
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
await SpectreAnalyzerVerifier<UseSpectreInsteadOfSystemConsoleAnalyzer>
|
||||||
|
.VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(11, 9), FixedSource)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SystemConsole_replaced_with_AnsiConsole_from_local_function_parameter()
|
||||||
|
{
|
||||||
|
const string Source = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
static void TestMethod()
|
||||||
|
{
|
||||||
|
static void LocalFunction(IAnsiConsole ansiConsole) => Console.WriteLine(""Hello, World"");
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
const string FixedSource = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
static void TestMethod()
|
||||||
|
{
|
||||||
|
static void LocalFunction(IAnsiConsole ansiConsole) => ansiConsole.WriteLine(""Hello, World"");
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
await SpectreAnalyzerVerifier<UseSpectreInsteadOfSystemConsoleAnalyzer>
|
||||||
|
.VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(9, 64), FixedSource)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SystemConsole_do_not_use_variable_from_parent_method_in_static_local_function()
|
||||||
|
{
|
||||||
|
const string Source = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
static void TestMethod()
|
||||||
|
{
|
||||||
|
IAnsiConsole ansiConsole = null;
|
||||||
|
static void LocalFunction() => Console.WriteLine(""Hello, World"");
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
const string FixedSource = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
class TestClass
|
||||||
|
{
|
||||||
|
static void TestMethod()
|
||||||
|
{
|
||||||
|
IAnsiConsole ansiConsole = null;
|
||||||
|
static void LocalFunction() => AnsiConsole.WriteLine(""Hello, World"");
|
||||||
|
}
|
||||||
|
}";
|
||||||
|
|
||||||
|
await SpectreAnalyzerVerifier<UseSpectreInsteadOfSystemConsoleAnalyzer>
|
||||||
|
.VerifyCodeFixAsync(Source, _expectedDiagnostic.WithLocation(10, 40), FixedSource)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SystemConsole_replaced_with_AnsiConsole_in_top_level_statements()
|
||||||
|
{
|
||||||
|
const string Source = @"
|
||||||
|
using System;
|
||||||
|
|
||||||
|
Console.WriteLine(""Hello, World"");
|
||||||
|
";
|
||||||
|
|
||||||
|
const string FixedSource = @"
|
||||||
|
using System;
|
||||||
|
using Spectre.Console;
|
||||||
|
|
||||||
|
AnsiConsole.WriteLine(""Hello, World"");
|
||||||
|
";
|
||||||
|
|
||||||
|
await SpectreAnalyzerVerifier<UseSpectreInsteadOfSystemConsoleAnalyzer>
|
||||||
|
.VerifyCodeFixAsync(Source, OutputKind.ConsoleApplication, _expectedDiagnostic.WithLocation(4, 1), FixedSource)
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
namespace Spectre.Console.Tests.Data;
|
namespace Spectre.Console.Tests.Data;
|
||||||
|
|
||||||
[Description("The horse command.")]
|
[Description("The horse command.")]
|
||||||
public class HorseCommand : AnimalCommand<MammalSettings>
|
public class HorseCommand : AnimalCommand<HorseSettings>
|
||||||
{
|
{
|
||||||
public override int Execute(CommandContext context, MammalSettings settings)
|
public override int Execute(CommandContext context, HorseSettings settings)
|
||||||
{
|
{
|
||||||
DumpSettings(context, settings);
|
DumpSettings(context, settings);
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace Spectre.Console.Tests.Data;
|
||||||
|
|
||||||
|
public class HorseSettings : MammalSettings
|
||||||
|
{
|
||||||
|
[CommandOption("-d|--day")]
|
||||||
|
public DayOfWeek Day { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("--file")]
|
||||||
|
public FileInfo File { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("--directory")]
|
||||||
|
public DirectoryInfo Directory { get; set; }
|
||||||
|
}
|
||||||
@@ -9,4 +9,9 @@ public class LionSettings : CatSettings
|
|||||||
[CommandOption("-c <CHILDREN>")]
|
[CommandOption("-c <CHILDREN>")]
|
||||||
[Description("The number of children the lion has.")]
|
[Description("The number of children the lion has.")]
|
||||||
public int Children { get; set; }
|
public int Children { get; set; }
|
||||||
|
|
||||||
|
[CommandOption("-d <DAY>")]
|
||||||
|
[Description("The days the lion goes hunting.")]
|
||||||
|
[DefaultValue(new[] { DayOfWeek.Monday, DayOfWeek.Thursday })]
|
||||||
|
public DayOfWeek[] HuntDays { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,3 +33,18 @@ public sealed class RequiredArgumentWithDefaultValueSettings : CommandSettings
|
|||||||
[DefaultValue("Hello World")]
|
[DefaultValue("Hello World")]
|
||||||
public string Greeting { get; set; }
|
public string Greeting { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class OptionWithArrayOfEnumDefaultValueSettings : CommandSettings
|
||||||
|
{
|
||||||
|
[CommandOption("--days")]
|
||||||
|
[DefaultValue(new[] { DayOfWeek.Sunday, DayOfWeek.Saturday })]
|
||||||
|
public DayOfWeek[] Days { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class OptionWithArrayOfStringDefaultValueAndTypeConverterSettings : CommandSettings
|
||||||
|
{
|
||||||
|
[CommandOption("--numbers")]
|
||||||
|
[DefaultValue(new[] { "2", "3" })]
|
||||||
|
[TypeConverter(typeof(StringToIntegerConverter))]
|
||||||
|
public int[] Numbers { get; set; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ ARGUMENTS:
|
|||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
DEFAULT
|
DEFAULT
|
||||||
-h, --help Prints help information
|
-h, --help Prints help information
|
||||||
-a, --alive Indicates whether or not the animal is alive
|
-a, --alive Indicates whether or not the animal is alive
|
||||||
-n, --name <VALUE>
|
-n, --name <VALUE>
|
||||||
--agility <VALUE> 10 The agility between 0 and 100
|
--agility <VALUE> 10 The agility between 0 and 100
|
||||||
-c <CHILDREN> The number of children the lion has
|
-c <CHILDREN> The number of children the lion has
|
||||||
|
-d <DAY> Monday, Thursday The days the lion goes hunting
|
||||||
@@ -13,8 +13,9 @@ ARGUMENTS:
|
|||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
DEFAULT
|
DEFAULT
|
||||||
-h, --help Prints help information
|
-h, --help Prints help information
|
||||||
-a, --alive Indicates whether or not the animal is alive
|
-a, --alive Indicates whether or not the animal is alive
|
||||||
-n, --name <VALUE>
|
-n, --name <VALUE>
|
||||||
--agility <VALUE> 10 The agility between 0 and 100
|
--agility <VALUE> 10 The agility between 0 and 100
|
||||||
-c <CHILDREN> The number of children the lion has
|
-c <CHILDREN> The number of children the lion has
|
||||||
|
-d <DAY> Monday, Thursday The days the lion goes hunting
|
||||||
@@ -10,8 +10,9 @@ ARGUMENTS:
|
|||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
DEFAULT
|
DEFAULT
|
||||||
-h, --help Prints help information
|
-h, --help Prints help information
|
||||||
-a, --alive Indicates whether or not the animal is alive
|
-a, --alive Indicates whether or not the animal is alive
|
||||||
-n, --name <VALUE>
|
-n, --name <VALUE>
|
||||||
--agility <VALUE> 10 The agility between 0 and 100
|
--agility <VALUE> 10 The agility between 0 and 100
|
||||||
-c <CHILDREN> The number of children the lion has
|
-c <CHILDREN> The number of children the lion has
|
||||||
|
-d <DAY> Monday, Thursday The days the lion goes hunting
|
||||||
@@ -10,11 +10,12 @@ ARGUMENTS:
|
|||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
DEFAULT
|
DEFAULT
|
||||||
-h, --help Prints help information
|
-h, --help Prints help information
|
||||||
-a, --alive Indicates whether or not the animal is alive
|
-a, --alive Indicates whether or not the animal is alive
|
||||||
-n, --name <VALUE>
|
-n, --name <VALUE>
|
||||||
--agility <VALUE> 10 The agility between 0 and 100
|
--agility <VALUE> 10 The agility between 0 and 100
|
||||||
-c <CHILDREN> The number of children the lion has
|
-c <CHILDREN> The number of children the lion has
|
||||||
|
-d <DAY> Monday, Thursday The days the lion goes hunting
|
||||||
|
|
||||||
COMMANDS:
|
COMMANDS:
|
||||||
giraffe <LENGTH> The giraffe command
|
giraffe <LENGTH> The giraffe command
|
||||||
@@ -8,5 +8,7 @@ ARGUMENTS:
|
|||||||
<TEETH> The number of teeth the lion has
|
<TEETH> The number of teeth the lion has
|
||||||
|
|
||||||
OPTIONS:
|
OPTIONS:
|
||||||
-h, --help Prints help information
|
DEFAULT
|
||||||
-c <CHILDREN> The number of children the lion has
|
-h, --help Prints help information
|
||||||
|
-c <CHILDREN> The number of children the lion has
|
||||||
|
-d <DAY> Monday, Thursday The days the lion goes hunting
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Model>
|
<Model>
|
||||||
<!--ANIMAL-->
|
<!--ANIMAL-->
|
||||||
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
||||||
@@ -27,9 +27,11 @@
|
|||||||
</Parameters>
|
</Parameters>
|
||||||
</Command>
|
</Command>
|
||||||
<!--HORSE-->
|
<!--HORSE-->
|
||||||
<Command Name="horse" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.MammalSettings">
|
<Command Name="horse" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.HorseSettings">
|
||||||
<Parameters>
|
<Parameters>
|
||||||
<Option Shadowed="true" Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
<Option Short="d" Long="day" Value="NULL" Required="false" Kind="scalar" ClrType="System.DayOfWeek" />
|
||||||
|
<Option Short="" Long="directory" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.DirectoryInfo" />
|
||||||
|
<Option Short="" Long="file" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.FileInfo" />
|
||||||
</Parameters>
|
</Parameters>
|
||||||
</Command>
|
</Command>
|
||||||
</Command>
|
</Command>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Model>
|
<Model>
|
||||||
<!--ANIMAL-->
|
<!--ANIMAL-->
|
||||||
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
||||||
@@ -23,8 +23,11 @@
|
|||||||
</Parameters>
|
</Parameters>
|
||||||
</Command>
|
</Command>
|
||||||
<!--HORSE-->
|
<!--HORSE-->
|
||||||
<Command Name="horse" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.MammalSettings">
|
<Command Name="horse" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.HorseSettings">
|
||||||
<Parameters>
|
<Parameters>
|
||||||
|
<Option Short="d" Long="day" Value="NULL" Required="false" Kind="scalar" ClrType="System.DayOfWeek" />
|
||||||
|
<Option Short="" Long="directory" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.DirectoryInfo" />
|
||||||
|
<Option Short="" Long="file" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.FileInfo" />
|
||||||
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
</Parameters>
|
</Parameters>
|
||||||
</Command>
|
</Command>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Model>
|
<Model>
|
||||||
<!--DEFAULT COMMAND-->
|
<!--DEFAULT COMMAND-->
|
||||||
<Command Name="__default_command" IsBranch="false" IsDefault="true" ClrType="Spectre.Console.Tests.Data.DogCommand" Settings="Spectre.Console.Tests.Data.DogSettings">
|
<Command Name="__default_command" IsBranch="false" IsDefault="true" ClrType="Spectre.Console.Tests.Data.DogCommand" Settings="Spectre.Console.Tests.Data.DogSettings">
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
</Parameters>
|
</Parameters>
|
||||||
</Command>
|
</Command>
|
||||||
<!--HORSE-->
|
<!--HORSE-->
|
||||||
<Command Name="horse" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.MammalSettings">
|
<Command Name="horse" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.HorseSettings">
|
||||||
<Parameters>
|
<Parameters>
|
||||||
<Argument Name="LEGS" Position="0" Required="false" Kind="scalar" ClrType="System.Int32">
|
<Argument Name="LEGS" Position="0" Required="false" Kind="scalar" ClrType="System.Int32">
|
||||||
<Description>The number of legs.</Description>
|
<Description>The number of legs.</Description>
|
||||||
@@ -31,6 +31,9 @@
|
|||||||
<Option Short="a" Long="alive,not-dead" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean">
|
<Option Short="a" Long="alive,not-dead" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean">
|
||||||
<Description>Indicates whether or not the animal is alive.</Description>
|
<Description>Indicates whether or not the animal is alive.</Description>
|
||||||
</Option>
|
</Option>
|
||||||
|
<Option Short="d" Long="day" Value="NULL" Required="false" Kind="scalar" ClrType="System.DayOfWeek" />
|
||||||
|
<Option Short="" Long="directory" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.DirectoryInfo" />
|
||||||
|
<Option Short="" Long="file" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.FileInfo" />
|
||||||
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
</Parameters>
|
</Parameters>
|
||||||
</Command>
|
</Command>
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Model>
|
||||||
|
<!--ANIMAL-->
|
||||||
|
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="LEGS" Position="0" Required="false" Kind="scalar" ClrType="System.Int32">
|
||||||
|
<Description>The number of legs.</Description>
|
||||||
|
<Validators>
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.EvenNumberValidatorAttribute" Message="Animals must have an even number of legs." />
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.PositiveNumberValidatorAttribute" Message="Number of legs must be greater than 0." />
|
||||||
|
</Validators>
|
||||||
|
</Argument>
|
||||||
|
<Option Short="a" Long="alive,not-dead" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean">
|
||||||
|
<Description>Indicates whether or not the animal is alive.</Description>
|
||||||
|
</Option>
|
||||||
|
</Parameters>
|
||||||
|
<!--MAMMAL-->
|
||||||
|
<Command Name="mammal" IsBranch="true" Settings="Spectre.Console.Tests.Data.MammalSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
<!--__DEFAULT_COMMAND-->
|
||||||
|
<Command Name="__default_command" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.HorseSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Option Short="d" Long="day" Value="NULL" Required="false" Kind="scalar" ClrType="System.DayOfWeek" />
|
||||||
|
<Option Short="" Long="directory" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.DirectoryInfo" />
|
||||||
|
<Option Short="" Long="file" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.FileInfo" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
</Command>
|
||||||
|
</Command>
|
||||||
|
</Model>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Model>
|
||||||
|
<!--ANIMAL-->
|
||||||
|
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="LEGS" Position="0" Required="false" Kind="scalar" ClrType="System.Int32">
|
||||||
|
<Description>The number of legs.</Description>
|
||||||
|
<Validators>
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.EvenNumberValidatorAttribute" Message="Animals must have an even number of legs." />
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.PositiveNumberValidatorAttribute" Message="Number of legs must be greater than 0." />
|
||||||
|
</Validators>
|
||||||
|
</Argument>
|
||||||
|
<Option Short="a" Long="alive,not-dead" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean">
|
||||||
|
<Description>Indicates whether or not the animal is alive.</Description>
|
||||||
|
</Option>
|
||||||
|
</Parameters>
|
||||||
|
<!--DOG-->
|
||||||
|
<Command Name="dog" IsBranch="false" ClrType="Spectre.Console.Tests.Data.DogCommand" Settings="Spectre.Console.Tests.Data.DogSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="AGE" Position="0" Required="true" Kind="scalar" ClrType="System.Int32" />
|
||||||
|
<Option Short="g" Long="good-boy" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean" />
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
<!--__DEFAULT_COMMAND-->
|
||||||
|
<Command Name="__default_command" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.HorseSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Option Short="d" Long="day" Value="NULL" Required="false" Kind="scalar" ClrType="System.DayOfWeek" />
|
||||||
|
<Option Short="" Long="directory" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.DirectoryInfo" />
|
||||||
|
<Option Short="" Long="file" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.FileInfo" />
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
</Command>
|
||||||
|
</Model>
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Model>
|
||||||
|
<!--DEFAULT COMMAND-->
|
||||||
|
<Command Name="__default_command" IsBranch="false" IsDefault="true" ClrType="Spectre.Console.Tests.Data.EmptyCommand" Settings="Spectre.Console.Cli.EmptyCommandSettings" />
|
||||||
|
<!--ANIMAL-->
|
||||||
|
<Command Name="animal" IsBranch="true" Settings="Spectre.Console.Tests.Data.AnimalSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="LEGS" Position="0" Required="false" Kind="scalar" ClrType="System.Int32">
|
||||||
|
<Description>The number of legs.</Description>
|
||||||
|
<Validators>
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.EvenNumberValidatorAttribute" Message="Animals must have an even number of legs." />
|
||||||
|
<Validator ClrType="Spectre.Console.Tests.Data.PositiveNumberValidatorAttribute" Message="Number of legs must be greater than 0." />
|
||||||
|
</Validators>
|
||||||
|
</Argument>
|
||||||
|
<Option Short="a" Long="alive,not-dead" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean">
|
||||||
|
<Description>Indicates whether or not the animal is alive.</Description>
|
||||||
|
</Option>
|
||||||
|
</Parameters>
|
||||||
|
<!--DOG-->
|
||||||
|
<Command Name="dog" IsBranch="false" ClrType="Spectre.Console.Tests.Data.DogCommand" Settings="Spectre.Console.Tests.Data.DogSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Argument Name="AGE" Position="0" Required="true" Kind="scalar" ClrType="System.Int32" />
|
||||||
|
<Option Short="g" Long="good-boy" Value="NULL" Required="false" Kind="flag" ClrType="System.Boolean" />
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
<!--__DEFAULT_COMMAND-->
|
||||||
|
<Command Name="__default_command" IsBranch="false" ClrType="Spectre.Console.Tests.Data.HorseCommand" Settings="Spectre.Console.Tests.Data.HorseSettings">
|
||||||
|
<Parameters>
|
||||||
|
<Option Short="d" Long="day" Value="NULL" Required="false" Kind="scalar" ClrType="System.DayOfWeek" />
|
||||||
|
<Option Short="" Long="directory" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.DirectoryInfo" />
|
||||||
|
<Option Short="" Long="file" Value="NULL" Required="false" Kind="scalar" ClrType="System.IO.FileInfo" />
|
||||||
|
<Option Short="n,p" Long="name,pet-name" Value="VALUE" Required="false" Kind="scalar" ClrType="System.String" />
|
||||||
|
</Parameters>
|
||||||
|
</Command>
|
||||||
|
</Command>
|
||||||
|
</Model>
|
||||||
351
test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Branches.cs
Normal file
351
test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Branches.cs
Normal file
@@ -0,0 +1,351 @@
|
|||||||
|
namespace Spectre.Console.Tests.Unit.Cli;
|
||||||
|
|
||||||
|
public sealed partial class CommandAppTests
|
||||||
|
{
|
||||||
|
public sealed class Branches
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_When_No_Default_Command_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal => { });
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() =>
|
||||||
|
{
|
||||||
|
app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
|
||||||
|
{
|
||||||
|
ex.Message.ShouldBe("The branch 'animal' does not define any commands.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1515:SingleLineCommentMustBePrecededByBlankLine", Justification = "Helps to illustrate the expected behaviour of this unit test.")]
|
||||||
|
[SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1005:SingleLineCommentsMustBeginWithSingleSpace", Justification = "Helps to illustrate the expected behaviour of this unit test.")]
|
||||||
|
[Fact]
|
||||||
|
public void Should_Be_Unable_To_Parse_Default_Command_Arguments_Relaxed_Parsing()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
// The CommandTreeParser should be unable to determine which command line
|
||||||
|
// arguments belong to the branch and which belong to the branch's
|
||||||
|
// default command (once inserted).
|
||||||
|
"animal", "4", "--name", "Kitty",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>().And(cat =>
|
||||||
|
{
|
||||||
|
cat.Legs.ShouldBe(4);
|
||||||
|
//cat.Name.ShouldBe("Kitty"); //<-- Should normally be correct, but instead name will be added to the remaining arguments (see below).
|
||||||
|
});
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
||||||
|
result.Context.ShouldHaveRemainingArgument("name", values: new[] { "Kitty", });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Be_Unable_To_Parse_Default_Command_Arguments_Strict_Parsing()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.UseStrictParsing();
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() =>
|
||||||
|
{
|
||||||
|
app.Run(new[]
|
||||||
|
{
|
||||||
|
// The CommandTreeParser should be unable to determine which command line
|
||||||
|
// arguments belong to the branch and which belong to the branch's
|
||||||
|
// default command (once inserted).
|
||||||
|
"animal", "4", "--name", "Kitty",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandParseException>().And(ex =>
|
||||||
|
{
|
||||||
|
ex.Message.ShouldBe("Unknown option 'name'.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_On_Branch_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
||||||
|
{
|
||||||
|
mammal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4", "mammal",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_On_Branch_On_Branch_With_Arguments()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
||||||
|
{
|
||||||
|
mammal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4", "mammal", "--name", "Kitty",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>().And(cat =>
|
||||||
|
{
|
||||||
|
cat.Legs.ShouldBe(4);
|
||||||
|
cat.Name.ShouldBe("Kitty");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_Not_The_Named_Command_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
|
||||||
|
animal.SetDefaultCommand<CatCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<CatSettings>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Named_Command_Not_The_Default_Command_On_Branch()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
|
||||||
|
animal.SetDefaultCommand<LionCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4", "dog", "12", "--good-boy", "--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Legs.ShouldBe(4);
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Allow_Multiple_Branches_Multiple_Commands()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
||||||
|
{
|
||||||
|
mammal.AddCommand<DogCommand>("dog");
|
||||||
|
mammal.AddCommand<CatCommand>("cat");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "--alive", "mammal", "--name",
|
||||||
|
"Rufus", "dog", "12", "--good-boy",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
dog.IsAlive.ShouldBe(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Allow_Single_Branch_Multiple_Commands()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
animal.AddCommand<CatCommand>("cat");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "dog", "12", "--good-boy",
|
||||||
|
"--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
dog.IsAlive.ShouldBe(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Allow_Single_Branch_Single_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"animal", "4", "dog", "12", "--good-boy",
|
||||||
|
"--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Legs.ShouldBe(4);
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.IsAlive.ShouldBe(false);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
using Spectre.Console.Cli;
|
|
||||||
|
|
||||||
namespace Spectre.Console.Tests.Unit.Cli;
|
namespace Spectre.Console.Tests.Unit.Cli;
|
||||||
|
|
||||||
public sealed partial class CommandAppTests
|
public sealed partial class CommandAppTests
|
||||||
@@ -41,7 +39,7 @@ public sealed partial class CommandAppTests
|
|||||||
configurator.AddCommand<DogCommand>("dog");
|
configurator.AddCommand<DogCommand>("dog");
|
||||||
configurator.AddCommand<HorseCommand>("horse");
|
configurator.AddCommand<HorseCommand>("horse");
|
||||||
configurator.AddCommand<GiraffeCommand>("giraffe")
|
configurator.AddCommand<GiraffeCommand>("giraffe")
|
||||||
.WithExample(new[] { "giraffe", "123" })
|
.WithExample("giraffe", "123")
|
||||||
.IsHidden();
|
.IsHidden();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -64,7 +62,7 @@ public sealed partial class CommandAppTests
|
|||||||
configurator.AddCommand<DogCommand>("dog");
|
configurator.AddCommand<DogCommand>("dog");
|
||||||
configurator.AddCommand<HorseCommand>("horse");
|
configurator.AddCommand<HorseCommand>("horse");
|
||||||
configurator.AddCommand<GiraffeCommand>("giraffe")
|
configurator.AddCommand<GiraffeCommand>("giraffe")
|
||||||
.WithExample(new[] { "giraffe", "123" })
|
.WithExample("giraffe", "123")
|
||||||
.IsHidden();
|
.IsHidden();
|
||||||
configurator.TrimTrailingPeriods(false);
|
configurator.TrimTrailingPeriods(false);
|
||||||
});
|
});
|
||||||
@@ -232,8 +230,8 @@ public sealed partial class CommandAppTests
|
|||||||
fixture.Configure(configurator =>
|
fixture.Configure(configurator =>
|
||||||
{
|
{
|
||||||
configurator.SetApplicationName("myapp");
|
configurator.SetApplicationName("myapp");
|
||||||
configurator.AddExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
|
configurator.AddExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||||
configurator.AddExample(new[] { "horse", "--name", "Brutus" });
|
configurator.AddExample("horse", "--name", "Brutus");
|
||||||
configurator.AddCommand<DogCommand>("dog");
|
configurator.AddCommand<DogCommand>("dog");
|
||||||
configurator.AddCommand<HorseCommand>("horse");
|
configurator.AddCommand<HorseCommand>("horse");
|
||||||
});
|
});
|
||||||
@@ -255,9 +253,9 @@ public sealed partial class CommandAppTests
|
|||||||
{
|
{
|
||||||
configurator.SetApplicationName("myapp");
|
configurator.SetApplicationName("myapp");
|
||||||
configurator.AddCommand<DogCommand>("dog")
|
configurator.AddCommand<DogCommand>("dog")
|
||||||
.WithExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
|
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||||
configurator.AddCommand<HorseCommand>("horse")
|
configurator.AddCommand<HorseCommand>("horse")
|
||||||
.WithExample(new[] { "horse", "--name", "Brutus" });
|
.WithExample("horse", "--name", "Brutus");
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
@@ -280,9 +278,9 @@ public sealed partial class CommandAppTests
|
|||||||
{
|
{
|
||||||
animal.SetDescription("The animal command.");
|
animal.SetDescription("The animal command.");
|
||||||
animal.AddCommand<DogCommand>("dog")
|
animal.AddCommand<DogCommand>("dog")
|
||||||
.WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
|
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||||
animal.AddCommand<HorseCommand>("horse")
|
animal.AddCommand<HorseCommand>("horse")
|
||||||
.WithExample(new[] { "animal", "horse", "--name", "Brutus" });
|
.WithExample("animal", "horse", "--name", "Brutus");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -308,9 +306,9 @@ public sealed partial class CommandAppTests
|
|||||||
animal.AddExample(new[] { "animal", "--help" });
|
animal.AddExample(new[] { "animal", "--help" });
|
||||||
|
|
||||||
animal.AddCommand<DogCommand>("dog")
|
animal.AddCommand<DogCommand>("dog")
|
||||||
.WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" });
|
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
|
||||||
animal.AddCommand<HorseCommand>("horse")
|
animal.AddCommand<HorseCommand>("horse")
|
||||||
.WithExample(new[] { "animal", "horse", "--name", "Brutus" });
|
.WithExample("animal", "horse", "--name", "Brutus");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -331,7 +329,7 @@ public sealed partial class CommandAppTests
|
|||||||
fixture.Configure(configurator =>
|
fixture.Configure(configurator =>
|
||||||
{
|
{
|
||||||
configurator.SetApplicationName("myapp");
|
configurator.SetApplicationName("myapp");
|
||||||
configurator.AddExample(new[] { "12", "-c", "3" });
|
configurator.AddExample("12", "-c", "3");
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
|
|||||||
@@ -3,7 +3,167 @@ namespace Spectre.Console.Tests.Unit.Cli;
|
|||||||
public sealed partial class CommandAppTests
|
public sealed partial class CommandAppTests
|
||||||
{
|
{
|
||||||
public sealed class Remaining
|
public sealed class Remaining
|
||||||
{
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("-a")]
|
||||||
|
[InlineData("--alive")]
|
||||||
|
public void Should_Not_Add_Known_Flags_To_Remaining_Arguments_RelaxedParsing(string knownFlag)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
knownFlag,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.IsAlive.ShouldBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(0);
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("-r")]
|
||||||
|
[InlineData("--romeo")]
|
||||||
|
public void Should_Add_Unknown_Flags_To_Remaining_Arguments_RelaxedParsing(string unknownFlag)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
unknownFlag,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
||||||
|
result.Context.ShouldHaveRemainingArgument(unknownFlag.TrimStart('-'), values: new[] { (string)null });
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Add_Unknown_Flags_When_Grouped_To_Remaining_Arguments_RelaxedParsing()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
"-agr",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
||||||
|
result.Context.ShouldHaveRemainingArgument("r", values: new[] { (string)null });
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("-a")]
|
||||||
|
[InlineData("--alive")]
|
||||||
|
public void Should_Not_Add_Known_Flags_To_Remaining_Arguments_StrictParsing(string knownFlag)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.UseStrictParsing();
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
knownFlag,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(0);
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("-r")]
|
||||||
|
[InlineData("--romeo")]
|
||||||
|
public void Should_Not_Add_Unknown_Flags_To_Remaining_Arguments_StrictParsing(string unknownFlag)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.UseStrictParsing();
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
unknownFlag,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandParseException>().And(ex =>
|
||||||
|
{
|
||||||
|
ex.Message.ShouldBe($"Unknown option '{unknownFlag.TrimStart('-')}'.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Not_Add_Unknown_Flags_When_Grouped_To_Remaining_Arguments_StrictParsing()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.UseStrictParsing();
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
"-agr",
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandParseException>().And(ex =>
|
||||||
|
{
|
||||||
|
ex.Message.ShouldBe($"Unknown option 'r'.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Register_Remaining_Parsed_Arguments_With_Context()
|
public void Should_Register_Remaining_Parsed_Arguments_With_Context()
|
||||||
{
|
{
|
||||||
@@ -94,6 +254,35 @@ public sealed partial class CommandAppTests
|
|||||||
result.Context.Remaining.Raw[0].ShouldBe("/c");
|
result.Context.Remaining.Raw[0].ShouldBe("/c");
|
||||||
result.Context.Remaining.Raw[1].ShouldBe("\"set && pause\"");
|
result.Context.Remaining.Raw[1].ShouldBe("\"set && pause\"");
|
||||||
result.Context.Remaining.Raw[2].ShouldBe("Name=\" -Rufus --' ");
|
result.Context.Remaining.Raw[2].ShouldBe("Name=\" -Rufus --' ");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(true)]
|
||||||
|
[InlineData(false)]
|
||||||
|
public void Should_Convert_Flags_To_Remaining_Arguments_If_Cannot_Be_Assigned(bool useStrictParsing)
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.Settings.ConvertFlagsToRemainingArguments = true;
|
||||||
|
config.Settings.StrictParsing = useStrictParsing;
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("dog");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"dog", "12", "4",
|
||||||
|
"--good-boy=Please be good Rufus!",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
||||||
|
result.Context.ShouldHaveRemainingArgument("good-boy", values: new[] { "Please be good Rufus!" });
|
||||||
|
|
||||||
|
result.Context.Remaining.Raw.Count.ShouldBe(0); // nb. there are no "raw" remaining arguments on the command line
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Spectre.Console.Tests.Unit.Cli;
|
namespace Spectre.Console.Tests.Unit.Cli;
|
||||||
|
|
||||||
public sealed partial class CommandAppTests
|
public sealed partial class CommandAppTests
|
||||||
@@ -29,5 +31,74 @@ public sealed partial class CommandAppTests
|
|||||||
cat.Agility.ShouldBe(6);
|
cat.Agility.ShouldBe(6);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Convert_Enum_Ignoring_Case()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.AddCommand<HorseCommand>("horse");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[] { "horse", "--day", "friday" });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<HorseSettings>().And(horse =>
|
||||||
|
{
|
||||||
|
horse.Day.ShouldBe(DayOfWeek.Friday);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_List_All_Valid_Enum_Values_On_Conversion_Error()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.AddCommand<HorseCommand>("horse");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[] { "horse", "--day", "heimday" });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(-1);
|
||||||
|
result.Output.ShouldStartWith("Error");
|
||||||
|
result.Output.ShouldContain("heimday");
|
||||||
|
result.Output.ShouldContain(nameof(DayOfWeek.Sunday));
|
||||||
|
result.Output.ShouldContain(nameof(DayOfWeek.Monday));
|
||||||
|
result.Output.ShouldContain(nameof(DayOfWeek.Tuesday));
|
||||||
|
result.Output.ShouldContain(nameof(DayOfWeek.Wednesday));
|
||||||
|
result.Output.ShouldContain(nameof(DayOfWeek.Thursday));
|
||||||
|
result.Output.ShouldContain(nameof(DayOfWeek.Friday));
|
||||||
|
result.Output.ShouldContain(nameof(DayOfWeek.Saturday));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Convert_FileInfo_And_DirectoryInfo()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.AddCommand<HorseCommand>("horse");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[] { "horse", "--file", "ntp.conf", "--directory", "etc" });
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<HorseSettings>().And(horse =>
|
||||||
|
{
|
||||||
|
horse.File.Name.ShouldBe("ntp.conf");
|
||||||
|
horse.Directory.Name.ShouldBe("etc");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,6 +130,77 @@ public sealed partial class CommandAppTests
|
|||||||
return Verifier.Verify(result.Output);
|
return Verifier.Verify(result.Output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Expectation("Test_7")]
|
||||||
|
public Task Should_Dump_Correct_Model_For_Model_With_Single_Branch_Single_Branch_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new CommandAppTester();
|
||||||
|
fixture.Configure(configuration =>
|
||||||
|
{
|
||||||
|
configuration.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
||||||
|
{
|
||||||
|
mammal.SetDefaultCommand<HorseCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = fixture.Run(Constants.XmlDocCommand);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(result.Output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Expectation("Test_8")]
|
||||||
|
public Task Should_Dump_Correct_Model_For_Model_With_Single_Branch_Single_Command_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new CommandAppTester();
|
||||||
|
fixture.Configure(configuration =>
|
||||||
|
{
|
||||||
|
configuration.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
|
||||||
|
animal.SetDefaultCommand<HorseCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = fixture.Run(Constants.XmlDocCommand);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(result.Output);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Expectation("Test_9")]
|
||||||
|
public Task Should_Dump_Correct_Model_For_Model_With_Default_Command_Single_Branch_Single_Command_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var fixture = new CommandAppTester();
|
||||||
|
fixture.SetDefaultCommand<EmptyCommand>();
|
||||||
|
fixture.Configure(configuration =>
|
||||||
|
{
|
||||||
|
configuration.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
|
||||||
|
animal.SetDefaultCommand<HorseCommand>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = fixture.Run(Constants.XmlDocCommand);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(result.Output);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Expectation("Hidden_Command_Options")]
|
[Expectation("Hidden_Command_Options")]
|
||||||
public Task Should_Not_Dump_Hidden_Options_On_A_Command()
|
public Task Should_Not_Dump_Hidden_Options_On_A_Command()
|
||||||
|
|||||||
@@ -4,42 +4,6 @@ public sealed partial class CommandAppTests
|
|||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Pass_Case_1()
|
public void Should_Pass_Case_1()
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var app = new CommandAppTester();
|
|
||||||
app.Configure(config =>
|
|
||||||
{
|
|
||||||
config.PropagateExceptions();
|
|
||||||
config.AddBranch<AnimalSettings>("animal", animal =>
|
|
||||||
{
|
|
||||||
animal.AddBranch<MammalSettings>("mammal", mammal =>
|
|
||||||
{
|
|
||||||
mammal.AddCommand<DogCommand>("dog");
|
|
||||||
mammal.AddCommand<HorseCommand>("horse");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// When
|
|
||||||
var result = app.Run(new[]
|
|
||||||
{
|
|
||||||
"animal", "--alive", "mammal", "--name",
|
|
||||||
"Rufus", "dog", "12", "--good-boy",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.ExitCode.ShouldBe(0);
|
|
||||||
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
|
||||||
{
|
|
||||||
dog.Age.ShouldBe(12);
|
|
||||||
dog.GoodBoy.ShouldBe(true);
|
|
||||||
dog.Name.ShouldBe("Rufus");
|
|
||||||
dog.IsAlive.ShouldBe(true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Pass_Case_2()
|
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
@@ -52,8 +16,8 @@ public sealed partial class CommandAppTests
|
|||||||
// When
|
// When
|
||||||
var result = app.Run(new[]
|
var result = app.Run(new[]
|
||||||
{
|
{
|
||||||
"dog", "12", "4", "--good-boy",
|
"dog", "12", "4", "--good-boy",
|
||||||
"--name", "Rufus", "--alive",
|
"--name", "Rufus", "--alive",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
@@ -69,7 +33,7 @@ public sealed partial class CommandAppTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Pass_Case_3()
|
public void Should_Pass_Case_2()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
@@ -132,8 +96,8 @@ public sealed partial class CommandAppTests
|
|||||||
dog.IsAlive.ShouldBe(false);
|
dog.IsAlive.ShouldBe(false);
|
||||||
dog.Name.ShouldBe("Rufus");
|
dog.Name.ShouldBe("Rufus");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Pass_Case_5()
|
public void Should_Pass_Case_5()
|
||||||
{
|
{
|
||||||
@@ -165,7 +129,7 @@ public sealed partial class CommandAppTests
|
|||||||
dog.IsAlive.ShouldBe(true);
|
dog.IsAlive.ShouldBe(true);
|
||||||
dog.Name.ShouldBe("Rufus");
|
dog.Name.ShouldBe("Rufus");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Pass_Case_6()
|
public void Should_Pass_Case_6()
|
||||||
@@ -197,7 +161,7 @@ public sealed partial class CommandAppTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Pass_Case_7()
|
public void Should_Pass_Case_3()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
@@ -237,10 +201,10 @@ public sealed partial class CommandAppTests
|
|||||||
var result = app.Run(new[]
|
var result = app.Run(new[]
|
||||||
{
|
{
|
||||||
"dog", "12", "4",
|
"dog", "12", "4",
|
||||||
"--name=\" -Rufus --' ",
|
"--name=\" -Rufus --' ",
|
||||||
"--",
|
"--",
|
||||||
"--order-by", "\"-size\"",
|
"--order-by", "\"-size\"",
|
||||||
"--order-by", " ",
|
"--order-by", " ",
|
||||||
"--order-by", string.Empty,
|
"--order-by", string.Empty,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -249,8 +213,8 @@ public sealed partial class CommandAppTests
|
|||||||
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
{
|
{
|
||||||
dog.Name.ShouldBe("\" -Rufus --' ");
|
dog.Name.ShouldBe("\" -Rufus --' ");
|
||||||
});
|
});
|
||||||
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
result.Context.Remaining.Parsed.Count.ShouldBe(1);
|
||||||
result.Context.ShouldHaveRemainingArgument("order-by", values: new[] { "\"-size\"", " ", string.Empty });
|
result.Context.ShouldHaveRemainingArgument("order-by", values: new[] { "\"-size\"", " ", string.Empty });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,6 +244,65 @@ public sealed partial class CommandAppTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Be_Able_To_Use_Branch_Alias()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
animal.AddCommand<HorseCommand>("horse");
|
||||||
|
}).WithAlias("a");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"a", "dog", "12", "--good-boy",
|
||||||
|
"--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
dog.IsAlive.ShouldBe(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Throw_If_Branch_Alias_Conflicts_With_Another_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandApp();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<DogCommand>("a");
|
||||||
|
config.AddBranch<AnimalSettings>("animal", animal =>
|
||||||
|
{
|
||||||
|
animal.AddCommand<DogCommand>("dog");
|
||||||
|
animal.AddCommand<HorseCommand>("horse");
|
||||||
|
}).WithAlias("a");
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = Record.Exception(() => app.Run(new[] { "a", "0", "12" }));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ShouldBeOfType<CommandConfigurationException>().And(ex =>
|
||||||
|
{
|
||||||
|
ex.Message.ShouldBe("The alias 'a' for 'animal' conflicts with another command.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Assign_Default_Value_To_Optional_Argument()
|
public void Should_Assign_Default_Value_To_Optional_Argument()
|
||||||
{
|
{
|
||||||
@@ -373,6 +396,50 @@ public sealed partial class CommandAppTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Assign_Array_Default_Value_To_Command_Option()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.SetDefaultCommand<GenericCommand<OptionWithArrayOfEnumDefaultValueSettings>>();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(Array.Empty<string>());
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<OptionWithArrayOfEnumDefaultValueSettings>().And(settings =>
|
||||||
|
{
|
||||||
|
settings.Days.ShouldBe(new[] { DayOfWeek.Sunday, DayOfWeek.Saturday });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Assign_Array_Default_Value_To_Command_Option_Using_Converter_If_Necessary()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.SetDefaultCommand<GenericCommand<OptionWithArrayOfStringDefaultValueAndTypeConverterSettings>>();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
});
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(Array.Empty<string>());
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<OptionWithArrayOfStringDefaultValueAndTypeConverterSettings>().And(settings =>
|
||||||
|
{
|
||||||
|
settings.Numbers.ShouldBe(new[] { 2, 3 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Throw_If_Required_Argument_Have_Default_Value()
|
public void Should_Throw_If_Required_Argument_Have_Default_Value()
|
||||||
{
|
{
|
||||||
@@ -554,181 +621,181 @@ public sealed partial class CommandAppTests
|
|||||||
{
|
{
|
||||||
dog.IsAlive.ShouldBe(expected);
|
dog.IsAlive.ShouldBe(expected);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Set_Short_Option_Before_Argument()
|
public void Should_Set_Short_Option_Before_Argument()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
config.PropagateExceptions();
|
config.PropagateExceptions();
|
||||||
config.AddCommand<DogCommand>("dog");
|
config.AddCommand<DogCommand>("dog");
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
var result = app.Run(new[] { "dog", "-a", "-n=Rufus", "4", "12", });
|
var result = app.Run(new[] { "dog", "-a", "-n=Rufus", "4", "12", });
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ExitCode.ShouldBe(0);
|
result.ExitCode.ShouldBe(0);
|
||||||
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
||||||
{
|
{
|
||||||
settings.IsAlive.ShouldBeTrue();
|
settings.IsAlive.ShouldBeTrue();
|
||||||
settings.Name.ShouldBe("Rufus");
|
settings.Name.ShouldBe("Rufus");
|
||||||
settings.Legs.ShouldBe(4);
|
settings.Legs.ShouldBe(4);
|
||||||
settings.Age.ShouldBe(12);
|
settings.Age.ShouldBe(12);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("true", true)]
|
[InlineData("true", true)]
|
||||||
[InlineData("True", true)]
|
[InlineData("True", true)]
|
||||||
[InlineData("false", false)]
|
[InlineData("false", false)]
|
||||||
[InlineData("False", false)]
|
[InlineData("False", false)]
|
||||||
public void Should_Set_Short_Option_With_Explicit_Boolan_Flag_Before_Argument(string value, bool expected)
|
public void Should_Set_Short_Option_With_Explicit_Boolan_Flag_Before_Argument(string value, bool expected)
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
config.PropagateExceptions();
|
config.PropagateExceptions();
|
||||||
config.AddCommand<DogCommand>("dog");
|
config.AddCommand<DogCommand>("dog");
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
var result = app.Run(new[] { "dog", "-a", value, "4", "12", });
|
var result = app.Run(new[] { "dog", "-a", value, "4", "12", });
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ExitCode.ShouldBe(0);
|
result.ExitCode.ShouldBe(0);
|
||||||
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
||||||
{
|
{
|
||||||
settings.IsAlive.ShouldBe(expected);
|
settings.IsAlive.ShouldBe(expected);
|
||||||
settings.Legs.ShouldBe(4);
|
settings.Legs.ShouldBe(4);
|
||||||
settings.Age.ShouldBe(12);
|
settings.Age.ShouldBe(12);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Set_Long_Option_Before_Argument()
|
public void Should_Set_Long_Option_Before_Argument()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
config.PropagateExceptions();
|
config.PropagateExceptions();
|
||||||
config.AddCommand<DogCommand>("dog");
|
config.AddCommand<DogCommand>("dog");
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
var result = app.Run(new[] { "dog", "--alive", "--name=Rufus", "4", "12" });
|
var result = app.Run(new[] { "dog", "--alive", "--name=Rufus", "4", "12" });
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ExitCode.ShouldBe(0);
|
result.ExitCode.ShouldBe(0);
|
||||||
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
||||||
{
|
{
|
||||||
settings.IsAlive.ShouldBeTrue();
|
settings.IsAlive.ShouldBeTrue();
|
||||||
settings.Name.ShouldBe("Rufus");
|
settings.Name.ShouldBe("Rufus");
|
||||||
settings.Legs.ShouldBe(4);
|
settings.Legs.ShouldBe(4);
|
||||||
settings.Age.ShouldBe(12);
|
settings.Age.ShouldBe(12);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("true", true)]
|
[InlineData("true", true)]
|
||||||
[InlineData("True", true)]
|
[InlineData("True", true)]
|
||||||
[InlineData("false", false)]
|
[InlineData("false", false)]
|
||||||
[InlineData("False", false)]
|
[InlineData("False", false)]
|
||||||
public void Should_Set_Long_Option_With_Explicit_Boolan_Flag_Before_Argument(string value, bool expected)
|
public void Should_Set_Long_Option_With_Explicit_Boolan_Flag_Before_Argument(string value, bool expected)
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
config.PropagateExceptions();
|
config.PropagateExceptions();
|
||||||
config.AddCommand<DogCommand>("dog");
|
config.AddCommand<DogCommand>("dog");
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
var result = app.Run(new[] { "dog", "--alive", value, "4", "12", });
|
var result = app.Run(new[] { "dog", "--alive", value, "4", "12", });
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ExitCode.ShouldBe(0);
|
result.ExitCode.ShouldBe(0);
|
||||||
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
||||||
{
|
{
|
||||||
settings.IsAlive.ShouldBe(expected);
|
settings.IsAlive.ShouldBe(expected);
|
||||||
settings.Legs.ShouldBe(4);
|
settings.Legs.ShouldBe(4);
|
||||||
settings.Age.ShouldBe(12);
|
settings.Age.ShouldBe(12);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
|
||||||
// Long options
|
// Long options
|
||||||
[InlineData("dog --alive 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
[InlineData("dog --alive 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
||||||
[InlineData("dog --alive=true 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
[InlineData("dog --alive=true 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
||||||
[InlineData("dog --alive:true 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
[InlineData("dog --alive:true 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
||||||
[InlineData("dog --alive --good-boy 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --alive --good-boy 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --alive=true --good-boy=true 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --alive=true --good-boy=true 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --alive:true --good-boy:true 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --alive:true --good-boy:true 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --alive --good-boy --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --alive --good-boy --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --alive=true --good-boy=true --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --alive=true --good-boy=true --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --alive:true --good-boy:true --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --alive:true --good-boy:true --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
||||||
|
|
||||||
// Short options
|
// Short options
|
||||||
[InlineData("dog -a 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
[InlineData("dog -a 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
||||||
[InlineData("dog -a=true 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
[InlineData("dog -a=true 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
||||||
[InlineData("dog -a:true 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
[InlineData("dog -a:true 4 12 --name Rufus", 4, 12, false, true, "Rufus")]
|
||||||
[InlineData("dog -a --good-boy 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
[InlineData("dog -a --good-boy 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog -a=true -g=true 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
[InlineData("dog -a=true -g=true 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog -a:true -g:true 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
[InlineData("dog -a:true -g:true 4 12 --name Rufus", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog -a -g --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog -a -g --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog -a=true -g=true --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog -a=true -g=true --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog -a:true -g:true --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog -a:true -g:true --name Rufus 4 12", 4, 12, true, true, "Rufus")]
|
||||||
|
|
||||||
// Switch around ordering of the options
|
// Switch around ordering of the options
|
||||||
[InlineData("dog --good-boy:true --name Rufus --alive:true 4 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --good-boy:true --name Rufus --alive:true 4 12", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --name Rufus --alive:true --good-boy:true 4 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --name Rufus --alive:true --good-boy:true 4 12", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --name Rufus --good-boy:true --alive:true 4 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --name Rufus --good-boy:true --alive:true 4 12", 4, 12, true, true, "Rufus")]
|
||||||
|
|
||||||
// Inject the command arguments in between the options
|
// Inject the command arguments in between the options
|
||||||
[InlineData("dog 4 12 --good-boy:true --name Rufus --alive:true", 4, 12, true, true, "Rufus")]
|
[InlineData("dog 4 12 --good-boy:true --name Rufus --alive:true", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog 4 --good-boy:true 12 --name Rufus --alive:true", 4, 12, true, true, "Rufus")]
|
[InlineData("dog 4 --good-boy:true 12 --name Rufus --alive:true", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --good-boy:true 4 12 --name Rufus --alive:true", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --good-boy:true 4 12 --name Rufus --alive:true", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --good-boy:true 4 --name Rufus 12 --alive:true", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --good-boy:true 4 --name Rufus 12 --alive:true", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --name Rufus --alive:true 4 12 --good-boy:true", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --name Rufus --alive:true 4 12 --good-boy:true", 4, 12, true, true, "Rufus")]
|
||||||
[InlineData("dog --name Rufus --alive:true 4 --good-boy:true 12", 4, 12, true, true, "Rufus")]
|
[InlineData("dog --name Rufus --alive:true 4 --good-boy:true 12", 4, 12, true, true, "Rufus")]
|
||||||
|
|
||||||
// Inject the command arguments in between the options (all flag values set to false)
|
// Inject the command arguments in between the options (all flag values set to false)
|
||||||
[InlineData("dog 4 12 --good-boy:false --name Rufus --alive:false", 4, 12, false, false, "Rufus")]
|
[InlineData("dog 4 12 --good-boy:false --name Rufus --alive:false", 4, 12, false, false, "Rufus")]
|
||||||
[InlineData("dog 4 --good-boy:false 12 --name Rufus --alive:false", 4, 12, false, false, "Rufus")]
|
[InlineData("dog 4 --good-boy:false 12 --name Rufus --alive:false", 4, 12, false, false, "Rufus")]
|
||||||
[InlineData("dog --good-boy:false 4 12 --name Rufus --alive:false", 4, 12, false, false, "Rufus")]
|
[InlineData("dog --good-boy:false 4 12 --name Rufus --alive:false", 4, 12, false, false, "Rufus")]
|
||||||
[InlineData("dog --good-boy:false 4 --name Rufus 12 --alive:false", 4, 12, false, false, "Rufus")]
|
[InlineData("dog --good-boy:false 4 --name Rufus 12 --alive:false", 4, 12, false, false, "Rufus")]
|
||||||
[InlineData("dog --name Rufus --alive:false 4 12 --good-boy:false", 4, 12, false, false, "Rufus")]
|
[InlineData("dog --name Rufus --alive:false 4 12 --good-boy:false", 4, 12, false, false, "Rufus")]
|
||||||
[InlineData("dog --name Rufus --alive:false 4 --good-boy:false 12", 4, 12, false, false, "Rufus")]
|
[InlineData("dog --name Rufus --alive:false 4 --good-boy:false 12", 4, 12, false, false, "Rufus")]
|
||||||
public void Should_Set_Option_Before_Argument(string arguments, int legs, int age, bool goodBoy, bool isAlive, string name)
|
public void Should_Set_Option_Before_Argument(string arguments, int legs, int age, bool goodBoy, bool isAlive, string name)
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
config.PropagateExceptions();
|
config.PropagateExceptions();
|
||||||
config.AddCommand<DogCommand>("dog");
|
config.AddCommand<DogCommand>("dog");
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
var result = app.Run(arguments.Split(' '));
|
var result = app.Run(arguments.Split(' '));
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
result.ExitCode.ShouldBe(0);
|
result.ExitCode.ShouldBe(0);
|
||||||
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
result.Settings.ShouldBeOfType<DogSettings>().And(settings =>
|
||||||
{
|
{
|
||||||
settings.Legs.ShouldBe(legs);
|
settings.Legs.ShouldBe(legs);
|
||||||
settings.Age.ShouldBe(age);
|
settings.Age.ShouldBe(age);
|
||||||
settings.GoodBoy.ShouldBe(goodBoy);
|
settings.GoodBoy.ShouldBe(goodBoy);
|
||||||
settings.IsAlive.ShouldBe(isAlive);
|
settings.IsAlive.ShouldBe(isAlive);
|
||||||
settings.Name.ShouldBe(name);
|
settings.Name.ShouldBe(name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -846,7 +913,7 @@ public sealed partial class CommandAppTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Be_Able_To_Set_The_Default_Command()
|
public void Should_Run_The_Default_Command()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var app = new CommandAppTester();
|
var app = new CommandAppTester();
|
||||||
@@ -869,6 +936,62 @@ public sealed partial class CommandAppTests
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Default_Command_Not_The_Named_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<HorseCommand>("horse");
|
||||||
|
});
|
||||||
|
app.SetDefaultCommand<DogCommand>();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"4", "12", "--good-boy", "--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Legs.ShouldBe(4);
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Run_The_Named_Command_Not_The_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.Configure(config =>
|
||||||
|
{
|
||||||
|
config.PropagateExceptions();
|
||||||
|
config.AddCommand<HorseCommand>("horse");
|
||||||
|
});
|
||||||
|
app.SetDefaultCommand<DogCommand>();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"horse", "4", "--name", "Arkle",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<HorseSettings>().And(horse =>
|
||||||
|
{
|
||||||
|
horse.Legs.ShouldBe(4);
|
||||||
|
horse.Name.ShouldBe("Arkle");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Set_Command_Name_In_Context()
|
public void Should_Set_Command_Name_In_Context()
|
||||||
{
|
{
|
||||||
@@ -917,6 +1040,66 @@ public sealed partial class CommandAppTests
|
|||||||
// Then
|
// Then
|
||||||
result.Context.ShouldNotBeNull();
|
result.Context.ShouldNotBeNull();
|
||||||
result.Context.Data.ShouldBe(123);
|
result.Context.Data.ShouldBe(123);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class Default_Command
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Be_Able_To_Set_The_Default_Command()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandAppTester();
|
||||||
|
app.SetDefaultCommand<DogCommand>();
|
||||||
|
|
||||||
|
// When
|
||||||
|
var result = app.Run(new[]
|
||||||
|
{
|
||||||
|
"4", "12", "--good-boy", "--name", "Rufus",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.ExitCode.ShouldBe(0);
|
||||||
|
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
|
||||||
|
{
|
||||||
|
dog.Legs.ShouldBe(4);
|
||||||
|
dog.Age.ShouldBe(12);
|
||||||
|
dog.GoodBoy.ShouldBe(true);
|
||||||
|
dog.Name.ShouldBe("Rufus");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Set_The_Default_Command_Description_Data_CommandApp()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandApp();
|
||||||
|
app.SetDefaultCommand<DogCommand>()
|
||||||
|
.WithDescription("The default command")
|
||||||
|
.WithData(new string[] { "foo", "bar" });
|
||||||
|
|
||||||
|
// When
|
||||||
|
|
||||||
|
// Then
|
||||||
|
app.GetConfigurator().DefaultCommand.ShouldNotBeNull();
|
||||||
|
app.GetConfigurator().DefaultCommand.Description.ShouldBe("The default command");
|
||||||
|
app.GetConfigurator().DefaultCommand.Data.ShouldBe(new string[] { "foo", "bar" });
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Should_Set_The_Default_Command_Description_Data_CommandAppOfT()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var app = new CommandApp<DogCommand>()
|
||||||
|
.WithDescription("The default command")
|
||||||
|
.WithData(new string[] { "foo", "bar" });
|
||||||
|
|
||||||
|
// When
|
||||||
|
|
||||||
|
// Then
|
||||||
|
app.GetConfigurator().DefaultCommand.ShouldNotBeNull();
|
||||||
|
app.GetConfigurator().DefaultCommand.Description.ShouldBe("The default command");
|
||||||
|
app.GetConfigurator().DefaultCommand.Data.ShouldBe(new string[] { "foo", "bar" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Delegate_Commands
|
public sealed class Delegate_Commands
|
||||||
@@ -930,7 +1113,7 @@ public sealed partial class CommandAppTests
|
|||||||
|
|
||||||
var app = new CommandApp();
|
var app = new CommandApp();
|
||||||
app.Configure(config =>
|
app.Configure(config =>
|
||||||
{
|
{
|
||||||
config.PropagateExceptions();
|
config.PropagateExceptions();
|
||||||
config.AddDelegate<DogSettings>(
|
config.AddDelegate<DogSettings>(
|
||||||
"foo", (context, settings) =>
|
"foo", (context, settings) =>
|
||||||
@@ -986,67 +1169,4 @@ public sealed partial class CommandAppTests
|
|||||||
data.ShouldBe(2);
|
data.ShouldBe(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Remaining_Arguments
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void Should_Register_Remaining_Parsed_Arguments_With_Context()
|
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var app = new CommandAppTester();
|
|
||||||
app.Configure(config =>
|
|
||||||
{
|
|
||||||
config.PropagateExceptions();
|
|
||||||
config.AddBranch<AnimalSettings>("animal", animal =>
|
|
||||||
{
|
|
||||||
animal.AddCommand<DogCommand>("dog");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// When
|
|
||||||
var result = app.Run(new[]
|
|
||||||
{
|
|
||||||
"animal", "4", "dog", "12", "--",
|
|
||||||
"--foo", "bar", "--foo", "baz",
|
|
||||||
"-bar", "\"baz\"", "qux",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.Context.Remaining.Parsed.Count.ShouldBe(4);
|
|
||||||
result.Context.ShouldHaveRemainingArgument("foo", values: new[] { "bar", "baz" });
|
|
||||||
result.Context.ShouldHaveRemainingArgument("b", values: new[] { (string)null });
|
|
||||||
result.Context.ShouldHaveRemainingArgument("a", values: new[] { (string)null });
|
|
||||||
result.Context.ShouldHaveRemainingArgument("r", values: new[] { (string)null });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Should_Register_Remaining_Raw_Arguments_With_Context()
|
|
||||||
{
|
|
||||||
// Given
|
|
||||||
var app = new CommandAppTester();
|
|
||||||
app.Configure(config =>
|
|
||||||
{
|
|
||||||
config.PropagateExceptions();
|
|
||||||
config.AddBranch<AnimalSettings>("animal", animal =>
|
|
||||||
{
|
|
||||||
animal.AddCommand<DogCommand>("dog");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// When
|
|
||||||
var result = app.Run(new[]
|
|
||||||
{
|
|
||||||
"animal", "4", "dog", "12", "--",
|
|
||||||
"--foo", "bar", "-bar", "\"baz\"", "qux",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Then
|
|
||||||
result.Context.Remaining.Raw.Count.ShouldBe(5);
|
|
||||||
result.Context.Remaining.Raw[0].ShouldBe("--foo");
|
|
||||||
result.Context.Remaining.Raw[1].ShouldBe("bar");
|
|
||||||
result.Context.Remaining.Raw[2].ShouldBe("-bar");
|
|
||||||
result.Context.Remaining.Raw[3].ShouldBe("\"baz\"");
|
|
||||||
result.Context.Remaining.Raw[4].ShouldBe("qux");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,42 +247,42 @@ public sealed class TextPromptTests
|
|||||||
|
|
||||||
// Then
|
// Then
|
||||||
return Verifier.Verify(console.Output);
|
return Verifier.Verify(console.Output);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Expectation("SecretDefaultValueCustomMask")]
|
[Expectation("SecretDefaultValueCustomMask")]
|
||||||
public Task Should_Choose_Custom_Masked_Default_Value_If_Nothing_Is_Entered_And_Prompt_Is_Secret_And_Mask_Is_Custom()
|
public Task Should_Choose_Custom_Masked_Default_Value_If_Nothing_Is_Entered_And_Prompt_Is_Secret_And_Mask_Is_Custom()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var console = new TestConsole();
|
var console = new TestConsole();
|
||||||
console.Input.PushKey(ConsoleKey.Enter);
|
console.Input.PushKey(ConsoleKey.Enter);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Prompt(
|
console.Prompt(
|
||||||
new TextPrompt<string>("Favorite fruit?")
|
new TextPrompt<string>("Favorite fruit?")
|
||||||
.Secret('-')
|
.Secret('-')
|
||||||
.DefaultValue("Banana"));
|
.DefaultValue("Banana"));
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
return Verifier.Verify(console.Output);
|
return Verifier.Verify(console.Output);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Expectation("SecretDefaultValueNullMask")]
|
[Expectation("SecretDefaultValueNullMask")]
|
||||||
public Task Should_Choose_Empty_Masked_Default_Value_If_Nothing_Is_Entered_And_Prompt_Is_Secret_And_Mask_Is_Null()
|
public Task Should_Choose_Empty_Masked_Default_Value_If_Nothing_Is_Entered_And_Prompt_Is_Secret_And_Mask_Is_Null()
|
||||||
{
|
{
|
||||||
// Given
|
// Given
|
||||||
var console = new TestConsole();
|
var console = new TestConsole();
|
||||||
console.Input.PushKey(ConsoleKey.Enter);
|
console.Input.PushKey(ConsoleKey.Enter);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Prompt(
|
console.Prompt(
|
||||||
new TextPrompt<string>("Favorite fruit?")
|
new TextPrompt<string>("Favorite fruit?")
|
||||||
.Secret(null)
|
.Secret(null)
|
||||||
.DefaultValue("Banana"));
|
.DefaultValue("Banana"));
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
return Verifier.Verify(console.Output);
|
return Verifier.Verify(console.Output);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -337,7 +337,7 @@ public sealed class TextPromptTests
|
|||||||
var prompt = new TextPrompt<string>("Enter Value:")
|
var prompt = new TextPrompt<string>("Enter Value:")
|
||||||
.ShowDefaultValue()
|
.ShowDefaultValue()
|
||||||
.DefaultValue("default")
|
.DefaultValue("default")
|
||||||
.DefaultValueStyle(new Style(foreground: Color.Red));
|
.DefaultValueStyle(Color.Red);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Prompt(prompt);
|
console.Prompt(prompt);
|
||||||
@@ -384,7 +384,7 @@ public sealed class TextPromptTests
|
|||||||
.ShowChoices()
|
.ShowChoices()
|
||||||
.AddChoice("Choice 1")
|
.AddChoice("Choice 1")
|
||||||
.AddChoice("Choice 2")
|
.AddChoice("Choice 2")
|
||||||
.ChoicesStyle(new Style(foreground: Color.Red));
|
.ChoicesStyle(Color.Red);
|
||||||
|
|
||||||
// When
|
// When
|
||||||
console.Prompt(prompt);
|
console.Prompt(prompt);
|
||||||
|
|||||||
@@ -1,7 +1,20 @@
|
|||||||
namespace Spectre.Console.Tests.Unit;
|
namespace Spectre.Console.Tests.Unit;
|
||||||
|
|
||||||
public sealed class StyleTests
|
public sealed class StyleTests
|
||||||
{
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Should_Convert_From_Color_As_Expected()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
Style style;
|
||||||
|
|
||||||
|
// When
|
||||||
|
style = Color.Red;
|
||||||
|
|
||||||
|
// Then
|
||||||
|
style.Foreground.ShouldBe(Color.Red);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Should_Combine_Two_Styles_As_Expected()
|
public void Should_Combine_Two_Styles_As_Expected()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user