CLI приложения #8. Когда цвет не нужен
В прошлый раз мы научились красить вывод. Теперь научимся вовремя переставать.
Проблема простая. ANSI-коды — это символы в строке. Терминал их исполняет, а файл — просто сохраняет. Перенаправьте цветной вывод:
myapp status > report.txt
И внутри report.txt окажется примерно такое:
←[32mВсё в порядке←[0m
Файл засорён, автоматический разбор сломается, а человек читать это тоже не захочет.
Хорошая новость: у мира консольных приложений на этот счёт есть договорённости, и достаточно их соблюдать.
Проверка первая: нас перенаправили?
if (Console.IsOutputRedirected)
{
// вывод уходит в файл или другой программе — цвет не нужен
}
Для потока ошибок есть отдельное свойство Console.IsErrorRedirected. Их полезно проверять по отдельности: результат вполне может уезжать в файл, пока сообщения для человека остаются на экране цветными.
Проверка вторая: переменная NO_COLOR
Есть неофициальный, но широко поддерживаемый стандарт: если в окружении задана переменная NO_COLOR с любым непустым значением, приложение должно отключить цвет. Её поддерживают десятки популярных утилит, и пользователю удобно выставить её один раз на всю систему.
Проверка третья: примитивный терминал
Переменная TERM со значением dumb означает, что перед нами терминал, который ничего не умеет. Встречается в старых средах и в некоторых системах сборки.
Проверка четвёртая: явное указание пользователя
Опция --no-color должна перебивать всё остальное. Полезна и обратная — --force-color: на серверах сборки вывод часто перенаправлен, но их веб-интерфейс цвет показывает прекрасно.
Собираем всё вместе:
internal static class ColorSupport
{
public static bool IsEnabled { get; private set; } = true;
public static void Configure(bool noColorFlag, bool forceColorFlag)
{
if (forceColorFlag)
{
IsEnabled = true;
return;
}
if (noColorFlag)
{
IsEnabled = false;
return;
}
var noColorVar = Environment.GetEnvironmentVariable("NO_COLOR");
if (!string.IsNullOrEmpty(noColorVar))
{
IsEnabled = false;
return;
}
if (Environment.GetEnvironmentVariable("TERM") == "dumb")
{
IsEnabled = false;
return;
}
IsEnabled = !Console.IsOutputRedirected;
}
}
Здесь важен порядок проверок: сначала то, что пользователь сам указал, потом настройки окружения, и только в самом конце наши догадки.
Осталось научить helper из прошлого поста молчать, когда цвет выключен:
public static string Paint(string text, string color)
=> ColorSupport.IsEnabled ? $"{color}{text}{Reset}" : text;
Одна строка — и во всём приложении цвет вывода под контролем ))).
Важно!
Цвет должен быть дополнением, а не носителем смысла. Если единственное отличие ошибки от успеха — красный оттенок, то в чёрно-белом выводе разницы не останется вовсе. Плюс около восьми процентов мужчин не различают красный и зелёный. Пишите «Ошибка:» словом, а цветом только подчёркивайте.
Проверить себя просто:
$env:NO_COLOR=1
dotnet run -- status
Если вывод стал чистым — вы всё сделали правильно.
Похоже мы изучили ручное управление цветом вывода. В следующем посте посмотрим на Spectre.Console, где всё это уже сделано за нас — вместе с таблицами, рамками и прогресс-барами.