Создание CLI-приложений при помощи System.CommandLine в .NET
В .NET уже несколько лет существует библиотека System.CommandLine, позволяющая быстро создавать CLI-приложения. Несмотря на то, что библиотека ещё в стадии beta, её активно используют и сами разработчики Microsoft, например, в утилите dotnet из пакета .NET SDK.
Преимущества этой библиотеки в том, что она позволяет сосредоточиться на разработке полезного функционала приложения и не тратить время на создание парсера команд, опций и аргументов, а также имеет широкие возможности для кастомизации.
Реализация простого CLI-приложения
Библиотека поддерживает следующие виды токенов:
Опции и аргументы определяются совместно через обобщённый тип Option<T>:
Полностью исходный код можно найти в нашем GitLab-репозитории.
Subcommand и command отличаются тем, что subcommand определяет группу команд, а command определяет непосредственно действие, выполняемое командой.
Определим для начала команду sum:
Разделение на команды и группы команд очень условное, т.к. с точки зрения C# оба вида определяются через класс Command. Также нам ничто не мешает определить действие и для группы команд:
Помимо примитивных типов, библиотека также поддерживает массивы, списки, FileInfo, DirectoryInfo и другие. Полный список можно узнать, заглянув в документацию. Если есть необходимость привязать опции к кастомному типу, то можно воспользоваться встроенным механизмом привязки через тип BinderBase<T>. Определим операцию вычитания, используя этот способ:
Тогда определение хендлера для команды вычитания будет выглядеть следующим образом:
Осталось определить Root command через одноимённый класс RootCommand :
Теперь можно собрать приложение и проверить работу:
Внедрение зависимостей
Жизненный цикл CLI-приложения выглядит следующим образом:
Вызывается некоторая команда.
Запускается процесс.
Происходит обработка данных, возвращается результат.
Процесс завершается.
Классические контейнеры зависимостей не рекомендуется использовать, поскольку зависимости одной команды могут быть совершенно не нужны для другой команды, а инициализация всех зависимостей может увеличить время запуска CLI-приложения. Вместо этого можно воспользоваться механизмом внедрения зависимостей для конкретного обработчика. Для этого снова понадобится класс BinderBase<T>. Определим новый класс ResultWriter, который будет записывать результат математической операции в файл:
Теперь создадим класс ResultWriterBinder. Этот класс инкапсулирует экземпляр ResultWriter:
=> _resultWriter;
Теперь определим операцию умножения и внедрим туда экземпляр ResultWriter:
Такой подход позволяет внедрять зависимости в обработчики команд независимо друг от друга.
Кастомизация вывода
Справка генерируется автоматически из описаний, которые использовались при инициализации опций и команд. Например, команда cli-app math sum -h отобразит следующее:
При желании можно заменить любой раздел справки, например, Description или создать новый. Добавим новую строку с текстом «This is a new section» в начало справки:
Тогда вывод станет следующим:
Если требуется улучшить внешний вид вывода данных в целом, то сделать это можно, например, написав кастомный способ отображения при помощи методов и свойств класса Console, таких как SetCursorPosition, ForegroundColor, BackgroundColor и т.д. Либо воспользоваться 3rd-party библиотеками:
1. ShellProgressBar. Простая библиотека для отображения прогресса в командном окне.
2. Spectre.Console. Мощная библиотека для создания красивых консольных приложений, которая имеет множество компонентов, упрощающих создание интерфейса. Кстати, обложка для статьи была сделана с помощью этой библиотеки.
3. ConsoleGUI. Позволяет создавать полноценный GUI на основе консоли. Судя по всему, автор вдохновлялся WPF, так как в ней содержатся привычные для этого фреймворка компоненты: TextBox, CheckBox, DataGrid, DockPanel и другие.
Заключение
Библиотека System.CommandLine является полезным инструментом для создания CLI-приложений. Она даёт разработчикам гибкий инструментарий для работы командами и опциями, что позволяет сократить время разработки и создать удобный и функциональный пользовательский интерфейс.