fixed line-endings

This commit is contained in:
Nils Andresen 2023-11-27 12:41:08 +01:00 committed by Patrik Svensson
parent 989c0b9904
commit 44300c871f
79 changed files with 2696 additions and 2696 deletions

View File

@ -1,77 +1,77 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Docs.Extensions;
using Docs.Shortcodes;
using Docs.Utilities;
using Microsoft.Extensions.DependencyInjection;
using Statiq.App;
using Statiq.Common;
using Statiq.Core;
using Statiq.Web;
namespace Docs
{
public static class Program
{
public static async Task<int> Main(string[] args) =>
await Bootstrapper.Factory
.CreateWeb(args)
.AddSetting(Keys.Host, "spectreconsole.net")
.AddSetting(Keys.LinksUseHttps, true)
.AddSetting(Constants.EditLink, ConfigureEditLink())
.AddSetting(Constants.SourceFiles, new List<string>
{
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
})
.AddSetting(Constants.ExampleSourceFiles, new List<string>
{
"../../examples/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
}
)
.ConfigureServices(i =>
{
i.AddSingleton(new TypeNameLinks());
})
.ConfigureSite("spectreconsole", "spectre.console", "main")
.AddShortcode("Children", typeof(ChildrenShortcode))
.AddShortcode("ColorTable", typeof(ColorTableShortcode))
.AddShortcode("EmojiTable", typeof(EmojiTableShortcode))
.AddShortcode("Alert", typeof(AlertShortcode))
.AddShortcode("Info", typeof(InfoShortcode))
.AddShortcode("AsciiCast", typeof(AsciiCastShortcode))
.AddShortcode("Example", typeof(ExampleSnippet))
.AddPipelines()
.BuildPipeline(
"Bootstrap",
builder => builder
.WithInputReadFiles("../node_modules/asciinema-player/dist/bundle/asciinema-player.js")
.WithProcessModules(new SetDestination(Config.FromDocument(doc => new NormalizedPath($"./assets/{doc.Source.FileName}")), true))
.WithOutputWriteFiles()
)
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("npm", "install --audit false --fund false --progress false")
{
LogErrors = false
})
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("dotnet", "playwright install chromium"))
.AddProcess(ProcessTiming.BeforeDeployment, _ => new ProcessLauncher("npm", "run build:tailwind")
{
LogErrors = false
})
.RunAsync();
private static Config<string> ConfigureEditLink()
{
return Config.FromDocument((doc, ctx) =>
{
return string.Format("https://github.com/{0}/{1}/edit/{2}/docs/input/{3}",
ctx.GetString(Constants.Site.Owner),
ctx.GetString(Constants.Site.Repository),
ctx.GetString(Constants.Site.Branch),
doc.Source.GetRelativeInputPath());
});
}
}
}
using System.Collections.Generic;
using System.Threading.Tasks;
using Docs.Extensions;
using Docs.Shortcodes;
using Docs.Utilities;
using Microsoft.Extensions.DependencyInjection;
using Statiq.App;
using Statiq.Common;
using Statiq.Core;
using Statiq.Web;
namespace Docs
{
public static class Program
{
public static async Task<int> Main(string[] args) =>
await Bootstrapper.Factory
.CreateWeb(args)
.AddSetting(Keys.Host, "spectreconsole.net")
.AddSetting(Keys.LinksUseHttps, true)
.AddSetting(Constants.EditLink, ConfigureEditLink())
.AddSetting(Constants.SourceFiles, new List<string>
{
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
"../../src/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
})
.AddSetting(Constants.ExampleSourceFiles, new List<string>
{
"../../examples/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
}
)
.ConfigureServices(i =>
{
i.AddSingleton(new TypeNameLinks());
})
.ConfigureSite("spectreconsole", "spectre.console", "main")
.AddShortcode("Children", typeof(ChildrenShortcode))
.AddShortcode("ColorTable", typeof(ColorTableShortcode))
.AddShortcode("EmojiTable", typeof(EmojiTableShortcode))
.AddShortcode("Alert", typeof(AlertShortcode))
.AddShortcode("Info", typeof(InfoShortcode))
.AddShortcode("AsciiCast", typeof(AsciiCastShortcode))
.AddShortcode("Example", typeof(ExampleSnippet))
.AddPipelines()
.BuildPipeline(
"Bootstrap",
builder => builder
.WithInputReadFiles("../node_modules/asciinema-player/dist/bundle/asciinema-player.js")
.WithProcessModules(new SetDestination(Config.FromDocument(doc => new NormalizedPath($"./assets/{doc.Source.FileName}")), true))
.WithOutputWriteFiles()
)
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("npm", "install --audit false --fund false --progress false")
{
LogErrors = false
})
.AddProcess(ProcessTiming.Initialization, _ => new ProcessLauncher("dotnet", "playwright install chromium"))
.AddProcess(ProcessTiming.BeforeDeployment, _ => new ProcessLauncher("npm", "run build:tailwind")
{
LogErrors = false
})
.RunAsync();
private static Config<string> ConfigureEditLink()
{
return Config.FromDocument((doc, ctx) =>
{
return string.Format("https://github.com/{0}/{1}/edit/{2}/docs/input/{3}",
ctx.GetString(Constants.Site.Owner),
ctx.GetString(Constants.Site.Repository),
ctx.GetString(Constants.Site.Branch),
doc.Source.GetRelativeInputPath());
});
}
}
}

View File

@ -4,7 +4,7 @@ Description: "*Spectre.Console* makes it easy to write text with different style
Highlights:
- Bold, Italic, Underline, strikethrough
- Dim, Invert
- Conceal, slowblink, rapidblink
- Conceal, slowblink, rapidblink
- Links
---
@ -46,9 +46,9 @@ Note that what styles that can be used is defined by the system or your terminal
<tr>
<td><code>strikethrough</code></td>
<td>Shows text with a horizontal line through the center</td>
</tr>
<tr>
<td><code>link</link></td>
<td>Creates a clickable link within text</td>
</tr>
<tr>
<td><code>link</link></td>
<td>Creates a clickable link within text</td>
</tr>
</table>

View File

@ -1,47 +1,47 @@
Title: Command Help
Order: 13
Description: "Console applications built with *Spectre.Console.Cli* include automatically generated help command line help."
---
Console applications built with `Spectre.Console.Cli` include automatically generated help which is displayed when `-h` or `--help` has been specified on the command line.
The automatically generated help is derived from the configured commands and their command settings.
The help is also context aware and tailored depending on what has been specified on the command line before it. For example,
1. When `-h` or `--help` appears immediately after the application name (eg. `application.exe --help`), then the help displayed is a high-level summary of the application, including any command line examples and a listing of all possible commands the user can execute.
2. When `-h` or `--help` appears immediately after a command has been specified (eg. `application.exe command --help`), then the help displayed is specific to the command and includes information about command specific switches and any default values.
`HelpProvider` is the `Spectre.Console` class responsible for determining context and preparing the help text to write to the console. It is an implementation of the public interface `IHelpProvider`.
## Custom help providers
Whilst it shouldn't be common place to implement your own help provider, it is however possible.
You are able to implement your own `IHelpProvider` and configure a `CommandApp` to use that instead of the Spectre.Console help provider.
```csharp
using Spectre.Console.Cli;
namespace Help;
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<DefaultCommand>();
app.Configure(config =>
{
// Register the custom help provider
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
});
return app.Run(args);
}
}
```
There is a working [example of a custom help provider](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Help) demonstrating this.
Title: Command Help
Order: 13
Description: "Console applications built with *Spectre.Console.Cli* include automatically generated help command line help."
---
Console applications built with `Spectre.Console.Cli` include automatically generated help which is displayed when `-h` or `--help` has been specified on the command line.
The automatically generated help is derived from the configured commands and their command settings.
The help is also context aware and tailored depending on what has been specified on the command line before it. For example,
1. When `-h` or `--help` appears immediately after the application name (eg. `application.exe --help`), then the help displayed is a high-level summary of the application, including any command line examples and a listing of all possible commands the user can execute.
2. When `-h` or `--help` appears immediately after a command has been specified (eg. `application.exe command --help`), then the help displayed is specific to the command and includes information about command specific switches and any default values.
`HelpProvider` is the `Spectre.Console` class responsible for determining context and preparing the help text to write to the console. It is an implementation of the public interface `IHelpProvider`.
## Custom help providers
Whilst it shouldn't be common place to implement your own help provider, it is however possible.
You are able to implement your own `IHelpProvider` and configure a `CommandApp` to use that instead of the Spectre.Console help provider.
```csharp
using Spectre.Console.Cli;
namespace Help;
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<DefaultCommand>();
app.Configure(config =>
{
// Register the custom help provider
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
});
return app.Run(args);
}
}
```
There is a working [example of a custom help provider](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Help) demonstrating this.

View File

@ -1,119 +1,119 @@
Title: Markup
Order: 30
Description: The Markup class allows you to output rich text to the console.
Highlights:
- Easily add *color*.
- Add hyperlinks to for supported terminals.
- Emoji 🚀 parsing.
Reference:
- M:Spectre.Console.AnsiConsole.Markup(System.String)
- M:Spectre.Console.AnsiConsole.MarkupLine(System.String)
- T:Spectre.Console.Markup
---
The `Markup` class allows you to output rich text to the console.
## Syntax
Console markup uses a syntax inspired by bbcode. If you write the style (see [Styles](xref:styles))
in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`.
```csharp
AnsiConsole.Write(new Markup("[bold yellow]Hello[/] [red]World![/]"));
```
The `Markup` class implements `IRenderable` which means that you
can use this in tables, grids, and panels. Most classes that support
rendering of `IRenderable` also have overloads for rendering rich text.
```csharp
var table = new Table();
table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]")));
table.AddColumn(new TableColumn("[blue]Bar[/]"));
AnsiConsole.Write(table);
```
## Convenience methods
There are also convenience methods on `AnsiConsole` that can be used
to write markup text to the console without instantiating a new `Markup`
instance.
```csharp
AnsiConsole.Markup("[underline green]Hello[/] ");
AnsiConsole.MarkupLine("[bold]World[/]");
```
## Escaping format characters
To output a `[` you use `[[`, and to output a `]` you use `]]`.
```csharp
AnsiConsole.Markup("[[Hello]] "); // [Hello]
AnsiConsole.Markup("[red][[World]][/]"); // [World]
```
You can also use the `EscapeMarkup` extension method.
```csharp
AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".EscapeMarkup());
```
You can also use the `Markup.Escape` method.
```csharp
AnsiConsole.Markup("[red]{0}[/]", Markup.Escape("Hello [World]"));
```
## Escaping Interpolated Strings
When working with interpolated strings, you can use the `MarkupInterpolated` and `MarkupLineInterpolated` methods to automatically escape the values in the interpolated string "holes".
```csharp
string hello = "Hello [World]";
AnsiConsole.MarkupInterpolated($"[red]{hello}[/]");
```
## Setting background color
You can set the background color in markup by prefixing the color with `on`.
```csharp
AnsiConsole.Markup("[bold yellow on blue]Hello[/]");
AnsiConsole.Markup("[default on blue]World[/]");
```
## Rendering emojis
To output an emoji as part of markup, you can use emoji shortcodes.
```csharp
AnsiConsole.Markup("Hello :globe_showing_europe_africa:!");
```
For a list of emoji, see the [Emojis](xref:emojis) appendix section.
## Colors
In the examples above, all colors were referenced by their name,
but you can also use the hex or rgb representation for colors in markdown.
```csharp
AnsiConsole.Markup("[red]Foo[/] ");
AnsiConsole.Markup("[#ff0000]Bar[/] ");
AnsiConsole.Markup("[rgb(255,0,0)]Baz[/] ");
```
For a list of colors, see the [Colors](xref:colors) appendix section.
## Links
To output a clickable link, you can use the `[link]` style.
```csharp
AnsiConsole.Markup("[link]https://spectreconsole.net[/]");
AnsiConsole.Markup("[link=https://spectreconsole.net]Spectre Console Documentation[/]");
```
## Styles
For a list of styles, see the [Styles](xref:styles) appendix section.
Title: Markup
Order: 30
Description: The Markup class allows you to output rich text to the console.
Highlights:
- Easily add *color*.
- Add hyperlinks to for supported terminals.
- Emoji 🚀 parsing.
Reference:
- M:Spectre.Console.AnsiConsole.Markup(System.String)
- M:Spectre.Console.AnsiConsole.MarkupLine(System.String)
- T:Spectre.Console.Markup
---
The `Markup` class allows you to output rich text to the console.
## Syntax
Console markup uses a syntax inspired by bbcode. If you write the style (see [Styles](xref:styles))
in square brackets, e.g. `[bold red]`, that style will apply until it is closed with a `[/]`.
```csharp
AnsiConsole.Write(new Markup("[bold yellow]Hello[/] [red]World![/]"));
```
The `Markup` class implements `IRenderable` which means that you
can use this in tables, grids, and panels. Most classes that support
rendering of `IRenderable` also have overloads for rendering rich text.
```csharp
var table = new Table();
table.AddColumn(new TableColumn(new Markup("[yellow]Foo[/]")));
table.AddColumn(new TableColumn("[blue]Bar[/]"));
AnsiConsole.Write(table);
```
## Convenience methods
There are also convenience methods on `AnsiConsole` that can be used
to write markup text to the console without instantiating a new `Markup`
instance.
```csharp
AnsiConsole.Markup("[underline green]Hello[/] ");
AnsiConsole.MarkupLine("[bold]World[/]");
```
## Escaping format characters
To output a `[` you use `[[`, and to output a `]` you use `]]`.
```csharp
AnsiConsole.Markup("[[Hello]] "); // [Hello]
AnsiConsole.Markup("[red][[World]][/]"); // [World]
```
You can also use the `EscapeMarkup` extension method.
```csharp
AnsiConsole.Markup("[red]{0}[/]", "Hello [World]".EscapeMarkup());
```
You can also use the `Markup.Escape` method.
```csharp
AnsiConsole.Markup("[red]{0}[/]", Markup.Escape("Hello [World]"));
```
## Escaping Interpolated Strings
When working with interpolated strings, you can use the `MarkupInterpolated` and `MarkupLineInterpolated` methods to automatically escape the values in the interpolated string "holes".
```csharp
string hello = "Hello [World]";
AnsiConsole.MarkupInterpolated($"[red]{hello}[/]");
```
## Setting background color
You can set the background color in markup by prefixing the color with `on`.
```csharp
AnsiConsole.Markup("[bold yellow on blue]Hello[/]");
AnsiConsole.Markup("[default on blue]World[/]");
```
## Rendering emojis
To output an emoji as part of markup, you can use emoji shortcodes.
```csharp
AnsiConsole.Markup("Hello :globe_showing_europe_africa:!");
```
For a list of emoji, see the [Emojis](xref:emojis) appendix section.
## Colors
In the examples above, all colors were referenced by their name,
but you can also use the hex or rgb representation for colors in markdown.
```csharp
AnsiConsole.Markup("[red]Foo[/] ");
AnsiConsole.Markup("[#ff0000]Bar[/] ");
AnsiConsole.Markup("[rgb(255,0,0)]Baz[/] ");
```
For a list of colors, see the [Colors](xref:colors) appendix section.
## Links
To output a clickable link, you can use the `[link]` style.
```csharp
AnsiConsole.Markup("[link]https://spectreconsole.net[/]");
AnsiConsole.Markup("[link=https://spectreconsole.net]Spectre Console Documentation[/]");
```
## Styles
For a list of styles, see the [Styles](xref:styles) appendix section.

View File

@ -63,22 +63,22 @@ What's the secret number? _
```text
Enter password: ************_
```
## Masks
```
## Masks
<?# Example symbol="M:Prompt.Program.AskPasswordWithCustomMask" project="Prompt" /?>
```text
```text
Enter password: ------------_
```
You can utilize a null character to completely hide input.
```
You can utilize a null character to completely hide input.
<?# Example symbol="M:Prompt.Program.AskPasswordWithNullMask" project="Prompt" /?>
```text
```text
Enter password: _
```

View File

@ -1,4 +1,4 @@
Title: Calendar
Title: Calendar
Order: 40
RedirectFrom: calendar
Description: "The **Calendar** is used to render a calendar to the terminal."

View File

@ -1,4 +1,4 @@
using Statiq.App;
using Statiq.App;
using Statiq.Common;
using Statiq.Web;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;

View File

@ -1,4 +1,4 @@
namespace Docs.Extensions
namespace Docs.Extensions
{
public static class StringExtensions
{

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;

View File

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
namespace Docs.Models
{

View File

@ -1,4 +1,4 @@
using System.Linq;
using System.Linq;
using System.Net;
using Docs.Utilities;
using Microsoft.Extensions.DependencyInjection;

View File

@ -1,4 +1,4 @@
using Statiq.Common;
using Statiq.Common;
using Statiq.Web.GitHub;
using Statiq.Web.Netlify;

View File

@ -1,119 +1,119 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Playwright;
using Statiq.Common;
using Statiq.Core;
using Statiq.Web;
using Statiq.Web.Modules;
using Statiq.Web.Pipelines;
namespace Docs.Pipelines
{
public class SocialImages : Pipeline
{
public SocialImages()
{
Dependencies.AddRange(nameof(Inputs));
ProcessModules = new ModuleList
{
new GetPipelineDocuments(ContentType.Content),
// Filter to non-archive content
new FilterDocuments(Config.FromDocument(doc => !Archives.IsArchive(doc))),
// Process the content
new CacheDocuments
{
new AddTitle(),
new SetDestination(true),
new ExecuteIf(Config.FromSetting(WebKeys.OptimizeContentFileNames, true))
{
new OptimizeFileName()
},
new GenerateSocialImage(),
}
};
OutputModules = new ModuleList { new WriteFiles() };
}
}
class GenerateSocialImage : ParallelModule
{
private IPlaywright _playwright;
private IBrowser _browser;
private WebApplication _app;
private IBrowserContext _context;
protected override async Task BeforeExecutionAsync(IExecutionContext context)
{
var builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
builder.Services
.AddRazorPages()
.WithRazorPagesRoot("/src/SocialCards/");
_app = builder.Build();
_app.MapRazorPages();
_app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "src/SocialCards")),
RequestPath = "/static"
});
await _app.StartAsync().ConfigureAwait(false);
_playwright = await Playwright.CreateAsync().ConfigureAwait(false);
_browser = await _playwright.Chromium.LaunchAsync().ConfigureAwait(false);
_context = await _browser.NewContextAsync(new BrowserNewContextOptions {
ViewportSize = new ViewportSize { Width = 1200, Height = 618 },
}).ConfigureAwait(false);
}
protected override async Task FinallyAsync(IExecutionContext context)
{
await _context.DisposeAsync().ConfigureAwait(false);
await _browser.DisposeAsync().ConfigureAwait(false);
_playwright.Dispose();
await _app.DisposeAsync().ConfigureAwait(false);
await base.FinallyAsync(context);
}
protected override async Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context)
{
var url = _app.Urls.FirstOrDefault(u => u.StartsWith("http://"));
var page = await _context.NewPageAsync().ConfigureAwait(false);
var title = input.GetString("Title");
var description = input.GetString("Description");
var highlights = input.GetList<string>("Highlights") ?? Array.Empty<string>();
await page.GotoAsync($"{url}/?title={title}&desc={description}&highlights={string.Join("||", highlights)}");
// This will not just wait for the page to load over the network, but it'll also give
// chrome a chance to complete rendering of the fonts while the wait timeout completes.
await page.WaitForLoadStateAsync(LoadState.NetworkIdle).ConfigureAwait(false);
var bytes = await page.ScreenshotAsync().ConfigureAwait(false);
await page.CloseAsync().ConfigureAwait(false);
var destination = input.Destination.InsertSuffix("-social").ChangeExtension("png");
var doc = context.CreateDocument(
input.Source,
destination,
new MetadataItems { { "DocId", input.Id }},
context.GetContentProvider(bytes));
return new[] { doc };
}
}
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Logging;
using Microsoft.Playwright;
using Statiq.Common;
using Statiq.Core;
using Statiq.Web;
using Statiq.Web.Modules;
using Statiq.Web.Pipelines;
namespace Docs.Pipelines
{
public class SocialImages : Pipeline
{
public SocialImages()
{
Dependencies.AddRange(nameof(Inputs));
ProcessModules = new ModuleList
{
new GetPipelineDocuments(ContentType.Content),
// Filter to non-archive content
new FilterDocuments(Config.FromDocument(doc => !Archives.IsArchive(doc))),
// Process the content
new CacheDocuments
{
new AddTitle(),
new SetDestination(true),
new ExecuteIf(Config.FromSetting(WebKeys.OptimizeContentFileNames, true))
{
new OptimizeFileName()
},
new GenerateSocialImage(),
}
};
OutputModules = new ModuleList { new WriteFiles() };
}
}
class GenerateSocialImage : ParallelModule
{
private IPlaywright _playwright;
private IBrowser _browser;
private WebApplication _app;
private IBrowserContext _context;
protected override async Task BeforeExecutionAsync(IExecutionContext context)
{
var builder = WebApplication.CreateBuilder();
builder.Logging.ClearProviders();
builder.Services
.AddRazorPages()
.WithRazorPagesRoot("/src/SocialCards/");
_app = builder.Build();
_app.MapRazorPages();
_app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(
Path.Combine(builder.Environment.ContentRootPath, "src/SocialCards")),
RequestPath = "/static"
});
await _app.StartAsync().ConfigureAwait(false);
_playwright = await Playwright.CreateAsync().ConfigureAwait(false);
_browser = await _playwright.Chromium.LaunchAsync().ConfigureAwait(false);
_context = await _browser.NewContextAsync(new BrowserNewContextOptions {
ViewportSize = new ViewportSize { Width = 1200, Height = 618 },
}).ConfigureAwait(false);
}
protected override async Task FinallyAsync(IExecutionContext context)
{
await _context.DisposeAsync().ConfigureAwait(false);
await _browser.DisposeAsync().ConfigureAwait(false);
_playwright.Dispose();
await _app.DisposeAsync().ConfigureAwait(false);
await base.FinallyAsync(context);
}
protected override async Task<IEnumerable<IDocument>> ExecuteInputAsync(IDocument input, IExecutionContext context)
{
var url = _app.Urls.FirstOrDefault(u => u.StartsWith("http://"));
var page = await _context.NewPageAsync().ConfigureAwait(false);
var title = input.GetString("Title");
var description = input.GetString("Description");
var highlights = input.GetList<string>("Highlights") ?? Array.Empty<string>();
await page.GotoAsync($"{url}/?title={title}&desc={description}&highlights={string.Join("||", highlights)}");
// This will not just wait for the page to load over the network, but it'll also give
// chrome a chance to complete rendering of the fonts while the wait timeout completes.
await page.WaitForLoadStateAsync(LoadState.NetworkIdle).ConfigureAwait(false);
var bytes = await page.ScreenshotAsync().ConfigureAwait(false);
await page.CloseAsync().ConfigureAwait(false);
var destination = input.Destination.InsertSuffix("-social").ChangeExtension("png");
var doc = context.CreateDocument(
input.Source,
destination,
new MetadataItems { { "DocId", input.Id }},
context.GetContentProvider(bytes));
return new[] { doc };
}
}
}

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Docs.Extensions;

View File

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

View File

@ -15,22 +15,22 @@ public static class Program
{
config.SetApplicationName("fake-dotnet");
config.ValidateExamples();
config.AddExample("run", "--no-build");
// Run
config.AddCommand<RunCommand>("run");
// Add
config.AddBranch<AddSettings>("add", add =>
{
add.SetDescription("Add a package or reference to a .NET project");
add.AddCommand<AddPackageCommand>("package");
add.AddCommand<AddReferenceCommand>("reference");
});
// Serve
config.AddCommand<ServeCommand>("serve")
.WithExample("serve", "-o", "firefox")
config.AddExample("run", "--no-build");
// Run
config.AddCommand<RunCommand>("run");
// Add
config.AddBranch<AddSettings>("add", add =>
{
add.SetDescription("Add a package or reference to a .NET project");
add.AddCommand<AddPackageCommand>("package");
add.AddCommand<AddReferenceCommand>("reference");
});
// Serve
config.AddCommand<ServeCommand>("serve")
.WithExample("serve", "-o", "firefox")
.WithExample("serve", "--port", "80", "-o", "firefox");
});

View File

@ -1,30 +1,30 @@
using System.Linq;
using Spectre.Console;
using Spectre.Console.Cli;
using Spectre.Console.Cli.Help;
using Spectre.Console.Rendering;
namespace Help;
/// <summary>
/// Example showing how to extend the built-in Spectre.Console help provider
/// by rendering a custom banner at the top of the help information
/// </summary>
internal class CustomHelpProvider : HelpProvider
{
public CustomHelpProvider(ICommandAppSettings settings)
: base(settings)
{
}
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
{
return new[]
{
new Text("--------------------------------------"), Text.NewLine,
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
new Text("--------------------------------------"), Text.NewLine,
Text.NewLine,
};
}
using System.Linq;
using Spectre.Console;
using Spectre.Console.Cli;
using Spectre.Console.Cli.Help;
using Spectre.Console.Rendering;
namespace Help;
/// <summary>
/// Example showing how to extend the built-in Spectre.Console help provider
/// by rendering a custom banner at the top of the help information
/// </summary>
internal class CustomHelpProvider : HelpProvider
{
public CustomHelpProvider(ICommandAppSettings settings)
: base(settings)
{
}
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
{
return new[]
{
new Text("--------------------------------------"), Text.NewLine,
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
new Text("--------------------------------------"), Text.NewLine,
Text.NewLine,
};
}
}

View File

@ -1,20 +1,20 @@
using Spectre.Console;
using Spectre.Console.Cli;
namespace Help;
public sealed class DefaultCommand : Command
{
private IAnsiConsole _console;
public DefaultCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute(CommandContext context)
{
_console.WriteLine("Hello world");
return 0;
}
using Spectre.Console;
using Spectre.Console.Cli;
namespace Help;
public sealed class DefaultCommand : Command
{
private IAnsiConsole _console;
public DefaultCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute(CommandContext context)
{
_console.WriteLine("Hello world");
return 0;
}
}

View File

@ -1,19 +1,19 @@
using Spectre.Console.Cli;
namespace Help;
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<DefaultCommand>();
app.Configure(config =>
{
// Register the custom help provider
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
});
return app.Run(args);
}
}
using Spectre.Console.Cli;
namespace Help;
public static class Program
{
public static int Main(string[] args)
{
var app = new CommandApp<DefaultCommand>();
app.Configure(config =>
{
// Register the custom help provider
config.SetHelpProvider(new CustomHelpProvider(config.Settings));
});
return app.Run(args);
}
}

View File

@ -33,11 +33,11 @@ namespace Prompt
var age = AskAge();
WriteDivider("Secrets");
var password = AskPassword();
var password = AskPassword();
WriteDivider("Mask");
var mask = AskPasswordWithCustomMask();
var mask = AskPasswordWithCustomMask();
WriteDivider("Null Mask");
var nullMask = AskPasswordWithNullMask();
@ -54,8 +54,8 @@ namespace Prompt
.AddRow("[grey]Favorite fruit[/]", fruit)
.AddRow("[grey]Favorite sport[/]", sport)
.AddRow("[grey]Age[/]", age.ToString())
.AddRow("[grey]Password[/]", password)
.AddRow("[grey]Mask[/]", mask)
.AddRow("[grey]Password[/]", password)
.AddRow("[grey]Mask[/]", mask)
.AddRow("[grey]Null Mask[/]", nullMask)
.AddRow("[grey]Favorite color[/]", string.IsNullOrEmpty(color) ? "Unknown" : color));
}
@ -153,22 +153,22 @@ namespace Prompt
new TextPrompt<string>("Enter [green]password[/]?")
.PromptStyle("red")
.Secret());
}
public static string AskPasswordWithCustomMask()
{
}
public static string AskPasswordWithCustomMask()
{
return AnsiConsole.Prompt(
new TextPrompt<string>("Enter [green]password[/]?")
.PromptStyle("red")
.Secret('-'));
}
public static string AskPasswordWithNullMask()
{
.Secret('-'));
}
public static string AskPasswordWithNullMask()
{
return AnsiConsole.Prompt(
new TextPrompt<string>("Enter [green]password[/]?")
.PromptStyle("red")
.Secret(null));
.Secret(null));
}
public static string AskColor()

View File

@ -1,4 +1,4 @@
using Spectre.Console;
using Spectre.Console;
namespace Generator.Commands
{

View File

@ -1,4 +1,4 @@
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Simplification;
namespace Spectre.Console.Analyzer.CodeActions;
@ -32,171 +32,171 @@ public class SwitchToAnsiConsoleAction : CodeAction
/// <inheritdoc />
protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
var compilation = editor.SemanticModel.Compilation;
var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation;
if (operation == null)
{
return _document;
}
// If there is an IAnsiConsole passed into the method then we'll use it.
// otherwise we'll check for a field level instance.
// if neither of those exist we'll fall back to the static param.
var spectreConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole");
ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol;
if (iansiConsoleSymbol != null)
{
var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition);
foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start))
{
// LookupSymbols check the accessibility of the symbol, but it can
// suggest instance members when the current context is static.
var symbolType = symbol switch
{
IParameterSymbol parameter => parameter.Type,
IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type,
IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type,
ILocalSymbol local => local.Type,
_ => null,
};
// Locals can be returned even if there are not valid in the current context. For instance,
// it can return locals declared after the current location. Or it can return locals that
// should not be accessible in a static local function.
//
// void Sample()
// {
// int local = 0;
// static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it
// }
//
// Parameters from the ancestor methods or local functions are also returned even if the operation is in a static local function.
if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter)
{
var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start;
// The local is not part of the source tree
if (localPosition == null)
{
break;
}
// The local is declared after the current expression
if (localPosition > _originalInvocation.Span.Start)
{
break;
}
// The local is declared outside the static local function
if (isInStaticContext && localPosition < parentStaticMemberStartPosition)
{
break;
}
}
if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol))
{
accessibleConsoleSymbol = symbol;
break;
}
}
}
if (accessibleConsoleSymbol == null)
{
return _document;
}
// Replace the original invocation
var generator = editor.Generator;
var consoleExpression = accessibleConsoleSymbol switch
{
ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
_ => generator.IdentifierName(accessibleConsoleSymbol.Name),
};
var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments)
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia())
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia());
editor.ReplaceNode(_originalInvocation, newExpression);
return editor.GetChangedDocument();
}
private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol)
{
if (symbol == null)
{
return false;
}
if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol))
{
return true;
}
foreach (var iface in symbol.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol))
{
return true;
}
}
return false;
}
private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition)
{
// Local functions can be nested, and an instance local function can be declared
// in a static local function. So, you need to continue to check ancestors when a
// local function is not static.
foreach (var member in operation.Syntax.Ancestors())
{
if (member is LocalFunctionStatementSyntax localFunction)
{
var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken);
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is LambdaExpressionSyntax lambdaExpression)
{
var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol;
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is AnonymousMethodExpressionSyntax anonymousMethod)
{
var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol;
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is MethodDeclarationSyntax methodDeclaration)
{
parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start;
var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken);
return symbol != null && symbol.IsStatic;
}
}
parentStaticMemberStartPosition = -1;
return false;
{
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);
var compilation = editor.SemanticModel.Compilation;
var operation = editor.SemanticModel.GetOperation(_originalInvocation, cancellationToken) as IInvocationOperation;
if (operation == null)
{
return _document;
}
// If there is an IAnsiConsole passed into the method then we'll use it.
// otherwise we'll check for a field level instance.
// if neither of those exist we'll fall back to the static param.
var spectreConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.AnsiConsole");
var iansiConsoleSymbol = compilation.GetTypeByMetadataName("Spectre.Console.IAnsiConsole");
ISymbol? accessibleConsoleSymbol = spectreConsoleSymbol;
if (iansiConsoleSymbol != null)
{
var isInStaticContext = IsInStaticContext(operation, cancellationToken, out var parentStaticMemberStartPosition);
foreach (var symbol in editor.SemanticModel.LookupSymbols(operation.Syntax.GetLocation().SourceSpan.Start))
{
// LookupSymbols check the accessibility of the symbol, but it can
// suggest instance members when the current context is static.
var symbolType = symbol switch
{
IParameterSymbol parameter => parameter.Type,
IFieldSymbol field when !isInStaticContext || field.IsStatic => field.Type,
IPropertySymbol { GetMethod: not null } property when !isInStaticContext || property.IsStatic => property.Type,
ILocalSymbol local => local.Type,
_ => null,
};
// Locals can be returned even if there are not valid in the current context. For instance,
// it can return locals declared after the current location. Or it can return locals that
// should not be accessible in a static local function.
//
// void Sample()
// {
// int local = 0;
// static void LocalFunction() => local; <-- local is invalid here but LookupSymbols suggests it
// }
//
// Parameters from the ancestor methods or local functions are also returned even if the operation is in a static local function.
if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter)
{
var localPosition = symbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(cancellationToken).GetLocation().SourceSpan.Start;
// The local is not part of the source tree
if (localPosition == null)
{
break;
}
// The local is declared after the current expression
if (localPosition > _originalInvocation.Span.Start)
{
break;
}
// The local is declared outside the static local function
if (isInStaticContext && localPosition < parentStaticMemberStartPosition)
{
break;
}
}
if (IsOrImplementSymbol(symbolType, iansiConsoleSymbol))
{
accessibleConsoleSymbol = symbol;
break;
}
}
}
if (accessibleConsoleSymbol == null)
{
return _document;
}
// Replace the original invocation
var generator = editor.Generator;
var consoleExpression = accessibleConsoleSymbol switch
{
ITypeSymbol typeSymbol => generator.TypeExpression(typeSymbol, addImport: true).WithAdditionalAnnotations(Simplifier.AddImportsAnnotation),
_ => generator.IdentifierName(accessibleConsoleSymbol.Name),
};
var newExpression = generator.InvocationExpression(generator.MemberAccessExpression(consoleExpression, operation.TargetMethod.Name), _originalInvocation.ArgumentList.Arguments)
.WithLeadingTrivia(_originalInvocation.GetLeadingTrivia())
.WithTrailingTrivia(_originalInvocation.GetTrailingTrivia());
editor.ReplaceNode(_originalInvocation, newExpression);
return editor.GetChangedDocument();
}
private static bool IsOrImplementSymbol(ITypeSymbol? symbol, ITypeSymbol interfaceSymbol)
{
if (symbol == null)
{
return false;
}
if (SymbolEqualityComparer.Default.Equals(symbol, interfaceSymbol))
{
return true;
}
foreach (var iface in symbol.AllInterfaces)
{
if (SymbolEqualityComparer.Default.Equals(iface, interfaceSymbol))
{
return true;
}
}
return false;
}
private static bool IsInStaticContext(IOperation operation, CancellationToken cancellationToken, out int parentStaticMemberStartPosition)
{
// Local functions can be nested, and an instance local function can be declared
// in a static local function. So, you need to continue to check ancestors when a
// local function is not static.
foreach (var member in operation.Syntax.Ancestors())
{
if (member is LocalFunctionStatementSyntax localFunction)
{
var symbol = operation.SemanticModel!.GetDeclaredSymbol(localFunction, cancellationToken);
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = localFunction.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is LambdaExpressionSyntax lambdaExpression)
{
var symbol = operation.SemanticModel!.GetSymbolInfo(lambdaExpression, cancellationToken).Symbol;
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = lambdaExpression.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is AnonymousMethodExpressionSyntax anonymousMethod)
{
var symbol = operation.SemanticModel!.GetSymbolInfo(anonymousMethod, cancellationToken).Symbol;
if (symbol != null && symbol.IsStatic)
{
parentStaticMemberStartPosition = anonymousMethod.GetLocation().SourceSpan.Start;
return true;
}
}
else if (member is MethodDeclarationSyntax methodDeclaration)
{
parentStaticMemberStartPosition = methodDeclaration.GetLocation().SourceSpan.Start;
var symbol = operation.SemanticModel!.GetDeclaredSymbol(methodDeclaration, cancellationToken);
return symbol != null && symbol.IsStatic;
}
}
parentStaticMemberStartPosition = -1;
return false;
}
}

View File

@ -1,5 +1,5 @@
using Spectre.Console.Cli.Internal.Configuration;
using Spectre.Console.Cli.Internal.Configuration;
namespace Spectre.Console.Cli;
/// <summary>

View File

@ -1,5 +1,5 @@
using Spectre.Console.Cli.Internal.Configuration;
using Spectre.Console.Cli.Internal.Configuration;
namespace Spectre.Console.Cli;
/// <summary>
@ -49,8 +49,8 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp
public Task<int> RunAsync(IEnumerable<string> args)
{
return _app.RunAsync(args);
}
}
internal Configurator GetConfigurator()
{
return _app.GetConfigurator();

View File

@ -5,40 +5,40 @@ namespace Spectre.Console.Cli;
/// and <see cref="IConfigurator{TSettings}"/>.
/// </summary>
public static class ConfiguratorExtensions
{
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="helpProvider">The help provider to use.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHelpProvider helpProvider)
{
{
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="helpProvider">The help provider to use.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetHelpProvider(this IConfigurator configurator, IHelpProvider helpProvider)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.SetHelpProvider(helpProvider);
return configurator;
}
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator)
where T : IHelpProvider
{
}
configurator.SetHelpProvider(helpProvider);
return configurator;
}
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator SetHelpProvider<T>(this IConfigurator configurator)
where T : IHelpProvider
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}
configurator.SetHelpProvider<T>();
return configurator;
}
configurator.SetHelpProvider<T>();
return configurator;
}
/// <summary>

View File

@ -11,22 +11,22 @@ namespace Spectre.Console.Cli.Help;
public class HelpProvider : IHelpProvider
{
private HelpProviderResources resources;
/// <summary>
/// Gets a value indicating how many examples from direct children to show in the help text.
/// </summary>
protected virtual int MaximumIndirectExamples { get; }
/// <summary>
/// Gets a value indicating whether any default values for command options are shown in the help text.
/// </summary>
protected virtual bool ShowOptionDefaultValues { get; }
/// <summary>
/// Gets a value indicating whether a trailing period of a command description is trimmed in the help text.
/// </summary>
protected virtual bool TrimTrailingPeriod { get; }
/// <summary>
/// Gets a value indicating how many examples from direct children to show in the help text.
/// </summary>
protected virtual int MaximumIndirectExamples { get; }
/// <summary>
/// Gets a value indicating whether any default values for command options are shown in the help text.
/// </summary>
protected virtual bool ShowOptionDefaultValues { get; }
/// <summary>
/// Gets a value indicating whether a trailing period of a command description is trimmed in the help text.
/// </summary>
protected virtual bool TrimTrailingPeriod { get; }
private sealed class HelpArgument
{
public string Name { get; }
@ -74,14 +74,14 @@ public class HelpProvider : IHelpProvider
public static IReadOnlyList<HelpOption> Get(ICommandInfo? command, HelpProviderResources resources)
{
var parameters = new List<HelpOption>();
parameters.Add(new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null));
// Version information applies to the entire application
// Include the "-v" option in the help when at the root of the command line application
// Don't allow the "-v" option if users have specified one or more sub-commands
if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false))
{
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
parameters.Add(new HelpOption("h", "help", null, null, resources.PrintHelpDescription, null));
// Version information applies to the entire application
// Include the "-v" option in the help when at the root of the command line application
// Don't allow the "-v" option if users have specified one or more sub-commands
if ((command == null || command?.Parent == null) && !(command?.IsBranch ?? false))
{
parameters.Add(new HelpOption("v", "version", null, null, resources.PrintVersionDescription, null));
}
parameters.AddRange(command?.Parameters.OfType<ICommandOption>().Where(o => !o.IsHidden).Select(o =>
@ -92,55 +92,55 @@ public class HelpProvider : IHelpProvider
?? Array.Empty<HelpOption>());
return parameters;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="HelpProvider"/> class.
/// </summary>
/// <param name="settings">The command line application settings used for configuration.</param>
public HelpProvider(ICommandAppSettings settings)
{
this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues;
this.MaximumIndirectExamples = settings.MaximumIndirectExamples;
}
/// <summary>
/// Initializes a new instance of the <see cref="HelpProvider"/> class.
/// </summary>
/// <param name="settings">The command line application settings used for configuration.</param>
public HelpProvider(ICommandAppSettings settings)
{
this.ShowOptionDefaultValues = settings.ShowOptionDefaultValues;
this.MaximumIndirectExamples = settings.MaximumIndirectExamples;
this.TrimTrailingPeriod = settings.TrimTrailingPeriod;
resources = new HelpProviderResources(settings.Culture);
}
/// <inheritdoc/>
resources = new HelpProviderResources(settings.Culture);
}
/// <inheritdoc/>
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
{
var result = new List<IRenderable>();
result.AddRange(GetHeader(model, command));
var result = new List<IRenderable>();
result.AddRange(GetHeader(model, command));
result.AddRange(GetDescription(model, command));
result.AddRange(GetUsage(model, command));
result.AddRange(GetExamples(model, command));
result.AddRange(GetArguments(model, command));
result.AddRange(GetOptions(model, command));
result.AddRange(GetCommands(model, command));
result.AddRange(GetCommands(model, command));
result.AddRange(GetFooter(model, command));
return result;
}
/// <summary>
/// Gets the header for the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
}
/// <summary>
/// Gets the header for the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo? command)
{
yield break;
}
/// <summary>
/// Gets the description section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
{
yield break;
}
/// <summary>
/// Gets the description section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetDescription(ICommandModel model, ICommandInfo? command)
{
if (command?.Description == null)
@ -152,14 +152,14 @@ public class HelpProvider : IHelpProvider
composer.Style("yellow", $"{resources.Description}:").LineBreak();
composer.Text(command.Description).LineBreak();
yield return composer.LineBreak();
}
/// <summary>
/// Gets the usage section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
}
/// <summary>
/// Gets the usage section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetUsage(ICommandModel model, ICommandInfo? command)
{
var composer = new Composer();
@ -219,26 +219,26 @@ public class HelpProvider : IHelpProvider
}
if (command.IsBranch && command.DefaultCommand == null)
{
{
// The user must specify the command
parameters.Add($"[aqua]<{resources.Command}>[/]");
}
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
{
// We are on a branch with a default command
}
else if (command.IsBranch && command.DefaultCommand != null && command.Commands.Count > 0)
{
// We are on a branch with a default command
// The user can optionally specify the command
parameters.Add($"[aqua][[{resources.Command}]][/]");
}
else if (command.IsDefaultCommand)
{
var commands = model.Commands.Where(x => !x.IsHidden && !x.IsDefaultCommand).ToList();
if (commands.Count > 0)
{
// Commands other than the default are present
// So make these optional in the usage statement
parameters.Add($"[aqua][[{resources.Command}]][/]");
}
parameters.Add($"[aqua][[{resources.Command}]][/]");
}
else if (command.IsDefaultCommand)
{
var commands = model.Commands.Where(x => !x.IsHidden && !x.IsDefaultCommand).ToList();
if (commands.Count > 0)
{
// Commands other than the default are present
// So make these optional in the usage statement
parameters.Add($"[aqua][[{resources.Command}]][/]");
}
}
}
@ -249,19 +249,19 @@ public class HelpProvider : IHelpProvider
{
composer,
};
}
/// <summary>
/// Gets the examples section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
/// <remarks>
/// Examples from the command's direct children are used
/// if no examples have been set on the specified command or model.
/// </remarks>
public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, ICommandInfo? command)
}
/// <summary>
/// Gets the examples section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
/// <remarks>
/// Examples from the command's direct children are used
/// if no examples have been set on the specified command or model.
/// </remarks>
public virtual IEnumerable<IRenderable> GetExamples(ICommandModel model, ICommandInfo? command)
{
var maxExamples = int.MaxValue;
@ -272,12 +272,12 @@ public class HelpProvider : IHelpProvider
// make sure that we limit the number of examples.
maxExamples = MaximumIndirectExamples;
// Start at the current command (if exists)
// or alternatively commence at the model.
// Start at the current command (if exists)
// or alternatively commence at the model.
var commandContainer = command ?? (ICommandContainer)model;
var queue = new Queue<ICommandContainer>(new[] { commandContainer });
// Traverse the command tree and look for examples.
// Traverse the command tree and look for examples.
// As soon as a node contains commands, bail.
while (queue.Count > 0)
{
@ -317,14 +317,14 @@ public class HelpProvider : IHelpProvider
}
return Array.Empty<IRenderable>();
}
/// <summary>
/// Gets the arguments section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
}
/// <summary>
/// Gets the arguments section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetArguments(ICommandModel model, ICommandInfo? command)
{
var arguments = HelpArgument.Get(command);
@ -361,13 +361,13 @@ public class HelpProvider : IHelpProvider
result.Add(grid);
return result;
}
/// <summary>
/// Gets the options section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
}
/// <summary>
/// Gets the options section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetOptions(ICommandModel model, ICommandInfo? command)
{
@ -469,18 +469,18 @@ public class HelpProvider : IHelpProvider
result.Add(grid);
return result;
}
/// <summary>
/// Gets the commands section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
}
/// <summary>
/// Gets the commands section of the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetCommands(ICommandModel model, ICommandInfo? command)
{
var commandContainer = command ?? (ICommandContainer)model;
bool isDefaultCommand = command?.IsDefaultCommand ?? false;
{
var commandContainer = command ?? (ICommandContainer)model;
bool isDefaultCommand = command?.IsDefaultCommand ?? false;
var commands = isDefaultCommand ? model.Commands : commandContainer.Commands;
commands = commands.Where(x => !x.IsHidden).ToList();
@ -530,16 +530,16 @@ public class HelpProvider : IHelpProvider
result.Add(grid);
return result;
}
/// <summary>
/// Gets the footer for the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
}
/// <summary>
/// Gets the footer for the help information.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects.</returns>
public virtual IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo? command)
{
yield break;
{
yield break;
}
}

View File

@ -4,7 +4,7 @@ namespace Spectre.Console.Cli.Help;
/// Represents a command container.
/// </summary>
public interface ICommandContainer
{
{
/// <summary>
/// Gets all the examples for the container.
/// </summary>

View File

@ -1,42 +1,42 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// Represents an executable command.
/// </summary>
public interface ICommandInfo : ICommandContainer
{
/// <summary>
/// Gets the name of the command.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the description of the command.
/// </summary>
string? Description { get; }
/// <summary>
/// Gets a value indicating whether the command is a branch.
/// </summary>
bool IsBranch { get; }
/// <summary>
/// Gets a value indicating whether the command is the default command within its container.
/// </summary>
bool IsDefaultCommand { get; }
/// <summary>
/// Gets a value indicating whether the command is hidden.
/// </summary>
bool IsHidden { get; }
/// <summary>
/// Gets the parameters associated with the command.
/// </summary>
IReadOnlyList<ICommandParameter> Parameters { get; }
/// <summary>
/// Gets the parent command, if any.
/// </summary>
ICommandInfo? Parent { get; }
/// <summary>
/// Represents an executable command.
/// </summary>
public interface ICommandInfo : ICommandContainer
{
/// <summary>
/// Gets the name of the command.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the description of the command.
/// </summary>
string? Description { get; }
/// <summary>
/// Gets a value indicating whether the command is a branch.
/// </summary>
bool IsBranch { get; }
/// <summary>
/// Gets a value indicating whether the command is the default command within its container.
/// </summary>
bool IsDefaultCommand { get; }
/// <summary>
/// Gets a value indicating whether the command is hidden.
/// </summary>
bool IsHidden { get; }
/// <summary>
/// Gets the parameters associated with the command.
/// </summary>
IReadOnlyList<ICommandParameter> Parameters { get; }
/// <summary>
/// Gets the parent command, if any.
/// </summary>
ICommandInfo? Parent { get; }
}

View File

@ -1,23 +1,23 @@
namespace Spectre.Console.Cli.Help;
internal static class ICommandInfoExtensions
{
/// <summary>
/// Walks up the command.Parent tree, adding each command into a list as it goes.
/// </summary>
/// <remarks>The first command added to the list is the current (ie. this one).</remarks>
/// <returns>The list of commands from current to root, as traversed by <see cref="CommandInfo.Parent"/>.</returns>
public static List<ICommandInfo> Flatten(this ICommandInfo commandInfo)
{
var result = new Stack<Help.ICommandInfo>();
var current = commandInfo;
while (current != null)
{
result.Push(current);
current = current.Parent;
}
return result.ToList();
}
}
namespace Spectre.Console.Cli.Help;
internal static class ICommandInfoExtensions
{
/// <summary>
/// Walks up the command.Parent tree, adding each command into a list as it goes.
/// </summary>
/// <remarks>The first command added to the list is the current (ie. this one).</remarks>
/// <returns>The list of commands from current to root, as traversed by <see cref="CommandInfo.Parent"/>.</returns>
public static List<ICommandInfo> Flatten(this ICommandInfo commandInfo)
{
var result = new Stack<Help.ICommandInfo>();
var current = commandInfo;
while (current != null)
{
result.Push(current);
current = current.Parent;
}
return result.ToList();
}
}

View File

@ -1,27 +1,27 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// Represents a command option.
/// </summary>
public interface ICommandOption : ICommandParameter
{
/// <summary>
/// Gets the long names of the option.
/// </summary>
IReadOnlyList<string> LongNames { get; }
/// <summary>
/// Gets the short names of the option.
/// </summary>
IReadOnlyList<string> ShortNames { get; }
/// <summary>
/// Gets the value name of the option, if applicable.
/// </summary>
string? ValueName { get; }
/// <summary>
/// Gets a value indicating whether the option value is optional.
/// </summary>
bool ValueIsOptional { get; }
/// <summary>
/// Represents a command option.
/// </summary>
public interface ICommandOption : ICommandParameter
{
/// <summary>
/// Gets the long names of the option.
/// </summary>
IReadOnlyList<string> LongNames { get; }
/// <summary>
/// Gets the short names of the option.
/// </summary>
IReadOnlyList<string> ShortNames { get; }
/// <summary>
/// Gets the value name of the option, if applicable.
/// </summary>
string? ValueName { get; }
/// <summary>
/// Gets a value indicating whether the option value is optional.
/// </summary>
bool ValueIsOptional { get; }
}

View File

@ -1,32 +1,32 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// Represents a command parameter.
/// </summary>
public interface ICommandParameter
{
/// <summary>
/// Gets a value indicating whether the parameter is a flag.
/// </summary>
bool IsFlag { get; }
/// <summary>
/// Gets a value indicating whether the parameter is required.
/// </summary>
bool Required { get; }
/// <summary>
/// Gets the description of the parameter.
/// </summary>
string? Description { get; }
/// <summary>
/// Gets the default value of the parameter, if specified.
/// </summary>
DefaultValueAttribute? DefaultValue { get; }
/// <summary>
/// Gets a value indicating whether the parameter is hidden.
/// </summary>
bool IsHidden { get; }
/// <summary>
/// Represents a command parameter.
/// </summary>
public interface ICommandParameter
{
/// <summary>
/// Gets a value indicating whether the parameter is a flag.
/// </summary>
bool IsFlag { get; }
/// <summary>
/// Gets a value indicating whether the parameter is required.
/// </summary>
bool Required { get; }
/// <summary>
/// Gets the description of the parameter.
/// </summary>
string? Description { get; }
/// <summary>
/// Gets the default value of the parameter, if specified.
/// </summary>
DefaultValueAttribute? DefaultValue { get; }
/// <summary>
/// Gets a value indicating whether the parameter is hidden.
/// </summary>
bool IsHidden { get; }
}

View File

@ -1,20 +1,20 @@
namespace Spectre.Console.Cli.Help;
/// <summary>
/// The help provider interface for Spectre.Console.
/// </summary>
/// <remarks>
/// Implementations of this interface are responsbile
/// for writing command help to the terminal when the
/// `-h` or `--help` has been specified on the command line.
/// The help provider interface for Spectre.Console.
/// </summary>
/// <remarks>
/// Implementations of this interface are responsbile
/// for writing command help to the terminal when the
/// `-h` or `--help` has been specified on the command line.
/// </remarks>
public interface IHelpProvider
{
/// <summary>
/// Writes help information for the application.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects representing the help information.</returns>
{
/// <summary>
/// Writes help information for the application.
/// </summary>
/// <param name="model">The command model to write help for.</param>
/// <param name="command">The command for which to write help information (optional).</param>
/// <returns>An enumerable collection of <see cref="IRenderable"/> objects representing the help information.</returns>
IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command);
}

View File

@ -24,22 +24,22 @@ public interface ICommandAppSettings
/// <summary>
/// Gets or sets the application version (use it to override auto-detected value).
/// </summary>
string? ApplicationVersion { get; set; }
/// <summary>
/// Gets or sets a value indicating how many examples from direct children to show in the help text.
/// </summary>
int MaximumIndirectExamples { get; set; }
/// <summary>
/// Gets or sets a value indicating whether any default values for command options are shown in the help text.
/// </summary>
bool ShowOptionDefaultValues { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text.
/// </summary>
bool TrimTrailingPeriod { get; set; }
string? ApplicationVersion { get; set; }
/// <summary>
/// Gets or sets a value indicating how many examples from direct children to show in the help text.
/// </summary>
int MaximumIndirectExamples { get; set; }
/// <summary>
/// Gets or sets a value indicating whether any default values for command options are shown in the help text.
/// </summary>
bool ShowOptionDefaultValues { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a trailing period of a command description is trimmed in the help text.
/// </summary>
bool TrimTrailingPeriod { get; set; }
/// <summary>
/// Gets or sets the <see cref="IAnsiConsole"/>.
@ -65,14 +65,14 @@ public interface ICommandAppSettings
/// <summary>
/// Gets or sets a value indicating whether or not parsing is strict.
/// </summary>
bool StrictParsing { get; set; }
bool StrictParsing { get; set; }
/// <summary>
/// Gets or sets a value indicating whether or not flags found on the commnd line
/// that would normally result in a <see cref="CommandParseException"/> being thrown
/// during parsing with the message "Flags cannot be assigned a value."
/// Gets or sets a value indicating whether or not flags found on the commnd line
/// that would normally result in a <see cref="CommandParseException"/> being thrown
/// during parsing with the message "Flags cannot be assigned a value."
/// should instead be added to the remaining arguments collection.
/// </summary>
/// </summary>
bool ConvertFlagsToRemainingArguments { get; set; }
/// <summary>

View File

@ -4,19 +4,19 @@ namespace Spectre.Console.Cli;
/// Represents a configurator.
/// </summary>
public interface IConfigurator
{
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <param name="helpProvider">The help provider to use.</param>
public void SetHelpProvider(IHelpProvider helpProvider);
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
public void SetHelpProvider<T>()
where T : IHelpProvider;
{
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <param name="helpProvider">The help provider to use.</param>
public void SetHelpProvider(IHelpProvider helpProvider);
/// <summary>
/// Sets the help provider for the application.
/// </summary>
/// <typeparam name="T">The type of the help provider to instantiate at runtime and use.</typeparam>
public void SetHelpProvider<T>()
where T : IHelpProvider;
/// <summary>
/// Gets the command app settings.
@ -66,5 +66,5 @@ public interface IConfigurator
/// <param name="action">The command branch configurator.</param>
/// <returns>A branch configurator that can be used to configure the branch further.</returns>
IBranchConfigurator AddBranch<TSettings>(string name, Action<IConfigurator<TSettings>> action)
where TSettings : CommandSettings;
where TSettings : CommandSettings;
}

View File

@ -1,146 +1,146 @@
namespace Spectre.Console.Cli;
internal sealed class CommandExecutor
{
private readonly ITypeRegistrar _registrar;
public CommandExecutor(ITypeRegistrar registrar)
{
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
}
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
args ??= new List<string>();
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
// Create the command model.
var model = CommandModelBuilder.Build(configuration);
_registrar.RegisterInstance(typeof(CommandModel), model);
_registrar.RegisterDependencies(model);
// Asking for version? Kind of a hack, but it's alright.
// We should probably make this a bit better in the future.
if (args.Contains("-v") || args.Contains("--version"))
{
var console = configuration.Settings.Console.GetConsole();
console.WriteLine(ResolveApplicationVersion(configuration));
return 0;
}
// Parse and map the model against the arguments.
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
// Register the arguments with the container.
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
// Create the resolver.
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
{
// Get the registered help provider, falling back to the default provider
// if no custom implementations have been registered.
var helpProviders = resolver.Resolve(typeof(IEnumerable<IHelpProvider>)) as IEnumerable<IHelpProvider>;
var helpProvider = helpProviders?.LastOrDefault() ?? new HelpProvider(configuration.Settings);
// Currently the root?
if (parsedResult?.Tree == null)
{
// Display help.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
return 0;
}
// Get the command to execute.
var leaf = parsedResult.Tree.GetLeafCommand();
if (leaf.Command.IsBranch || leaf.ShowHelp)
{
// Branches can't be executed. Show help.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
return leaf.ShowHelp ? 0 : 1;
}
// Is this the default and is it called without arguments when there are required arguments?
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
{
// Display help for default command.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
return 1;
}
// Create the content.
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
// Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
}
}
#pragma warning disable CS8603 // Possible null reference return.
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
{
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
var parserContext = new CommandTreeParserContext(args, settings.ParsingMode);
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
var parsedResult = parser.Parse(parserContext, tokenizerResult);
var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
var lastParsedCommand = lastParsedLeaf?.Command;
if (lastParsedLeaf != null && lastParsedCommand != null &&
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
lastParsedCommand.DefaultCommand != null)
{
// Insert this branch's default command into the command line
// arguments and try again to see if it will parse.
var argsWithDefaultCommand = new List<string>(args);
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
parsedResult = parser.Parse(parserContext, tokenizerResult);
}
return parsedResult;
}
#pragma warning restore CS8603 // Possible null reference return.
private static string ResolveApplicationVersion(IConfiguration configuration)
{
return
configuration.Settings.ApplicationVersion ?? // potential override
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
}
private static Task<int> Execute(
CommandTree leaf,
CommandTree tree,
CommandContext context,
ITypeResolver resolver,
IConfiguration configuration)
{
// Bind the command tree against the settings.
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
configuration.Settings.Interceptor?.Intercept(context, settings);
// Create and validate the command.
var command = leaf.CreateCommand(resolver);
var validationResult = command.Validate(context, settings);
if (!validationResult.Successful)
{
throw CommandRuntimeException.ValidationFailed(validationResult);
}
// Execute the command.
return command.Execute(context, settings);
}
namespace Spectre.Console.Cli;
internal sealed class CommandExecutor
{
private readonly ITypeRegistrar _registrar;
public CommandExecutor(ITypeRegistrar registrar)
{
_registrar = registrar ?? throw new ArgumentNullException(nameof(registrar));
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
}
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
{
if (configuration == null)
{
throw new ArgumentNullException(nameof(configuration));
}
args ??= new List<string>();
_registrar.RegisterInstance(typeof(IConfiguration), configuration);
_registrar.RegisterLazy(typeof(IAnsiConsole), () => configuration.Settings.Console.GetConsole());
// Create the command model.
var model = CommandModelBuilder.Build(configuration);
_registrar.RegisterInstance(typeof(CommandModel), model);
_registrar.RegisterDependencies(model);
// Asking for version? Kind of a hack, but it's alright.
// We should probably make this a bit better in the future.
if (args.Contains("-v") || args.Contains("--version"))
{
var console = configuration.Settings.Console.GetConsole();
console.WriteLine(ResolveApplicationVersion(configuration));
return 0;
}
// Parse and map the model against the arguments.
var parsedResult = ParseCommandLineArguments(model, configuration.Settings, args);
// Register the arguments with the container.
_registrar.RegisterInstance(typeof(CommandTreeParserResult), parsedResult);
_registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining);
// Create the resolver.
using (var resolver = new TypeResolverAdapter(_registrar.Build()))
{
// Get the registered help provider, falling back to the default provider
// if no custom implementations have been registered.
var helpProviders = resolver.Resolve(typeof(IEnumerable<IHelpProvider>)) as IEnumerable<IHelpProvider>;
var helpProvider = helpProviders?.LastOrDefault() ?? new HelpProvider(configuration.Settings);
// Currently the root?
if (parsedResult?.Tree == null)
{
// Display help.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, null));
return 0;
}
// Get the command to execute.
var leaf = parsedResult.Tree.GetLeafCommand();
if (leaf.Command.IsBranch || leaf.ShowHelp)
{
// Branches can't be executed. Show help.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
return leaf.ShowHelp ? 0 : 1;
}
// Is this the default and is it called without arguments when there are required arguments?
if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required))
{
// Display help for default command.
configuration.Settings.Console.SafeRender(helpProvider.Write(model, leaf.Command));
return 1;
}
// Create the content.
var context = new CommandContext(parsedResult.Remaining, leaf.Command.Name, leaf.Command.Data);
// Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
}
}
#pragma warning disable CS8603 // Possible null reference return.
private CommandTreeParserResult ParseCommandLineArguments(CommandModel model, CommandAppSettings settings, IEnumerable<string> args)
{
var parser = new CommandTreeParser(model, settings.CaseSensitivity, settings.ParsingMode, settings.ConvertFlagsToRemainingArguments);
var parserContext = new CommandTreeParserContext(args, settings.ParsingMode);
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
var parsedResult = parser.Parse(parserContext, tokenizerResult);
var lastParsedLeaf = parsedResult?.Tree?.GetLeafCommand();
var lastParsedCommand = lastParsedLeaf?.Command;
if (lastParsedLeaf != null && lastParsedCommand != null &&
lastParsedCommand.IsBranch && !lastParsedLeaf.ShowHelp &&
lastParsedCommand.DefaultCommand != null)
{
// Insert this branch's default command into the command line
// arguments and try again to see if it will parse.
var argsWithDefaultCommand = new List<string>(args);
argsWithDefaultCommand.Insert(tokenizerResult.Tokens.Position, lastParsedCommand.DefaultCommand.Name);
parserContext = new CommandTreeParserContext(argsWithDefaultCommand, settings.ParsingMode);
tokenizerResult = CommandTreeTokenizer.Tokenize(argsWithDefaultCommand);
parsedResult = parser.Parse(parserContext, tokenizerResult);
}
return parsedResult;
}
#pragma warning restore CS8603 // Possible null reference return.
private static string ResolveApplicationVersion(IConfiguration configuration)
{
return
configuration.Settings.ApplicationVersion ?? // potential override
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
}
private static Task<int> Execute(
CommandTree leaf,
CommandTree tree,
CommandContext context,
ITypeResolver resolver,
IConfiguration configuration)
{
// Bind the command tree against the settings.
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
configuration.Settings.Interceptor?.Intercept(context, settings);
// Create and validate the command.
var command = leaf.CreateCommand(resolver);
var validationResult = command.Validate(context, settings);
if (!validationResult.Successful)
{
throw CommandRuntimeException.ValidationFailed(validationResult);
}
// Execute the command.
return command.Execute(context, settings);
}
}

View File

@ -1,4 +1,4 @@
namespace Spectre.Console.Cli;
namespace Spectre.Console.Cli;
internal sealed class BranchConfigurator : IBranchConfigurator
{

View File

@ -5,16 +5,16 @@ internal sealed class CommandAppSettings : ICommandAppSettings
public CultureInfo? Culture { get; set; }
public string? ApplicationName { get; set; }
public string? ApplicationVersion { get; set; }
public int MaximumIndirectExamples { get; set; }
public bool ShowOptionDefaultValues { get; set; }
public int MaximumIndirectExamples { get; set; }
public bool ShowOptionDefaultValues { get; set; }
public IAnsiConsole? Console { get; set; }
public ICommandInterceptor? Interceptor { get; set; }
public ITypeRegistrarFrontend Registrar { get; set; }
public CaseSensitivity CaseSensitivity { get; set; }
public bool PropagateExceptions { get; set; }
public bool ValidateExamples { get; set; }
public bool TrimTrailingPeriod { get; set; } = true;
public bool StrictParsing { get; set; }
public bool TrimTrailingPeriod { get; set; } = true;
public bool StrictParsing { get; set; }
public bool ConvertFlagsToRemainingArguments { get; set; } = false;
public ParsingMode ParsingMode =>
@ -26,7 +26,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings
{
Registrar = new TypeRegistrar(registrar);
CaseSensitivity = CaseSensitivity.All;
ShowOptionDefaultValues = true;
ShowOptionDefaultValues = true;
MaximumIndirectExamples = 5;
}

View File

@ -19,19 +19,19 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig
Settings = new CommandAppSettings(registrar);
Examples = new List<string[]>();
}
public void SetHelpProvider(IHelpProvider helpProvider)
{
// Register the help provider
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
}
public void SetHelpProvider<T>()
where T : IHelpProvider
{
// Register the help provider
_registrar.Register(typeof(IHelpProvider), typeof(T));
}
public void SetHelpProvider(IHelpProvider helpProvider)
{
// Register the help provider
_registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
}
public void SetHelpProvider<T>()
where T : IHelpProvider
{
// Register the help provider
_registrar.Register(typeof(IHelpProvider), typeof(T));
}
public void AddExample(params string[] args)
{

View File

@ -1,5 +1,5 @@
namespace Spectre.Console.Cli;
internal sealed class CommandInfo : ICommandContainer, ICommandInfo
{
public string Name { get; }
@ -20,14 +20,14 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
// only branches can have a default command
public CommandInfo? DefaultCommand => IsBranch ? Children.FirstOrDefault(c => c.IsDefaultCommand) : null;
public bool IsHidden { get; }
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList();
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList();
ICommandInfo? ICommandInfo.Parent => Parent;
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
public bool IsHidden { get; }
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Children.Cast<ICommandInfo>().ToList();
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
IReadOnlyList<ICommandParameter> ICommandInfo.Parameters => Parameters.Cast<ICommandParameter>().ToList();
ICommandInfo? ICommandInfo.Parent => Parent;
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
public CommandInfo(CommandInfo? parent, ConfiguredCommand prototype)
{
Parent = parent;
@ -54,5 +54,5 @@ internal sealed class CommandInfo : ICommandContainer, ICommandInfo
Description = description.Description;
}
}
}
}
}

View File

@ -1,17 +1,17 @@
namespace Spectre.Console.Cli;
internal sealed class CommandModel : ICommandContainer, ICommandModel
{
public string? ApplicationName { get; }
public ParsingMode ParsingMode { get; }
public IList<CommandInfo> Commands { get; }
public IList<string[]> Examples { get; }
public IList<string[]> Examples { get; }
public CommandInfo? DefaultCommand => Commands.FirstOrDefault(c => c.IsDefaultCommand);
string ICommandModel.ApplicationName => GetApplicationName(ApplicationName);
IReadOnlyList<ICommandInfo> Help.ICommandContainer.Commands => Commands.Cast<ICommandInfo>().ToList();
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
ICommandInfo? Help.ICommandContainer.DefaultCommand => DefaultCommand;
IReadOnlyList<string[]> Help.ICommandContainer.Examples => (IReadOnlyList<string[]>)Examples;
public CommandModel(
@ -22,18 +22,18 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel
ApplicationName = settings.ApplicationName;
ParsingMode = settings.ParsingMode;
Commands = new List<CommandInfo>(commands ?? Array.Empty<CommandInfo>());
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
}
/// <summary>
/// Gets the name of the application.
/// If the provided <paramref name="applicationName"/> is not null or empty,
/// it is returned. Otherwise the name of the current application
/// is determined based on the executable file's name.
/// </summary>
/// <param name="applicationName">The optional name of the application.</param>
/// <returns>
/// The name of the application, or a default value of "?" if no valid application name can be determined.
Examples = new List<string[]>(examples ?? Array.Empty<string[]>());
}
/// <summary>
/// Gets the name of the application.
/// If the provided <paramref name="applicationName"/> is not null or empty,
/// it is returned. Otherwise the name of the current application
/// is determined based on the executable file's name.
/// </summary>
/// <param name="applicationName">The optional name of the application.</param>
/// <returns>
/// The name of the application, or a default value of "?" if no valid application name can be determined.
/// </returns>
private static string GetApplicationName(string? applicationName)
{
@ -45,7 +45,7 @@ internal sealed class CommandModel : ICommandContainer, ICommandModel
private static string? GetApplicationFile()
{
var location = Assembly.GetEntryAssembly()?.Location;
var location = Assembly.GetEntryAssembly()?.Location;
if (string.IsNullOrWhiteSpace(location))
{

View File

@ -1,5 +1,5 @@
namespace Spectre.Console.Cli;
internal static class CommandModelBuilder
{
// Consider removing this in favor for value tuples at some point.
@ -31,8 +31,8 @@ internal static class CommandModelBuilder
configuration.DefaultCommand.Examples.AddRange(configuration.Examples);
// Build the default command.
var defaultCommand = Build(null, configuration.DefaultCommand);
var defaultCommand = Build(null, configuration.DefaultCommand);
result.Add(defaultCommand);
}
@ -55,7 +55,7 @@ internal static class CommandModelBuilder
foreach (var childCommand in command.Children)
{
var child = Build(info, childCommand);
info.Children.Add(child);
info.Children.Add(child);
}
// Normalize argument positions.

View File

@ -1,5 +1,5 @@
namespace Spectre.Console.Cli;
internal abstract class CommandParameter : ICommandParameterInfo, ICommandParameter
{
public Guid Id { get; }
@ -17,10 +17,10 @@ internal abstract class CommandParameter : ICommandParameterInfo, ICommandParame
public string PropertyName => Property.Name;
public virtual bool WantRawValue => ParameterType.IsPairDeconstructable()
&& (PairDeconstructor != null || Converter == null);
public bool IsFlag => ParameterKind == ParameterKind.Flag;
&& (PairDeconstructor != null || Converter == null);
public bool IsFlag => ParameterKind == ParameterKind.Flag;
protected CommandParameter(
Type parameterType, ParameterKind parameterKind, PropertyInfo property,
string? description, TypeConverterAttribute? converter,

View File

@ -8,13 +8,13 @@ internal interface ICommandContainer
/// <summary>
/// Gets all commands in the container.
/// </summary>
IList<CommandInfo> Commands { get; }
IList<CommandInfo> Commands { get; }
/// <summary>
/// Gets the default command for the container.
/// </summary>
/// <remarks>
/// Returns null if a default command has not been set.
/// </remarks>
/// </summary>
/// <remarks>
/// Returns null if a default command has not been set.
/// </remarks>
CommandInfo? DefaultCommand { get; }
}

View File

@ -1,12 +1,12 @@
using static Spectre.Console.Cli.CommandTreeTokenizer;
using static Spectre.Console.Cli.CommandTreeTokenizer;
namespace Spectre.Console.Cli;
internal class CommandTreeParser
{
private readonly CommandModel _configuration;
private readonly ParsingMode _parsingMode;
private readonly CommandOptionAttribute _help;
private readonly CommandOptionAttribute _help;
private readonly bool _convertFlagsToRemainingArguments;
public CaseSensitivity CaseSensitivity { get; }
@ -21,20 +21,20 @@ internal class CommandTreeParser
{
_configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
_parsingMode = parsingMode ?? _configuration.ParsingMode;
_help = new CommandOptionAttribute("-h|--help");
_help = new CommandOptionAttribute("-h|--help");
_convertFlagsToRemainingArguments = convertFlagsToRemainingArguments ?? false;
CaseSensitivity = caseSensitivity;
}
}
public CommandTreeParserResult Parse(IEnumerable<string> args)
{
var parserContext = new CommandTreeParserContext(args, _parsingMode);
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
return Parse(parserContext, tokenizerResult);
}
{
var parserContext = new CommandTreeParserContext(args, _parsingMode);
var tokenizerResult = CommandTreeTokenizer.Tokenize(args);
return Parse(parserContext, tokenizerResult);
}
public CommandTreeParserResult Parse(CommandTreeParserContext context, CommandTreeTokenizerResult tokenizerResult)
{
var tokens = tokenizerResult.Tokens;
@ -258,8 +258,8 @@ internal class CommandTreeParser
// Find the option.
var option = node.FindOption(token.Value, isLongOption, CaseSensitivity);
if (option != null)
{
ParseOptionValue(context, stream, token, node, option);
{
ParseOptionValue(context, stream, token, node, option);
return;
}
@ -291,10 +291,10 @@ internal class CommandTreeParser
CommandTreeParserContext context,
CommandTreeTokenStream stream,
CommandTreeToken token,
CommandTree current,
CommandTree current,
CommandParameter? parameter = null)
{
bool addToMappedCommandParameters = parameter != null;
{
bool addToMappedCommandParameters = parameter != null;
var value = default(string);
@ -312,49 +312,49 @@ internal class CommandTreeParser
{
// Is this a command?
if (current.Command.FindCommand(valueToken.Value, CaseSensitivity) == null)
{
if (parameter != null)
{
if (parameter.ParameterKind == ParameterKind.Flag)
{
if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase))
{
if (!valueToken.HadSeparator)
{
// Do nothing
// - assume valueToken is unrelated to the flag parameter (ie. we've parsed it unnecessarily)
// - rely on the "No value?" code below to set the flag to its default value
// - valueToken will be handled on the next pass of the parser
}
else
{
// Flags cannot be assigned a value.
if (_convertFlagsToRemainingArguments)
{
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
context.AddRemainingArgument(token.Value, value);
// Prevent the option and it's non-boolean value from being added to
// mapped parameters (otherwise an exception will be thrown later
// when binding the value to the flag in the comand settings)
addToMappedCommandParameters = false;
}
else
{
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
}
}
}
else
{
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
}
}
else
{
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
}
{
if (parameter != null)
{
if (parameter.ParameterKind == ParameterKind.Flag)
{
if (!CliConstants.AcceptedBooleanValues.Contains(valueToken.Value, StringComparer.OrdinalIgnoreCase))
{
if (!valueToken.HadSeparator)
{
// Do nothing
// - assume valueToken is unrelated to the flag parameter (ie. we've parsed it unnecessarily)
// - rely on the "No value?" code below to set the flag to its default value
// - valueToken will be handled on the next pass of the parser
}
else
{
// Flags cannot be assigned a value.
if (_convertFlagsToRemainingArguments)
{
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
context.AddRemainingArgument(token.Value, value);
// Prevent the option and it's non-boolean value from being added to
// mapped parameters (otherwise an exception will be thrown later
// when binding the value to the flag in the comand settings)
addToMappedCommandParameters = false;
}
else
{
throw CommandParseException.CannotAssignValueToFlag(context.Arguments, token);
}
}
}
else
{
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
}
}
else
{
value = stream.Consume(CommandTreeToken.Kind.String)?.Value;
}
}
else
{
@ -370,13 +370,13 @@ internal class CommandTreeParser
}
}
else
{
{
context.AddRemainingArgument(token.Value, parseValue ? valueToken.Value : null);
}
}
else
{
if (parameter == null && // Only add tokens which have not been matched to a command parameter
if (parameter == null && // Only add tokens which have not been matched to a command parameter
(context.State == State.Remaining || context.ParsingMode == ParsingMode.Relaxed))
{
context.AddRemainingArgument(token.Value, null);
@ -399,10 +399,10 @@ internal class CommandTreeParser
if (parameter.IsFlagValue())
{
value = null;
}
else
{
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
}
else
{
throw CommandParseException.OptionHasNoValue(context.Arguments, token, option);
}
}
else
@ -415,9 +415,9 @@ internal class CommandTreeParser
}
}
if (parameter != null && addToMappedCommandParameters)
{
current.Mapped.Add(new MappedCommandParameter(parameter, value));
if (parameter != null && addToMappedCommandParameters)
{
current.Mapped.Add(new MappedCommandParameter(parameter, value));
}
}
}

View File

@ -26,15 +26,15 @@ internal class CommandTreeParserContext
public void IncreaseArgumentPosition()
{
CurrentArgumentPosition++;
}
}
public void AddRemainingArgument(string key, string? value)
{
if (!_remaining.ContainsKey(key))
{
_remaining.Add(key, new List<string?>());
}
{
if (!_remaining.ContainsKey(key))
{
_remaining.Add(key, new List<string?>());
}
_remaining[key].Add(value);
}

View File

@ -6,11 +6,11 @@ internal sealed class CommandTreeToken
public int Position { get; }
public string Value { get; }
public string Representation { get; }
public bool IsGrouped { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>.
/// </summary>
public bool IsGrouped { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a separater was encountered immediately before the <see cref="CommandTreeToken.Value"/>.
/// </summary>
public bool HadSeparator { get; set; }
public enum Kind

View File

@ -5,7 +5,7 @@ internal sealed class CommandTreeTokenStream : IReadOnlyList<CommandTreeToken>
private readonly List<CommandTreeToken> _tokens;
private int _position;
public int Count => _tokens.Count;
public int Count => _tokens.Count;
public int Position => _position;
public CommandTreeToken this[int index] => _tokens[index];

View File

@ -29,13 +29,13 @@ internal static class CommandTreeTokenizer
var context = new CommandTreeTokenizerContext();
foreach (var arg in args)
{
if (string.IsNullOrEmpty(arg))
{
// Null strings in the args array are still represented as tokens
tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty));
continue;
}
{
if (string.IsNullOrEmpty(arg))
{
// Null strings in the args array are still represented as tokens
tokens.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, string.Empty, string.Empty));
continue;
}
var start = position;
var reader = new TextBuffer(previousReader, arg);
@ -55,30 +55,30 @@ internal static class CommandTreeTokenizer
}
private static int ParseToken(CommandTreeTokenizerContext context, TextBuffer reader, int position, int start, List<CommandTreeToken> tokens)
{
if (!reader.ReachedEnd && reader.Peek() == '-')
{
// Option
tokens.AddRange(ScanOptions(context, reader));
}
else
{
// Command or argument
while (reader.Peek() != -1)
{
if (reader.ReachedEnd)
{
position += reader.Position - start;
break;
}
tokens.Add(ScanString(context, reader));
// Flush remaining tokens
context.FlushRemaining();
}
}
{
if (!reader.ReachedEnd && reader.Peek() == '-')
{
// Option
tokens.AddRange(ScanOptions(context, reader));
}
else
{
// Command or argument
while (reader.Peek() != -1)
{
if (reader.ReachedEnd)
{
position += reader.Position - start;
break;
}
tokens.Add(ScanString(context, reader));
// Flush remaining tokens
context.FlushRemaining();
}
}
return position;
}
@ -102,7 +102,7 @@ internal static class CommandTreeTokenizer
builder.Append(current);
}
var value = builder.ToString();
var value = builder.ToString();
return new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value);
}
@ -149,8 +149,8 @@ internal static class CommandTreeTokenizer
var token = new CommandTreeToken(CommandTreeToken.Kind.String, reader.Position, "=", "=");
throw CommandParseException.OptionValueWasExpected(reader.Original, token);
}
var tokenValue = ScanString(context, reader);
var tokenValue = ScanString(context, reader);
tokenValue.HadSeparator = true;
result.Add(tokenValue);
}
@ -193,7 +193,7 @@ internal static class CommandTreeTokenizer
// be tokenized as strings. This block handles parsing those cases, but we only allow this
// when the digit is the first character in the token (i.e. "-a1" is always an error), hence the
// result.Count == 0 check above.
string value = string.Empty;
string value = string.Empty;
while (!reader.ReachedEnd)
{
@ -212,12 +212,12 @@ internal static class CommandTreeTokenizer
result.Add(new CommandTreeToken(CommandTreeToken.Kind.String, position, value, value));
}
else
{
{
// Create a token representing the short option.
var representation = current.ToString(CultureInfo.InvariantCulture);
var tokenPosition = position + 1 + result.Count;
var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation);
var representation = current.ToString(CultureInfo.InvariantCulture);
var tokenPosition = position + 1 + result.Count;
var token = new CommandTreeToken(CommandTreeToken.Kind.ShortOption, tokenPosition, representation, representation);
throw CommandParseException.InvalidShortOptionName(reader.Original, token);
}
}

View File

@ -10,7 +10,7 @@ global using System.Linq;
global using System.Reflection;
global using System.Text;
global using System.Threading.Tasks;
global using System.Xml;
global using System.Xml;
global using Spectre.Console.Cli.Help;
global using Spectre.Console.Cli.Unsafe;
global using Spectre.Console.Rendering;

View File

@ -1,153 +1,153 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Spectre.Console.Cli.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class HelpProvider {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal HelpProvider() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to ARGUMENTS.
/// </summary>
internal static string Arguments {
get {
return ResourceManager.GetString("Arguments", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to COMMAND.
/// </summary>
internal static string Command {
get {
return ResourceManager.GetString("Command", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to COMMANDS.
/// </summary>
internal static string Commands {
get {
return ResourceManager.GetString("Commands", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to DEFAULT.
/// </summary>
internal static string Default {
get {
return ResourceManager.GetString("Default", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to DESCRIPTION.
/// </summary>
internal static string Description {
get {
return ResourceManager.GetString("Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to EXAMPLES.
/// </summary>
internal static string Examples {
get {
return ResourceManager.GetString("Examples", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OPTIONS.
/// </summary>
internal static string Options {
get {
return ResourceManager.GetString("Options", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prints help information.
/// </summary>
internal static string PrintHelpDescription {
get {
return ResourceManager.GetString("PrintHelpDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prints version information.
/// </summary>
internal static string PrintVersionDescription {
get {
return ResourceManager.GetString("PrintVersionDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to USAGE.
/// </summary>
internal static string Usage {
get {
return ResourceManager.GetString("Usage", resourceCulture);
}
}
}
}
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Spectre.Console.Cli.Resources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class HelpProvider {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal HelpProvider() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Spectre.Console.Cli.Resources.HelpProvider", typeof(HelpProvider).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to ARGUMENTS.
/// </summary>
internal static string Arguments {
get {
return ResourceManager.GetString("Arguments", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to COMMAND.
/// </summary>
internal static string Command {
get {
return ResourceManager.GetString("Command", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to COMMANDS.
/// </summary>
internal static string Commands {
get {
return ResourceManager.GetString("Commands", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to DEFAULT.
/// </summary>
internal static string Default {
get {
return ResourceManager.GetString("Default", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to DESCRIPTION.
/// </summary>
internal static string Description {
get {
return ResourceManager.GetString("Description", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to EXAMPLES.
/// </summary>
internal static string Examples {
get {
return ResourceManager.GetString("Examples", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to OPTIONS.
/// </summary>
internal static string Options {
get {
return ResourceManager.GetString("Options", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prints help information.
/// </summary>
internal static string PrintHelpDescription {
get {
return ResourceManager.GetString("PrintHelpDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Prints version information.
/// </summary>
internal static string PrintVersionDescription {
get {
return ResourceManager.GetString("PrintVersionDescription", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to USAGE.
/// </summary>
internal static string Usage {
get {
return ResourceManager.GetString("Usage", resourceCulture);
}
}
}
}

View File

@ -141,8 +141,8 @@ public sealed class CommandAppTester
.Trim();
return new CommandAppResult(result, output, context, settings);
}
}
/// <summary>
/// Runs the command application asynchronously.
/// </summary>

View File

@ -60,7 +60,7 @@ public static partial class AnsiConsoleExtensions
if (!char.IsControl(key.KeyChar))
{
text += key.KeyChar.ToString();
text += key.KeyChar.ToString();
var output = key.KeyChar.ToString();
console.Write(secret ? output.Mask(mask) : output, style);
}

View File

@ -185,28 +185,28 @@ public static class StringExtensions
#else
return text.Contains(value, StringComparison.Ordinal);
#endif
}
/// <summary>
/// "Masks" every character in a string.
/// </summary>
/// <param name="value">String value to mask.</param>
/// <param name="mask">Character to use for masking.</param>
/// <returns>Masked string.</returns>
public static string Mask(this string value, char? mask)
{
var output = string.Empty;
if (mask is null)
{
return output;
}
foreach (var c in value)
{
output += mask;
}
return output;
}
/// <summary>
/// "Masks" every character in a string.
/// </summary>
/// <param name="value">String value to mask.</param>
/// <param name="mask">Character to use for masking.</param>
/// <returns>Masked string.</returns>
public static string Mask(this string value, char? mask)
{
var output = string.Empty;
if (mask is null)
{
return output;
}
foreach (var c in value)
{
output += mask;
}
return output;
}
}

View File

@ -1,4 +1,4 @@
namespace Spectre.Console;
namespace Spectre.Console;
/// <summary>
/// Represents horizontal alignment.

View File

@ -286,25 +286,25 @@ public static class TextPromptExtensions
obj.IsSecret = true;
return obj;
}
}
/// <summary>
/// Replaces prompt user input with mask in the console.
/// </summary>
/// <typeparam name="T">The prompt type.</typeparam>
/// <param name="obj">The prompt.</param>
/// <param name="obj">The prompt.</param>
/// <param name="mask">The masking character to use for the secret.</param>
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> Secret<T>(this TextPrompt<T> obj, char? mask)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.IsSecret = true;
obj.Mask = mask;
return obj;
/// <returns>The same instance so that multiple calls can be chained.</returns>
public static TextPrompt<T> Secret<T>(this TextPrompt<T> obj, char? mask)
{
if (obj is null)
{
throw new ArgumentNullException(nameof(obj));
}
obj.IsSecret = true;
obj.Mask = mask;
return obj;
}
/// <summary>

View File

@ -1,4 +1,4 @@
namespace Spectre.Console;
namespace Spectre.Console;
/// <summary>
/// Represents vertical alignment.

View File

@ -1,4 +1,4 @@
namespace Spectre.Console;
namespace Spectre.Console;
internal static class TypeNameHelper
{

View File

@ -1,4 +1,4 @@
namespace Spectre.Console;
namespace Spectre.Console;
[DebuggerDisplay("{Region,nq}")]
internal sealed class LayoutRender

View File

@ -5,7 +5,7 @@ public static class SpectreAnalyzerVerifier<TAnalyzer>
{
public static Task VerifyCodeFixAsync(string source, DiagnosticResult expected, string 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);
@ -13,10 +13,10 @@ public static class SpectreAnalyzerVerifier<TAnalyzer>
{
var test = new Test
{
TestCode = source,
TestState =
{
OutputKind = outputKind,
TestCode = source,
TestState =
{
OutputKind = outputKind,
},
FixedCode = fixedSource,
};

View File

@ -1,28 +1,28 @@
namespace Spectre.Console.Tests.Data;
public sealed class AsynchronousCommand : AsyncCommand<AsynchronousCommandSettings>
{
private readonly IAnsiConsole _console;
public AsynchronousCommand(IAnsiConsole console)
{
_console = console;
}
public async override Task<int> ExecuteAsync(CommandContext context, AsynchronousCommandSettings settings)
{
// Simulate a long running asynchronous task
await Task.Delay(200);
if (settings.ThrowException)
{
throw new Exception($"Throwing exception asynchronously");
}
else
{
_console.WriteLine($"Finished executing asynchronously");
}
return 0;
}
}
namespace Spectre.Console.Tests.Data;
public sealed class AsynchronousCommand : AsyncCommand<AsynchronousCommandSettings>
{
private readonly IAnsiConsole _console;
public AsynchronousCommand(IAnsiConsole console)
{
_console = console;
}
public async override Task<int> ExecuteAsync(CommandContext context, AsynchronousCommandSettings settings)
{
// Simulate a long running asynchronous task
await Task.Delay(200);
if (settings.ThrowException)
{
throw new Exception($"Throwing exception asynchronously");
}
else
{
_console.WriteLine($"Finished executing asynchronously");
}
return 0;
}
}

View File

@ -1,17 +1,17 @@
using Spectre.Console;
public class GreeterCommand : Command<OptionalArgumentWithDefaultValueSettings>
{
private readonly IAnsiConsole _console;
public GreeterCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute(CommandContext context, OptionalArgumentWithDefaultValueSettings settings)
{
_console.WriteLine(settings.Greeting);
return 0;
}
using Spectre.Console;
public class GreeterCommand : Command<OptionalArgumentWithDefaultValueSettings>
{
private readonly IAnsiConsole _console;
public GreeterCommand(IAnsiConsole console)
{
_console = console;
}
public override int Execute(CommandContext context, OptionalArgumentWithDefaultValueSettings settings)
{
_console.WriteLine(settings.Greeting);
return 0;
}
}

View File

@ -1,34 +1,34 @@
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli.Tests.Data.Help;
internal class CustomHelpProvider : HelpProvider
{
private readonly string version;
public CustomHelpProvider(ICommandAppSettings settings, string version)
: base(settings)
{
this.version = version;
}
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo command)
{
return new IRenderable[]
{
new Text("--------------------------------------"), Text.NewLine,
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
new Text("--------------------------------------"), Text.NewLine,
Text.NewLine,
};
}
public override IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo command)
{
return new IRenderable[]
{
Text.NewLine,
new Text($"Version {version}"),
};
}
}
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli.Tests.Data.Help;
internal class CustomHelpProvider : HelpProvider
{
private readonly string version;
public CustomHelpProvider(ICommandAppSettings settings, string version)
: base(settings)
{
this.version = version;
}
public override IEnumerable<IRenderable> GetHeader(ICommandModel model, ICommandInfo command)
{
return new IRenderable[]
{
new Text("--------------------------------------"), Text.NewLine,
new Text("--- CUSTOM HELP PROVIDER ---"), Text.NewLine,
new Text("--------------------------------------"), Text.NewLine,
Text.NewLine,
};
}
public override IEnumerable<IRenderable> GetFooter(ICommandModel model, ICommandInfo command)
{
return new IRenderable[]
{
Text.NewLine,
new Text($"Version {version}"),
};
}
}

View File

@ -1,21 +1,21 @@
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli.Tests.Data.Help;
internal class RedirectHelpProvider : IHelpProvider
{
public virtual IEnumerable<IRenderable> Write(ICommandModel model)
{
return Write(model, null);
}
#nullable enable
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
#nullable disable
{
return new[]
{
new Text("Help has moved online. Please see: http://www.example.com"),
Text.NewLine,
};
}
using Spectre.Console.Rendering;
namespace Spectre.Console.Cli.Tests.Data.Help;
internal class RedirectHelpProvider : IHelpProvider
{
public virtual IEnumerable<IRenderable> Write(ICommandModel model)
{
return Write(model, null);
}
#nullable enable
public virtual IEnumerable<IRenderable> Write(ICommandModel model, ICommandInfo? command)
#nullable disable
{
return new[]
{
new Text("Help has moved online. Please see: http://www.example.com"),
Text.NewLine,
};
}
}

View File

@ -1,8 +1,8 @@
namespace Spectre.Console.Tests.Data;
public sealed class AsynchronousCommandSettings : CommandSettings
{
[CommandOption("--ThrowException")]
[DefaultValue(false)]
public bool ThrowException { get; set; }
namespace Spectre.Console.Tests.Data;
public sealed class AsynchronousCommandSettings : CommandSettings
{
[CommandOption("--ThrowException")]
[DefaultValue(false)]
public bool ThrowException { get; set; }
}

View File

@ -1,4 +1,4 @@
namespace Spectre.Console.Tests.Data;
namespace Spectre.Console.Tests.Data;
public class ReptileSettings : AnimalSettings
{

View File

@ -1,5 +1,5 @@
namespace Spectre.Console.Tests.Data;
public sealed class ThrowingCommandSettings : CommandSettings
{
}
namespace Spectre.Console.Tests.Data;
public sealed class ThrowingCommandSettings : CommandSettings
{
}

View File

@ -7,7 +7,7 @@ global using System.Linq;
global using System.Runtime.CompilerServices;
global using System.Threading.Tasks;
global using Shouldly;
global using Spectre.Console.Cli;
global using Spectre.Console.Cli;
global using Spectre.Console.Cli.Help;
global using Spectre.Console.Cli.Unsafe;
global using Spectre.Console.Testing;

View File

@ -1,70 +1,70 @@
namespace Spectre.Console.Tests.Unit.Cli;
public sealed partial class CommandAppTests
{
public sealed class Async
{
[Fact]
public async void Should_Execute_Command_Asynchronously()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<AsynchronousCommand>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = await app.RunAsync();
// Then
result.ExitCode.ShouldBe(0);
result.Output.ShouldBe("Finished executing asynchronously");
}
[Fact]
public async void Should_Handle_Exception_Asynchronously()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<AsynchronousCommand>();
// When
var result = await app.RunAsync(new[]
{
"--ThrowException",
"true",
});
// Then
result.ExitCode.ShouldBe(-1);
}
[Fact]
public async void Should_Throw_Exception_Asynchronously()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<AsynchronousCommand>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = await Record.ExceptionAsync(async () =>
await app.RunAsync(new[]
{
"--ThrowException",
"true",
}));
// Then
result.ShouldBeOfType<Exception>().And(ex =>
{
ex.Message.ShouldBe("Throwing exception asynchronously");
});
}
}
namespace Spectre.Console.Tests.Unit.Cli;
public sealed partial class CommandAppTests
{
public sealed class Async
{
[Fact]
public async void Should_Execute_Command_Asynchronously()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<AsynchronousCommand>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = await app.RunAsync();
// Then
result.ExitCode.ShouldBe(0);
result.Output.ShouldBe("Finished executing asynchronously");
}
[Fact]
public async void Should_Handle_Exception_Asynchronously()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<AsynchronousCommand>();
// When
var result = await app.RunAsync(new[]
{
"--ThrowException",
"true",
});
// Then
result.ExitCode.ShouldBe(-1);
}
[Fact]
public async void Should_Throw_Exception_Asynchronously()
{
// Given
var app = new CommandAppTester();
app.SetDefaultCommand<AsynchronousCommand>();
app.Configure(config =>
{
config.PropagateExceptions();
});
// When
var result = await Record.ExceptionAsync(async () =>
await app.RunAsync(new[]
{
"--ThrowException",
"true",
}));
// Then
result.ShouldBeOfType<Exception>().And(ex =>
{
ex.Message.ShouldBe("Throwing exception asynchronously");
});
}
}
}

View File

@ -3,133 +3,133 @@ 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
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDefaultCommand<CatCommand>();
animal.SetDefaultCommand<CatCommand>();
});
});
// When
var result = app.Run(new[]
});
// When
var result = app.Run(new[]
{
"animal", "4",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<CatSettings>();
}
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<CatSettings>();
}
[Fact]
public void Should_Throw_When_No_Default_Command_On_Branch()
{
// Given
{
// 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]
});
// 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
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDefaultCommand<CatCommand>();
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
});
// 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).
});
});
// 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]
result.Context.ShouldHaveRemainingArgument("name", values: new[] { "Kitty", });
}
[Fact]
public void Should_Be_Unable_To_Parse_Default_Command_Arguments_Strict_Parsing()
{
// Given
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.UseStrictParsing();
{
config.UseStrictParsing();
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDefaultCommand<CatCommand>();
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
});
// 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'.");
});
}
"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
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
@ -141,23 +141,23 @@ public sealed partial class CommandAppTests
mammal.SetDefaultCommand<CatCommand>();
});
});
});
// When
var result = app.Run(new[]
});
// When
var result = app.Run(new[]
{
"animal", "4", "mammal",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<CatSettings>();
}
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<CatSettings>();
}
[Fact]
public void Should_Run_The_Default_Command_On_Branch_On_Branch_With_Arguments()
{
// Given
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
@ -169,83 +169,83 @@ public sealed partial class CommandAppTests
mammal.SetDefaultCommand<CatCommand>();
});
});
});
// When
var result = app.Run(new[]
});
// 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");
});
}
});
// 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
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<DogCommand>("dog");
animal.SetDefaultCommand<CatCommand>();
animal.SetDefaultCommand<CatCommand>();
});
});
// When
var result = app.Run(new[]
});
// When
var result = app.Run(new[]
{
"animal", "4",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<CatSettings>();
}
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<CatSettings>();
}
[Fact]
public void Should_Run_The_Named_Command_Not_The_Default_Command_On_Branch()
{
// Given
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddBranch<AnimalSettings>("animal", animal =>
{
animal.AddCommand<DogCommand>("dog");
animal.AddCommand<DogCommand>("dog");
animal.SetDefaultCommand<LionCommand>();
animal.SetDefaultCommand<LionCommand>();
});
});
// When
var result = app.Run(new[]
});
// 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");
});
}
});
// 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()
{

View File

@ -1,5 +1,5 @@
using Spectre.Console.Cli.Tests.Data.Help;
using Spectre.Console.Cli.Tests.Data.Help;
namespace Spectre.Console.Tests.Unit.Cli;
public sealed partial class CommandAppTests
@ -93,12 +93,12 @@ public sealed partial class CommandAppTests
});
// When
var result = fixture.Run("cat", "--help");
var result = fixture.Run("cat", "--help");
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("Branch_Called_Without_Help")]
public Task Should_Output_Branch_When_Called_Without_Help_Option()
@ -110,27 +110,27 @@ public sealed partial class CommandAppTests
configurator.SetApplicationName("myapp");
configurator.AddBranch<CatSettings>("cat", animal =>
{
animal.SetDescription("Contains settings for a cat.");
animal.SetDescription("Contains settings for a cat.");
animal.AddCommand<LionCommand>("lion");
});
});
// When
var result = fixture.Run("cat");
var result = fixture.Run("cat");
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("Branch_Default_Greeter")]
[Expectation("Branch_Default_Greeter")]
public Task Should_Output_Branch_With_Default_Correctly()
{
// Given
var fixture = new CommandAppTester();
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.SetApplicationName("myapp");
configurator.AddBranch<OptionalArgumentWithDefaultValueSettings>("branch", animal =>
{
animal.SetDefaultCommand<GreeterCommand>();
@ -138,8 +138,8 @@ public sealed partial class CommandAppTests
});
});
// When
var result = fixture.Run("branch", "--help");
// When
var result = fixture.Run("branch", "--help");
// Then
return Verifier.Verify(result.Output);
@ -186,7 +186,7 @@ public sealed partial class CommandAppTests
});
// When
var result = fixture.Run("cat", "lion", "--help");
var result = fixture.Run("cat", "lion", "--help");
// Then
return Verifier.Verify(result.Output);
@ -233,13 +233,13 @@ public sealed partial class CommandAppTests
[Theory]
[InlineData(null, "EN")]
[InlineData("", "EN")]
[InlineData("en", "EN")]
[InlineData("en", "EN")]
[InlineData("en-EN", "EN")]
[InlineData("fr", "FR")]
[InlineData("fr", "FR")]
[InlineData("fr-FR", "FR")]
[InlineData("sv", "SV")]
[InlineData("sv", "SV")]
[InlineData("sv-SE", "SV")]
[InlineData("de", "DE")]
[InlineData("de", "DE")]
[InlineData("de-DE", "DE")]
[Expectation("Default_Without_Args_Additional")]
public Task Should_Output_Default_Command_And_Additional_Commands_When_Default_Command_Has_Required_Parameters_And_Is_Called_Without_Args_Localised(string culture, string expectationPrefix)
@ -281,106 +281,106 @@ public sealed partial class CommandAppTests
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("Custom_Help_Registered_By_Instance")]
public Task Should_Output_Custom_Help_When_Registered_By_Instance()
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Create the custom help provider
var helpProvider = new CustomHelpProvider(configurator.Settings, "1.0");
// Register the custom help provider instance
registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Create the custom help provider
var helpProvider = new CustomHelpProvider(configurator.Settings, "1.0");
// Register the custom help provider instance
registrar.RegisterInstance(typeof(IHelpProvider), helpProvider);
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Custom_Help_Registered_By_Type")]
public Task Should_Output_Custom_Help_When_Registered_By_Type()
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Register the custom help provider type
registrar.Register(typeof(IHelpProvider), typeof(RedirectHelpProvider));
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Register the custom help provider type
registrar.Register(typeof(IHelpProvider), typeof(RedirectHelpProvider));
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Custom_Help_Configured_By_Instance")]
public Task Should_Output_Custom_Help_When_Configured_By_Instance()
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Configure the custom help provider instance
configurator.SetHelpProvider(new CustomHelpProvider(configurator.Settings, "1.0"));
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Configure the custom help provider instance
configurator.SetHelpProvider(new CustomHelpProvider(configurator.Settings, "1.0"));
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Custom_Help_Configured_By_Type")]
public Task Should_Output_Custom_Help_When_Configured_By_Type()
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Configure the custom help provider type
configurator.SetHelpProvider<RedirectHelpProvider>();
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
{
var registrar = new DefaultTypeRegistrar();
// Given
var fixture = new CommandAppTester(registrar);
fixture.Configure(configurator =>
{
// Configure the custom help provider type
configurator.SetHelpProvider<RedirectHelpProvider>();
configurator.SetApplicationName("myapp");
configurator.AddCommand<DogCommand>("dog");
});
// When
var result = fixture.Run();
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples")]
@ -391,20 +391,20 @@ public sealed partial class CommandAppTests
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// All root examples should be shown
configurator.AddExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
configurator.AddExample("dog", "--name", "Luna");
configurator.AddExample("dog", "--name", "Charlie");
configurator.AddExample("dog", "--name", "Bella");
configurator.AddExample("dog", "--name", "Daisy");
// All root examples should be shown
configurator.AddExample("dog", "--name", "Rufus", "--age", "12", "--good-boy");
configurator.AddExample("dog", "--name", "Luna");
configurator.AddExample("dog", "--name", "Charlie");
configurator.AddExample("dog", "--name", "Bella");
configurator.AddExample("dog", "--name", "Daisy");
configurator.AddExample("dog", "--name", "Milo");
configurator.AddExample("horse", "--name", "Brutus");
configurator.AddExample("horse", "--name", "Sugar", "--IsAlive", "false");
configurator.AddExample("horse", "--name", "Cash");
configurator.AddExample("horse", "--name", "Dakota");
configurator.AddExample("horse", "--name", "Cisco");
configurator.AddExample("horse", "--name", "Spirit");
configurator.AddExample("horse", "--name", "Brutus");
configurator.AddExample("horse", "--name", "Sugar", "--IsAlive", "false");
configurator.AddExample("horse", "--name", "Cash");
configurator.AddExample("horse", "--name", "Dakota");
configurator.AddExample("horse", "--name", "Cisco");
configurator.AddExample("horse", "--name", "Spirit");
configurator.AddCommand<DogCommand>("dog");
configurator.AddCommand<HorseCommand>("horse");
@ -415,10 +415,10 @@ public sealed partial class CommandAppTests
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("Root_Examples_Children")]
[Expectation("Root_Examples_Children")]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:SingleLineCommentsMustNotBeFollowedByBlankLine", Justification = "Single line comment is relevant to several code blocks that follow.")]
public Task Should_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
@ -426,135 +426,24 @@ public sealed partial class CommandAppTests
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// It should be capped to the first 5 examples by default
configurator.SetApplicationName("myapp");
// It should be capped to the first 5 examples by default
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Children_Eight")]
public Task Should_Output_Eight_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// Show the first 8 examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 8;
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Children_Twelve")]
public Task Should_Output_All_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// Show all examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = int.MaxValue;
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Children_None")]
public Task Should_Not_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// Do not show examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 0;
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
@ -566,7 +455,118 @@ public sealed partial class CommandAppTests
}
[Fact]
[Expectation("Root_Examples_Leafs")]
[Expectation("Root_Examples_Children_Eight")]
public Task Should_Output_Eight_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// Show the first 8 examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 8;
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Children_Twelve")]
public Task Should_Output_All_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// Show all examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = int.MaxValue;
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Children_None")]
public Task Should_Not_Output_Examples_Defined_On_Direct_Children_If_Root_Has_No_Examples()
{
// Given
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// Do not show examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 0;
configurator.AddCommand<DogCommand>("dog")
.WithExample("dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("dog", "--name", "Luna")
.WithExample("dog", "--name", "Charlie")
.WithExample("dog", "--name", "Bella")
.WithExample("dog", "--name", "Daisy")
.WithExample("dog", "--name", "Milo");
configurator.AddCommand<HorseCommand>("horse")
.WithExample("horse", "--name", "Brutus")
.WithExample("horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("horse", "--name", "Cash")
.WithExample("horse", "--name", "Dakota")
.WithExample("horse", "--name", "Cisco")
.WithExample("horse", "--name", "Spirit");
});
// When
var result = fixture.Run("--help");
// Then
return Verifier.Verify(result.Output);
}
[Fact]
[Expectation("Root_Examples_Leafs")]
[SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1512:SingleLineCommentsMustNotBeFollowedByBlankLine", Justification = "Single line comment is relevant to several code blocks that follow.")]
public Task Should_Output_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
{
@ -577,24 +577,24 @@ public sealed partial class CommandAppTests
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
// It should be capped to the first 5 examples by default
animal.SetDescription("The animal command.");
// It should be capped to the first 5 examples by default
animal.AddCommand<DogCommand>("dog")
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
animal.AddCommand<HorseCommand>("horse")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Spirit");
});
});
@ -604,10 +604,10 @@ public sealed partial class CommandAppTests
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("Root_Examples_Leafs_Eight")]
[Expectation("Root_Examples_Leafs_Eight")]
public Task Should_Output_Eight_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
{
// Given
@ -617,25 +617,25 @@ public sealed partial class CommandAppTests
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
// Show the first 8 examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 8;
animal.SetDescription("The animal command.");
// Show the first 8 examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 8;
animal.AddCommand<DogCommand>("dog")
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
animal.AddCommand<HorseCommand>("horse")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Spirit");
});
});
@ -645,10 +645,10 @@ public sealed partial class CommandAppTests
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("Root_Examples_Leafs_Twelve")]
[Expectation("Root_Examples_Leafs_Twelve")]
public Task Should_Output_All_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
{
// Given
@ -658,25 +658,25 @@ public sealed partial class CommandAppTests
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
// Show all examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = int.MaxValue;
animal.SetDescription("The animal command.");
// Show all examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = int.MaxValue;
animal.AddCommand<DogCommand>("dog")
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
animal.AddCommand<HorseCommand>("horse")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Spirit");
});
});
@ -686,10 +686,10 @@ public sealed partial class CommandAppTests
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("Root_Examples_Leafs_None")]
[Expectation("Root_Examples_Leafs_None")]
public Task Should_Not_Output_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found()
{
// Given
@ -699,25 +699,25 @@ public sealed partial class CommandAppTests
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
// Do not show examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 0;
animal.SetDescription("The animal command.");
// Do not show examples defined on the direct children
configurator.Settings.MaximumIndirectExamples = 0;
animal.AddCommand<DogCommand>("dog")
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
.WithExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy")
.WithExample("animal", "dog", "--name", "Luna")
.WithExample("animal", "dog", "--name", "Charlie")
.WithExample("animal", "dog", "--name", "Bella")
.WithExample("animal", "dog", "--name", "Daisy")
.WithExample("animal", "dog", "--name", "Milo");
animal.AddCommand<HorseCommand>("horse")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Brutus")
.WithExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false")
.WithExample("animal", "horse", "--name", "Cash")
.WithExample("animal", "horse", "--name", "Dakota")
.WithExample("animal", "horse", "--name", "Cisco")
.WithExample("animal", "horse", "--name", "Spirit");
});
});
@ -737,23 +737,23 @@ public sealed partial class CommandAppTests
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
configurator.SetApplicationName("myapp");
configurator.AddBranch<AnimalSettings>("animal", animal =>
{
animal.SetDescription("The animal command.");
// All branch examples should be shown
animal.AddExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
animal.AddExample("animal", "dog", "--name", "Luna");
animal.AddExample("animal", "dog", "--name", "Charlie");
animal.AddExample("animal", "dog", "--name", "Bella");
animal.AddExample("animal", "dog", "--name", "Daisy");
animal.AddExample("animal", "dog", "--name", "Milo");
animal.AddExample("animal", "horse", "--name", "Brutus");
animal.AddExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false");
animal.AddExample("animal", "horse", "--name", "Cash");
animal.AddExample("animal", "horse", "--name", "Dakota");
animal.AddExample("animal", "horse", "--name", "Cisco");
animal.SetDescription("The animal command.");
// All branch examples should be shown
animal.AddExample("animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy");
animal.AddExample("animal", "dog", "--name", "Luna");
animal.AddExample("animal", "dog", "--name", "Charlie");
animal.AddExample("animal", "dog", "--name", "Bella");
animal.AddExample("animal", "dog", "--name", "Daisy");
animal.AddExample("animal", "dog", "--name", "Milo");
animal.AddExample("animal", "horse", "--name", "Brutus");
animal.AddExample("animal", "horse", "--name", "Sugar", "--IsAlive", "false");
animal.AddExample("animal", "horse", "--name", "Cash");
animal.AddExample("animal", "horse", "--name", "Dakota");
animal.AddExample("animal", "horse", "--name", "Cisco");
animal.AddExample("animal", "horse", "--name", "Spirit");
animal.AddCommand<DogCommand>("dog")
@ -768,7 +768,7 @@ public sealed partial class CommandAppTests
// Then
return Verifier.Verify(result.Output);
}
}
[Fact]
[Expectation("Default_Examples")]
@ -780,13 +780,13 @@ public sealed partial class CommandAppTests
fixture.Configure(configurator =>
{
configurator.SetApplicationName("myapp");
// All root examples should be shown
configurator.AddExample("--name", "Rufus", "--age", "12", "--good-boy");
configurator.AddExample("--name", "Luna");
configurator.AddExample("--name", "Charlie");
configurator.AddExample("--name", "Bella");
configurator.AddExample("--name", "Daisy");
// All root examples should be shown
configurator.AddExample("--name", "Rufus", "--age", "12", "--good-boy");
configurator.AddExample("--name", "Luna");
configurator.AddExample("--name", "Charlie");
configurator.AddExample("--name", "Bella");
configurator.AddExample("--name", "Daisy");
configurator.AddExample("--name", "Milo");
});

View File

@ -352,8 +352,8 @@ public sealed partial class CommandAppTests
var result = app.Run("dog", "-u");
// Then
return Verifier.Verify(result.Output);
}
return Verifier.Verify(result.Output);
}
}
[UsesVerify]
@ -584,7 +584,7 @@ public sealed partial class CommandAppTests
// Then
result.Output.ShouldBe("Error: Command 'dog' is missing required argument 'AGE'.");
}
}
}
}
}

View File

@ -3,16 +3,16 @@ namespace Spectre.Console.Tests.Unit.Cli;
public sealed partial class CommandAppTests
{
public sealed class Remaining
{
[Theory]
[InlineData("-a")]
{
[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");
});
@ -20,29 +20,29 @@ public sealed partial class CommandAppTests
// When
var result = app.Run(new[]
{
"dog", "12", "4",
knownFlag,
});
// Then
result.Settings.ShouldBeOfType<DogSettings>().And(dog =>
{
dog.IsAlive.ShouldBe(true);
});
"dog", "12", "4",
knownFlag,
});
result.Context.Remaining.Parsed.Count.ShouldBe(0);
// 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")]
}
[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");
});
@ -50,23 +50,23 @@ public sealed partial class CommandAppTests
// 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 });
"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]
}
[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");
});
@ -74,25 +74,25 @@ public sealed partial class CommandAppTests
// 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 });
"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")]
}
[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");
@ -101,68 +101,68 @@ public sealed partial class CommandAppTests
// When
var result = app.Run(new[]
{
"dog", "12", "4",
knownFlag,
});
// Then
result.Context.Remaining.Parsed.Count.ShouldBe(0);
"dog", "12", "4",
knownFlag,
});
// Then
result.Context.Remaining.Parsed.Count.ShouldBe(0);
result.Context.Remaining.Raw.Count.ShouldBe(0);
}
[Theory]
[InlineData("-r")]
}
[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
// 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('-')}'.");
"dog", "12", "4",
unknownFlag,
}));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe($"Unknown option '{unknownFlag.TrimStart('-')}'.");
});
}
[Fact]
}
[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
// When
var result = Record.Exception(() => app.Run(new[]
{
"dog", "12", "4",
"-agr",
}));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe($"Unknown option 'r'.");
"dog", "12", "4",
"-agr",
}));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe($"Unknown option 'r'.");
});
}
}
[Fact]
public void Should_Register_Remaining_Parsed_Arguments_With_Context()
@ -254,19 +254,19 @@ public sealed partial class CommandAppTests
result.Context.Remaining.Raw[0].ShouldBe("/c");
result.Context.Remaining.Raw[1].ShouldBe("\"set && pause\"");
result.Context.Remaining.Raw[2].ShouldBe("Name=\" -Rufus --' ");
}
[Theory]
[InlineData(true)]
}
[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.Settings.ConvertFlagsToRemainingArguments = true;
config.Settings.StrictParsing = useStrictParsing;
config.PropagateExceptions();
config.AddCommand<DogCommand>("dog");
});
@ -280,8 +280,8 @@ public sealed partial class CommandAppTests
// Then
result.Context.Remaining.Parsed.Count.ShouldBe(1);
result.Context.ShouldHaveRemainingArgument("good-boy", values: new[] { "Please be good Rufus!" });
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
}
}

View File

@ -15,8 +15,8 @@ public sealed partial class CommandAppTests
// Then
result.Output.ShouldStartWith("Spectre.Cli version ");
}
}
[Fact]
public void Should_Output_Application_Version_To_The_Console_With_No_Command()
{
@ -24,7 +24,7 @@ public sealed partial class CommandAppTests
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationVersion("1.0");
configurator.SetApplicationVersion("1.0");
});
// When
@ -32,8 +32,8 @@ public sealed partial class CommandAppTests
// Then
result.Output.ShouldBe("1.0");
}
}
[Fact]
public void Should_Output_Application_Version_To_The_Console_With_Command()
{
@ -41,8 +41,8 @@ public sealed partial class CommandAppTests
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationVersion("1.0");
configurator.SetApplicationVersion("1.0");
configurator.AddCommand<EmptyCommand>("empty");
});
@ -51,8 +51,8 @@ public sealed partial class CommandAppTests
// Then
result.Output.ShouldBe("1.0");
}
}
[Fact]
public void Should_Output_Application_Version_To_The_Console_With_Default_Command()
{
@ -69,8 +69,8 @@ public sealed partial class CommandAppTests
// Then
result.Output.ShouldBe("1.0");
}
}
[Fact]
public void Should_Output_Application_Version_To_The_Console_With_Branch_Default_Command()
{
@ -78,11 +78,11 @@ public sealed partial class CommandAppTests
var fixture = new CommandAppTester();
fixture.Configure(configurator =>
{
configurator.SetApplicationVersion("1.0");
configurator.AddBranch<EmptyCommandSettings>("branch", branch =>
{
branch.SetDefaultCommand<EmptyCommand>();
configurator.SetApplicationVersion("1.0");
configurator.AddBranch<EmptyCommandSettings>("branch", branch =>
{
branch.SetDefaultCommand<EmptyCommand>();
});
});

View File

@ -1041,66 +1041,66 @@ public sealed partial class CommandAppTests
// Then
result.Context.ShouldNotBeNull();
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[]
{
[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" });
}
});
// 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
@ -1114,7 +1114,7 @@ public sealed partial class CommandAppTests
var app = new CommandApp();
app.Configure(config =>
{
{
config.PropagateExceptions();
config.AddDelegate<DogSettings>(
"foo", (context, settings) =>
@ -1134,8 +1134,8 @@ public sealed partial class CommandAppTests
dog.Age.ShouldBe(12);
dog.Legs.ShouldBe(4);
data.ShouldBe(2);
}
}
[Fact]
public async void Should_Execute_Async_Delegate_Command_At_Root_Level()
{
@ -1145,7 +1145,7 @@ public sealed partial class CommandAppTests
var app = new CommandApp();
app.Configure(config =>
{
{
config.PropagateExceptions();
config.AddAsyncDelegate<DogSettings>(
"foo", (context, settings) =>
@ -1199,8 +1199,8 @@ public sealed partial class CommandAppTests
dog.Age.ShouldBe(12);
dog.Legs.ShouldBe(4);
data.ShouldBe(2);
}
}
[Fact]
public async void Should_Execute_Nested_Async_Delegate_Command()
{

View File

@ -1,314 +1,314 @@
namespace Spectre.Console.Tests.Unit.Cli.Parsing;
public class CommandTreeTokenizerTests
{
public sealed class ScanString
{
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(" ")]
[InlineData("\t")]
[InlineData("\r\n\t")]
[InlineData("👋🏻")]
[InlineData("🐎👋🏻🔥❤️")]
[InlineData("\"🐎👋🏻🔥❤️\" is an emoji sequence")]
public void Should_Preserve_Edgecase_Inputs(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
[Theory]
// Double-quote handling
[InlineData("\"")]
[InlineData("\"\"")]
[InlineData("\"Rufus\"")]
[InlineData("\" Rufus\"")]
[InlineData("\"-R\"")]
[InlineData("\"-Rufus\"")]
[InlineData("\" -Rufus\"")]
// Single-quote handling
[InlineData("'")]
[InlineData("''")]
[InlineData("'Rufus'")]
[InlineData("' Rufus'")]
[InlineData("'-R'")]
[InlineData("'-Rufus'")]
[InlineData("' -Rufus'")]
public void Should_Preserve_Quotes(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
[Theory]
[InlineData("Rufus-")]
[InlineData("Rufus--")]
[InlineData("R-u-f-u-s")]
public void Should_Preserve_Hyphen_Delimiters(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
[Theory]
[InlineData(" Rufus")]
[InlineData("Rufus ")]
[InlineData(" Rufus ")]
public void Should_Preserve_Spaces(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
[Theory]
[InlineData(" \" -Rufus -- ")]
[InlineData("Name=\" -Rufus --' ")]
public void Should_Preserve_Quotes_Hyphen_Delimiters_Spaces(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
}
public sealed class ScanLongOption
{
[Theory]
[InlineData("--Name-", "Name-")]
[InlineData("--Name_", "Name_")]
public void Should_Allow_Hyphens_And_Underscores_In_Option_Name(string actual, string expected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actual });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(expected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption);
}
[Theory]
[InlineData("-- ")]
[InlineData("--Name ")]
[InlineData("--Name\"")]
[InlineData("--Nam\"e")]
public void Should_Throw_On_Invalid_Option_Name(string actual)
{
// Given
// When
var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual }));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Invalid long option name.");
});
}
}
public sealed class ScanShortOptions
{
[Fact]
public void Should_Accept_Option_Without_Value()
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new[] { "-a" });
// Then
result.Remaining.ShouldBeEmpty();
result.Tokens.ShouldHaveSingleItem();
var t = result.Tokens[0];
t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(0);
t.Value.ShouldBe("a");
t.Representation.ShouldBe("-a");
}
[Theory]
[InlineData("-a:foo")]
[InlineData("-a=foo")]
public void Should_Accept_Option_With_Value(string param)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new[] { param });
// Then
result.Remaining.ShouldBeEmpty();
result.Tokens.Count.ShouldBe(2);
var t = result.Tokens.Consume();
t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(0);
t.Value.ShouldBe("a");
t.Representation.ShouldBe("-a");
t = result.Tokens.Consume();
t.TokenKind.ShouldBe(CommandTreeToken.Kind.String);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(3);
t.Value.ShouldBe("foo");
t.Representation.ShouldBe("foo");
}
[Theory]
// Positive values
[InlineData("-a:1.5", null, "1.5")]
[InlineData("-a=1.5", null, "1.5")]
[InlineData("-a", "1.5", "1.5")]
// Negative values
[InlineData("-a:-1.5", null, "-1.5")]
[InlineData("-a=-1.5", null, "-1.5")]
[InlineData("-a", "-1.5", "-1.5")]
public void Should_Accept_Option_With_Numeric_Value(string firstArg, string secondArg, string expectedValue)
{
// Given
List<string> args = new List<string>();
args.Add(firstArg);
if (secondArg != null)
{
args.Add(secondArg);
}
// When
var result = CommandTreeTokenizer.Tokenize(args);
// Then
result.Remaining.ShouldBeEmpty();
result.Tokens.Count.ShouldBe(2);
var t = result.Tokens.Consume();
t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(0);
t.Value.ShouldBe("a");
t.Representation.ShouldBe("-a");
t = result.Tokens.Consume();
t.TokenKind.ShouldBe(CommandTreeToken.Kind.String);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(3);
t.Value.ShouldBe(expectedValue);
t.Representation.ShouldBe(expectedValue);
}
[Fact]
public void Should_Accept_Option_With_Negative_Numeric_Prefixed_String_Value()
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new[] { "-6..2 " });
// Then
result.Remaining.ShouldBeEmpty();
result.Tokens.ShouldHaveSingleItem();
var t = result.Tokens[0];
t.TokenKind.ShouldBe(CommandTreeToken.Kind.String);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(0);
t.Value.ShouldBe("-6..2");
t.Representation.ShouldBe("-6..2");
}
[Theory]
[InlineData("-N ", "N")]
public void Should_Remove_Trailing_Spaces_In_Option_Name(string actual, string expected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actual });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(expected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption);
}
[Theory]
[InlineData("-N-")]
[InlineData("-N\"")]
[InlineData("-a1")]
public void Should_Throw_On_Invalid_Option_Name(string actual)
{
// Given
// When
var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual }));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Short option does not have a valid name.");
});
}
}
[Theory]
[InlineData("-")]
[InlineData("- ")]
public void Should_Throw_On_Missing_Option_Name(string actual)
{
// Given
// When
var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual }));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Option does not have a name.");
});
}
}
namespace Spectre.Console.Tests.Unit.Cli.Parsing;
public class CommandTreeTokenizerTests
{
public sealed class ScanString
{
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(" ")]
[InlineData("\t")]
[InlineData("\r\n\t")]
[InlineData("👋🏻")]
[InlineData("🐎👋🏻🔥❤️")]
[InlineData("\"🐎👋🏻🔥❤️\" is an emoji sequence")]
public void Should_Preserve_Edgecase_Inputs(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
[Theory]
// Double-quote handling
[InlineData("\"")]
[InlineData("\"\"")]
[InlineData("\"Rufus\"")]
[InlineData("\" Rufus\"")]
[InlineData("\"-R\"")]
[InlineData("\"-Rufus\"")]
[InlineData("\" -Rufus\"")]
// Single-quote handling
[InlineData("'")]
[InlineData("''")]
[InlineData("'Rufus'")]
[InlineData("' Rufus'")]
[InlineData("'-R'")]
[InlineData("'-Rufus'")]
[InlineData("' -Rufus'")]
public void Should_Preserve_Quotes(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
[Theory]
[InlineData("Rufus-")]
[InlineData("Rufus--")]
[InlineData("R-u-f-u-s")]
public void Should_Preserve_Hyphen_Delimiters(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
[Theory]
[InlineData(" Rufus")]
[InlineData("Rufus ")]
[InlineData(" Rufus ")]
public void Should_Preserve_Spaces(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
[Theory]
[InlineData(" \" -Rufus -- ")]
[InlineData("Name=\" -Rufus --' ")]
public void Should_Preserve_Quotes_Hyphen_Delimiters_Spaces(string actualAndExpected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actualAndExpected });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(actualAndExpected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.String);
}
}
public sealed class ScanLongOption
{
[Theory]
[InlineData("--Name-", "Name-")]
[InlineData("--Name_", "Name_")]
public void Should_Allow_Hyphens_And_Underscores_In_Option_Name(string actual, string expected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actual });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(expected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.LongOption);
}
[Theory]
[InlineData("-- ")]
[InlineData("--Name ")]
[InlineData("--Name\"")]
[InlineData("--Nam\"e")]
public void Should_Throw_On_Invalid_Option_Name(string actual)
{
// Given
// When
var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual }));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Invalid long option name.");
});
}
}
public sealed class ScanShortOptions
{
[Fact]
public void Should_Accept_Option_Without_Value()
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new[] { "-a" });
// Then
result.Remaining.ShouldBeEmpty();
result.Tokens.ShouldHaveSingleItem();
var t = result.Tokens[0];
t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(0);
t.Value.ShouldBe("a");
t.Representation.ShouldBe("-a");
}
[Theory]
[InlineData("-a:foo")]
[InlineData("-a=foo")]
public void Should_Accept_Option_With_Value(string param)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new[] { param });
// Then
result.Remaining.ShouldBeEmpty();
result.Tokens.Count.ShouldBe(2);
var t = result.Tokens.Consume();
t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(0);
t.Value.ShouldBe("a");
t.Representation.ShouldBe("-a");
t = result.Tokens.Consume();
t.TokenKind.ShouldBe(CommandTreeToken.Kind.String);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(3);
t.Value.ShouldBe("foo");
t.Representation.ShouldBe("foo");
}
[Theory]
// Positive values
[InlineData("-a:1.5", null, "1.5")]
[InlineData("-a=1.5", null, "1.5")]
[InlineData("-a", "1.5", "1.5")]
// Negative values
[InlineData("-a:-1.5", null, "-1.5")]
[InlineData("-a=-1.5", null, "-1.5")]
[InlineData("-a", "-1.5", "-1.5")]
public void Should_Accept_Option_With_Numeric_Value(string firstArg, string secondArg, string expectedValue)
{
// Given
List<string> args = new List<string>();
args.Add(firstArg);
if (secondArg != null)
{
args.Add(secondArg);
}
// When
var result = CommandTreeTokenizer.Tokenize(args);
// Then
result.Remaining.ShouldBeEmpty();
result.Tokens.Count.ShouldBe(2);
var t = result.Tokens.Consume();
t.TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(0);
t.Value.ShouldBe("a");
t.Representation.ShouldBe("-a");
t = result.Tokens.Consume();
t.TokenKind.ShouldBe(CommandTreeToken.Kind.String);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(3);
t.Value.ShouldBe(expectedValue);
t.Representation.ShouldBe(expectedValue);
}
[Fact]
public void Should_Accept_Option_With_Negative_Numeric_Prefixed_String_Value()
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new[] { "-6..2 " });
// Then
result.Remaining.ShouldBeEmpty();
result.Tokens.ShouldHaveSingleItem();
var t = result.Tokens[0];
t.TokenKind.ShouldBe(CommandTreeToken.Kind.String);
t.IsGrouped.ShouldBe(false);
t.Position.ShouldBe(0);
t.Value.ShouldBe("-6..2");
t.Representation.ShouldBe("-6..2");
}
[Theory]
[InlineData("-N ", "N")]
public void Should_Remove_Trailing_Spaces_In_Option_Name(string actual, string expected)
{
// Given
// When
var result = CommandTreeTokenizer.Tokenize(new string[] { actual });
// Then
result.Tokens.Count.ShouldBe(1);
result.Tokens[0].Value.ShouldBe(expected);
result.Tokens[0].TokenKind.ShouldBe(CommandTreeToken.Kind.ShortOption);
}
[Theory]
[InlineData("-N-")]
[InlineData("-N\"")]
[InlineData("-a1")]
public void Should_Throw_On_Invalid_Option_Name(string actual)
{
// Given
// When
var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual }));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Short option does not have a valid name.");
});
}
}
[Theory]
[InlineData("-")]
[InlineData("- ")]
public void Should_Throw_On_Missing_Option_Name(string actual)
{
// Given
// When
var result = Record.Exception(() => CommandTreeTokenizer.Tokenize(new string[] { actual }));
// Then
result.ShouldBeOfType<CommandParseException>().And(ex =>
{
ex.Message.ShouldBe("Option does not have a name.");
});
}
}

View File

@ -1,4 +1,4 @@
namespace Spectre.Console.Tests.Unit.Cli.Testing;
namespace Spectre.Console.Tests.Unit.Cli.Testing;
public class FakeTypeRegistrarTests
{

View File

@ -1,20 +1,20 @@
namespace Spectre.Console.Tests.Unit;
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);
}
{
// Given
Style style;
// When
style = Color.Red;
// Then
style.Foreground.ShouldBe(Color.Red);
}
[Fact]
public void Should_Combine_Two_Styles_As_Expected()
{

View File

@ -1,4 +1,4 @@
namespace Spectre.Console.Tests;
namespace Spectre.Console.Tests;
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class GitHubIssueAttribute : Attribute