CLI приложения #17. Вопросы пользователю
В прошлый раз мы разобрались, когда спрашивать. Теперь — как.
Наивный вариант выглядит так:
Console.Write("Введите порт: ");
var input = Console.ReadLine();
var port = int.Parse(input); // а если ввели "абв"?
И дальше начинается: проверка на пустую строку, проверка на число, проверка диапазона, цикл повторного запроса. Знакомая история — ровно как с разбором аргументов во втором посте.
Spectre.Console решает это одной строкой:
var port = AnsiConsole.Ask<int>("Введите порт:");
Тип указан явно, и библиотека сама повторит вопрос, если ответ не преобразуется в число. Значение по умолчанию тоже поддерживается:
var host = AnsiConsole.Ask("Адрес сервера:", "localhost");
Для более сложных случаев есть развёрнутая форма с собственной проверкой:
var port = AnsiConsole.Prompt(
new TextPrompt<int>("Введите порт:")
.DefaultValue(8080)
.Validate(p => p is > 0 and < 65536
? ValidationResult.Success()
: ValidationResult.Error("[red]Порт должен быть от 1 до 65535[/]")));
Пользователь не сможет ввести ерунду: вопрос будет повторяться с пояснением, пока ответ не окажется корректным.
Пароли и токены запрашиваются отдельно — их нельзя показывать на экране:
var token = AnsiConsole.Prompt(
new TextPrompt<string>("Введите токен доступа:")
.PromptStyle("red")
.Secret());
Ввод скрывается звёздочками. Метод .Secret('\0') спрячет его полностью, не показывая даже длину.
Отдельный сюжет — выбор из списка. Просить пользователя набрать название окружения руками, когда вариантов три, — так себе идея: он ошибётся в раскладке или регистре. Даём меню:
var environment = AnsiConsole.Prompt(
new SelectionPrompt<string>()
.Title("В какое окружение разворачиваем?")
.AddChoices("development", "staging", "production"));
Стрелками вверх-вниз, Enter для подтверждения. Если вариантов много, добавьте .PageSize(10) — появится прокрутка, а с .EnableSearch() ещё и поиск по мере набора.
Когда выбрать нужно несколько пунктов сразу — берём множественный вариант:
var modules = AnsiConsole.Prompt(
new MultiSelectionPrompt<string>()
.Title("Какие модули установить?")
.InstructionsText("[grey](пробел — выбрать, enter — подтвердить)[/]")
.AddChoices("api", "worker", "scheduler", "dashboard"));
Вернётся список выбранных значений.
Важно!
Все эти промпты читают клавиатуру напрямую и ждут человека. Перед каждым из них должна стоять проверка из прошлого поста — иначе в неинтерактивной среде программа зависнет. Правило простое: любой вопрос пользователю должен иметь альтернативу в виде аргумента командной строки.
И не увлекайтесь. Мастер из десяти вопросов раздражает, если то же самое можно было передать одной строкой. Хороший ориентир: спрашивайте только то, что нельзя разумно угадать, и обязательно показывайте, какую команду нужно было написать, чтобы обойтись без вопросов:
AnsiConsole.MarkupLine(
$"[grey]Подсказка: myapp deploy --environment {environment}[/]");
Пользователь запустит мастер один раз, увидит готовую команду и в следующий раз напишет её сам. Ровно так учит пользоваться собой gh от GitHub.