В нашем последнем разговоре о System.CommandLine мы узнали, как легко создавать приложения командной строки с помощью .NET и C#, следуя большинству советов из Рекомендаций по интерфейсу командной строки. Мы также ввели внедрение зависимостей, чтобы сделать наш код более гибким, удобным для чтения и тестирования.
Теперь, когда у нас есть готовые базовые настройки, пришло время улучшить взаимодействие с пользователем. До сих пор пользователи взаимодействовали с нашим приложением, вводя каждый аргумент при каждом вызове приложения. В следующей части моего путешествия по System.CommandLine мы собираемся сделать наши CLI-приложения красивыми, но в то же время интерактивными благодаря библиотеке Spectre.Console .NET.
Spectre.Console помогает создавать красивые интерактивные консольные приложения.
Spectre.Console — популярная библиотека .NET с открытым исходным кодом, поддерживаемая .NET Foundation, с более чем 7 тысячами звезд GitHub ⭐. Ключевые особенности:
- Улучшенные подсказки и интерактивность: попрощайтесь с
Console.ReadLine
иConsole.WriteLine
. Теперь мы можем использовать сложные подсказки, такие как интерактивный выбор одного элемента, выбор множественного выбора, секретная подсказка, подсказки с проверкой и многое другое. - Расширенные возможности рендеринга: Spectre.Console предлагает 24-битные цвета, стили текста (например, полужирный, курсив и т. д.), различные виджеты (такие как таблицы, деревья и даже изображения ASCII), отображение прогресса. для длительных задач, контроля состояния и многого другого.
- Совместимость терминала. Не все терминалы поддерживают эти расширенные функции. Но не волнуйтесь, Spectre.Console ловко определяет возможности терминала для настройки отображаемого вывода.
Интеграция Spectre.Console в консольное приложение System.CommandLine
Начните с установки NuGet-пакета Spectre.Console. В дальнейшем мы будем использовать интерфейс IAnsiConsole, так что давайте отложим в сторону статический класс System.Console
и абстракцию IConsole
из System.CommandLine. Далее мы добавим реализацию IAnsiConsole
по умолчанию в наши службы внедрения зависимостей. Если вы не знакомы с методом UseDependencyInjection(...)
, вы можете взять код из моей предыдущей записи в блоге.
// [...] var builder = new CommandLineBuilder(rootCommand); builder.UseDefaults(); builder.UseDependencyInjection(services => { services.AddSingleton(AnsiConsole.Console); // <-- HERE! }); return builder.Build().Invoke(args);
Создание красивой интерактивной команды
В демонстрационных целях давайте создадим команду, предназначенную для создания проекта .NET. Пользователю будет предложено выбрать тип проекта, а затем мы отобразим выбранный тип и нет операции фактический процесс создания проекта (поскольку это не является нашей целью здесь). Мы будем использовать класс SelectionPrompt<string>
для представления доступных типов проектов, позволяя пользователю перемещаться с помощью клавиш со стрелками вверх и вниз. Мы также добавим несколько цветов, чтобы выделить части вывода нашей консоли.
Вот как выглядит код:
using System.CommandLine; using Spectre.Console; public class CreateProjectCommand : Command<CreateProjectCommandOptions, CreateProjectCommandOptionsHandler> { public CreateProjectCommand() : base("create-project", "Create a .NET project") { } } public class CreateProjectCommandOptions : ICommandOptions { } public class CreateProjectCommandOptionsHandler : ICommandOptionsHandler<CreateProjectCommandOptions> { private readonly IAnsiConsole _console; private readonly IProjectManager _projectManager; public CreateProjectCommandOptionsHandler(IAnsiConsole console, IProjectManager projectManager) { this._console = console; this._projectManager = projectManager; } public async Task<int> HandleAsync(CreateProjectCommandOptions options, CancellationToken cancellationToken) { var prompt = new SelectionPrompt<string>() .Title("What [green]type of project[/] would you like to create?") .AddChoices(this._projectManager.ProjectTypes); var projectType = await prompt.ShowAsync(this._console, cancellationToken); this._console.MarkupLineInterpolated($"You selected [green]{projectType}[/]."); await this._projectManager.CreateProjectAsync(projectType, cancellationToken); return 0; } } // Also add "services.AddSingleton<IProjectManager, NoopProjectManager>()" in the dependency injection setup public interface IProjectManager { string[] ProjectTypes { get; } Task CreateProjectAsync(string projectType, CancellationToken cancellationToken); } public sealed class NoopProjectManager : IProjectManager { // For demonstrations purposes only public string[] ProjectTypes => new[] { "ASP.NET Core Web API", "ASP.NET Core Web App", "Class Library", "WPF Application", }; public Task CreateProjectAsync(string projectType, CancellationToken cancellationToken) { return Task.CompletedTask; } }
Вы заметили, как мы поддерживаем отмену пользователем (Ctrl+C
) с помощью CancellationToken
, предоставленного System.CommandLine? Эта функция невероятно полезна. Вы можете поймать OperationCanceledException
, отобразить сообщение о выходе на выходе, очистить некоторые ресурсы и многое другое. Всегда старайтесь выполнить запрос пользователя на отмену, когда это возможно.
Модульное тестирование интерактивных команд
Поскольку мы используем абстракцию IAnsiConsole
вместо статического класса AnsiConsole
, у нас есть возможность модульного тестирования нашего обработчика команд. Этому способствует дополнительный NuGet-пакет Spectre.Console.Testing. Он обеспечивает TestConsole
реализацию, в которой мы можем предустановить клавиши ввода. В приведенном ниже модульном тесте мы гарантируем, что при заданном списке типов проектов дважды нажав клавишу со стрелкой вниз, а затем клавишу ввода, выбирается третий тип проекта. Затем этот выбор передается методу зависимости IProjectManager.CreateProjectAsync
.
using FakeItEasy; using Spectre.Console.Testing; using Xunit; namespace HelloCommandLine.Tests; public class CreateProjectCommandTests { [Fact] public async Task Test1() { // I use FakeItEasy to mock the project manager: https://fakeiteasy.github.io/ var projectManager = A.Fake<IProjectManager>(); A.CallTo(() => projectManager.ProjectTypes).Returns(new[] { "a", "b", "c", "d" }); var console = new TestConsole(); console.Profile.Capabilities.Interactive = true; console.Input.PushKey(ConsoleKey.DownArrow); console.Input.PushKey(ConsoleKey.DownArrow); console.Input.PushKey(ConsoleKey.Enter); var handler = new CreateProjectCommandOptionsHandler(console, projectManager); var result = await handler.HandleAsync(new CreateProjectCommandOptions(), CancellationToken.None); Assert.Equal(0, result); A.CallTo(() => projectManager.CreateProjectAsync("c", CancellationToken.None)) .MustHaveHappenedOnceExactly(); } }
Включение всех цветов, смайликов и анимированных счетчиков
Чтобы получить доступ к полному набору цветов, эмодзи и анимированных счетчиков в Spectre.Console, нам нужно изменить кодировку ввода и вывода консоли на UTF-8. Это не включено по умолчанию, но это легко сделать:
Console.InputEncoding = System.Text.Encoding.UTF8; Console.OutputEncoding = System.Text.Encoding.UTF8;
Вы также можете рассмотреть возможность применения этого изменения только тогда, когда консольный ввод и вывод не перенаправляется.
Подведение итогов
Создавать удобные и привлекательные CLI-приложения в .NET намного проще благодаря объединенным возможностям System.CommandLine и Spectre.Console. Эти библиотеки — от улучшенных подсказок и интерактивности до расширенных возможностей рендеринга — позволяют разработчикам создавать надежные и привлекательные интерфейсы командной строки.
Вы пробовали использовать эти библиотеки раньше? Мне интересен ваш опыт!