Skip to content

Instantly share code, notes, and snippets.

@seesharprun
Last active November 8, 2024 14:47
Show Gist options
  • Save seesharprun/08682b3c2dbe9c180921c5336bff1254 to your computer and use it in GitHub Desktop.
Save seesharprun/08682b3c2dbe9c180921c5336bff1254 to your computer and use it in GitHub Desktop.
Spectre.Console integrated console application (Generic Host)
dotnet run

dotnet run -- --name "Person" --salutation "Ahoy"

dotnet run -- scare

dotnet run -- scare --name "Person"

dotnet run -- surprise

Tip

Of course, you can install this as a .NET tool, give it a unique name, and allow people to call it like they would other tools.

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>aaaaaaaa-0000-1111-2222-bbbbbbbbbbbb</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="*" />
<PackageReference Include="Spectre.Console" Version="0.49.1" />
<PackageReference Include="Spectre.Console.Cli" Version="0.49.1" />
<PackageReference Include="Spectre.Console.Registrars.Microsoft-Di" Version="0.5.0" />
</ItemGroup>
</Project>
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.Internal;
using Spectre.Console;
using Spectre.Console.Cli;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IAnsiConsole>((_) =>
AnsiConsole.Create(
new AnsiConsoleSettings
{
Ansi = AnsiSupport.Detect,
ColorSystem = ColorSystemSupport.Detect,
Out = new AnsiConsoleOutput(Console.Out)
}
)
);
builder.Services.AddSingleton<ILiveConsoleService, LiveConsoleService>();
builder.Services.AddSingleton<IHostLifetime, ConsoleLifetime>();
builder.Services.AddScoped<IMessageService, MessageService>();
ServiceCollectionRegistrar registrar = new (builder.Services);
CommandApp app = new(registrar);
app.SetDefaultCommand<GreetCommand>();
app.Configure(config =>
{
config.AddCommand<ScareCommand>("scare");
config.AddCommand<SurpriseCommand>("surprise");
});
return await app.RunAsync(args);
internal interface IMessageService
{
string GetGreeting(string salutation, string name);
string GetScare(string name);
string GetSurprise();
}
internal sealed class MessageService : IMessageService
{
public string GetGreeting(string salutation, string name) => $"{salutation}, {name}!";
public string GetScare(string name) => $"BOO, {name}!";
public string GetSurprise() => "SURPRISE!";
}
internal interface ILiveConsoleService
{
Task StartLiveConsoleAsync(Func<IAnsiConsole, StatusContext, Task> asyncLogic, string startMessage = "Starting...");
}
internal sealed class LiveConsoleService(
IAnsiConsole console
) : ILiveConsoleService
{
public async Task StartLiveConsoleAsync(Func<IAnsiConsole, StatusContext, Task> asyncLogic, string startMessage = "Starting...")
{
await console.Status().StartAsync(
startMessage,
async (StatusContext context) =>
{
await asyncLogic(console, context);
}
);
}
}
internal sealed class GreetSettings : CommandSettings
{
[CommandOption("-n|--name <NAME>")]
public required string Name { get; init; } = "World";
[CommandOption("-s|--salutation <SALUTATION>")]
public string Salutation { get; init; } = "Hello";
}
internal sealed class GreetCommand(
IMessageService messageService,
ILiveConsoleService liveConsoleService
) : AsyncCommand<GreetSettings>
{
public override async Task<int> ExecuteAsync(CommandContext context, GreetSettings settings)
{
await liveConsoleService.StartLiveConsoleAsync(
async (console, live) =>
{
live.Spinner(Spinner.Known.Star);
live.SpinnerStyle(Style.Parse("yellow"));
await DoWorkAsync(settings, console, live);
},
startMessage: "Greeting..."
);
return 0;
}
private async Task DoWorkAsync(GreetSettings settings, IAnsiConsole console, StatusContext live)
{
live.Status("[italic green]Starting...[/]");
await Task.Delay(TimeSpan.FromSeconds(1.5));
string message = messageService.GetGreeting(settings.Salutation, settings.Name);
console.MarkupLine($"[blue][bold]MESSAGE:[/]\t{message}[/]");
await Task.Delay(TimeSpan.FromSeconds(1.5));
live.Status("[italic green]Finishing...[/]");
await Task.Delay(TimeSpan.FromSeconds(1.5));
}
}
internal sealed class ScareSettings : CommandSettings
{
[CommandOption("-n|--name <NAME>")]
public required string Name { get; init; } = "World";
}
internal sealed class ScareCommand(
IMessageService messageService,
ILiveConsoleService liveConsoleService
) : AsyncCommand<ScareSettings>
{
public override async Task<int> ExecuteAsync(CommandContext context, ScareSettings settings)
{
await liveConsoleService.StartLiveConsoleAsync(
async (console, live) =>
{
live.Spinner(Spinner.Known.Aesthetic);
live.SpinnerStyle(Style.Parse("yellow"));
await DoWorkAsync(settings, console, live);
},
startMessage: "Scaring..."
);
return 0;
}
private async Task DoWorkAsync(ScareSettings settings, IAnsiConsole console, StatusContext live)
{
live.Status("[italic red]Starting...[/]");
await Task.Delay(TimeSpan.FromSeconds(1.5));
string message = messageService.GetScare(settings.Name);
console.MarkupLine($"[blue][bold]MESSAGE:[/]\t{message}[/]");
await Task.Delay(TimeSpan.FromSeconds(1.5));
live.Status("[italic red]Finishing...[/]");
await Task.Delay(TimeSpan.FromSeconds(1.5));
}
}
internal sealed class SurpriseCommand(
IMessageService messageService,
ILiveConsoleService liveConsoleService
) : AsyncCommand
{
public override async Task<int> ExecuteAsync(CommandContext context)
{
await liveConsoleService.StartLiveConsoleAsync(
async (console, live) =>
{
live.Spinner(Spinner.Known.Dots8Bit);
live.SpinnerStyle(Style.Parse("yellow"));
await DoWorkAsync(console, live);
},
startMessage: "Surprising..."
);
return 0;
}
private async Task DoWorkAsync(IAnsiConsole console, StatusContext live)
{
live.Status("[italic purple]Starting...[/]");
await Task.Delay(TimeSpan.FromSeconds(1.5));
string message = messageService.GetSurprise();
console.MarkupLine($"[blue][bold]MESSAGE:[/]\t{message}[/]");
await Task.Delay(TimeSpan.FromSeconds(1.5));
live.Status("[italic purple]Finishing...[/]");
await Task.Delay(TimeSpan.FromSeconds(1.5));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment