Spectre.Console для C# (.NET): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Spectre.Console для C# (.NET): Полное руководство

Spectre.Console — это мощная библиотека для .NET, которая превращает обычный консольный вывод в нечто визуально привлекательное и интерактивное. Она позволяет разработчикам C# создавать красивые, структурированные и информативные интерфейсы командной строки (CLI) с минимальными усилиями. Вместо скучного монохромного текста вы получаете поддержку цветов, таблиц, деревьев, прогресс-баров, диаграмм и даже интерактивных подсказок.

Зачем нужна Spectre.Console? В современной разработке консольные приложения часто используются для DevOps-инструментов, скриптов автоматизации, анализа данных и утилит. Пользователи ожидают от них не только функциональности, но и удобства. Spectre.Console решает проблему «голого» вывода, делая логи понятными, а взаимодействие с приложением — приятным. Она особенно полезна для создания профессиональных CLI-инструментов, где важна читаемость и визуальная иерархия данных.

Библиотека построена на принципах композиции и предоставляет богатый набор «виджетов» (widgets), которые можно комбинировать. Она активно поддерживается сообществом, имеет подробную документацию и используется в таких крупных проектах, как Cake Build, NuGet Client и .NET SDK. Если вы пишете на C# и хотите, чтобы ваша консольная утилита выглядела как современный инструмент, а не как relic из 80-х — Spectre.Console — ваш выбор.

Установка

Spectre.Console распространяется через NuGet. Для установки выполните одну из следующих команд в терминале или консоли диспетчера пакетов:

# Через .NET CLI
dotnet add package Spectre.Console

# Через Package Manager Console (Visual Studio)
Install-Package Spectre.Console

# Или через NuGet Package Manager в вашей IDE

После установки добавьте using Spectre.Console; в начало вашего файла с кодом.

Быстрый старт

Минимальный рабочий пример, демонстрирующий основные возможности:

using Spectre.Console;

// Простое цветное сообщение
AnsiConsole.MarkupLine("[bold green]Привет, Spectre.Console![/]");

// Таблица
var table = new Table();
table.AddColumn("Имя");
table.AddColumn("Возраст");
table.AddRow("Алиса", "30");
table.AddRow("Боб", "25");
AnsiConsole.Write(table);

// Прогресс-бар
AnsiConsole.Progress()
    .Start(ctx =>
    {
        var task = ctx.AddTask("[yellow]Обработка...[/]");
        while (!task.IsFinished)
        {
            task.Increment(15);
            Thread.Sleep(200);
        }
    });

Этот код выведет цветное приветствие, таблицу с данными и анимированный прогресс-бар.

Основные методы, функции и классы

1. AnsiConsole.MarkupLine

Сигнатура: public static void MarkupLine(string markup)

Что делает: Выводит строку с поддержкой разметки (markup) для стилизации текста (цвета, стили, ссылки).

AnsiConsole.MarkupLine("[bold yellow]Внимание![/] [red]Ошибка[/] в строке [underline]42[/].");

2. AnsiConsole.Progress

Сигнатура: public static Progress Display(ProgressContext context) (через fluent API)

Что делает: Создает и отображает интерактивный прогресс-бар с возможностью добавления нескольких задач.

AnsiConsole.Progress()
    .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn())
    .Start(ctx =>
    {
        var task1 = ctx.AddTask("[green]Загрузка файла 1[/]");
        var task2 = ctx.AddTask("[blue]Загрузка файла 2[/]");
        while (!task1.IsFinished || !task2.IsFinished)
        {
            task1.Increment(5);
            task2.Increment(3);
            Thread.Sleep(100);
        }
    });

3. Table

Сигнатура: public class Table : Renderable, IHasTableBorder

Что делает: Создает форматированную таблицу с заголовками, строками и настраиваемыми границами.

var table = new Table();
table.Border(TableBorder.Rounded);
table.AddColumn("[bold]ID[/]");
table.AddColumn("[bold]Имя[/]");
table.AddColumn("[bold]Статус[/]");
table.AddRow("1", "Алиса", "[green]Активен[/]");
table.AddRow("2", "Боб", "[red]Заблокирован[/]");
AnsiConsole.Write(table);

4. Tree

Сигнатура: public class Tree : Renderable

Что делает: Отображает иерархические данные в виде дерева с раскрывающимися узлами.

var tree = new Tree("Корень");
var node1 = tree.AddNode("Ветвь 1");
node1.AddNode("Лист 1.1");
node1.AddNode("Лист 1.2");
var node2 = tree.AddNode("Ветвь 2");
node2.AddNode("Лист 2.1");
AnsiConsole.Write(tree);

5. AnsiConsole.Prompt

Сигнатура: public static T Prompt(IPrompt prompt)

Что делает: Запрашивает ввод от пользователя с валидацией и автодополнением.

var name = AnsiConsole.Prompt(
    new TextPrompt("Введите ваше имя:")
        .PromptStyle("green")
        .ValidationErrorMessage("[red]Имя не может быть пустым[/]")
        .Validate(name => !string.IsNullOrWhiteSpace(name)));

var color = AnsiConsole.Prompt(
    new SelectionPrompt()
        .Title("Выберите цвет:")
        .AddChoices(new[] { "Красный", "Зеленый", "Синий" }));

AnsiConsole.MarkupLine($"Привет, [bold]{name}[/]! Вы выбрали [underline]{color}[/].");

6. Calendar

Сигнатура: public class Calendar : Renderable

Что делает: Отображает календарь на указанный месяц/год с подсветкой дат.

var calendar = new Calendar(2024, 12);
calendar.HighlightStyle(Style.Plain);
calendar.AddCalendarEvent(2024, 12, 25); // Рождество
AnsiConsole.Write(calendar);

7. AnsiConsole.Status

Сигнатура: public static Status Display(StatusContext context) (через fluent API)

Что делает: Отображает спиннер с текстом статуса во время выполнения длительной операции.

AnsiConsole.Status()
    .Start("Обработка...", ctx =>
    {
        ctx.Spinner(Spinner.Known.Dots);
        Thread.Sleep(3000);
        ctx.Status("Почти готово...");
        Thread.Sleep(2000);
    });
AnsiConsole.MarkupLine("[green]Готово![/]");

8. Rule

Сигнатура: public class Rule : Renderable

Что делает: Рисует горизонтальную разделительную линию (rule) с опциональным заголовком.

AnsiConsole.Write(new Rule("[yellow]Раздел 1[/]") { Style = Style.Parse("grey") });
AnsiConsole.MarkupLine("Текст раздела...");
AnsiConsole.Write(new Rule("[blue]Раздел 2[/]") { Style = Style.Parse("grey") });

9. AnsiConsole.Markup

Сигнатура: public static void Markup(string markup)

Что делает: Выводит строку с разметкой без перевода строки в конце.

AnsiConsole.Markup("[bold]Загрузка:[/] ");
for (int i = 0; i < 10; i++)
{
    AnsiConsole.Markup("[green]#[/]");
    Thread.Sleep(100);
}
AnsiConsole.MarkupLine(" [green]100%[/]");

10. Panel

Сигнатура: public class Panel : Renderable

Что делает: Оборачивает содержимое в рамку (панель) с заголовком.

var panel = new Panel("Это текст внутри панели.");
panel.Header("Информация");
panel.Border(BoxBorder.Rounded);
AnsiConsole.Write(panel);

Реальный практический пример

Создадим консольное приложение для анализа логов. Оно будет читать файл логов, отображать статистику в таблице, показывать прогресс обработки и выводить ошибки в виде дерева.

using Spectre.Console;
using System.Text.RegularExpressions;

class LogAnalyzer
{
    static async Task Main(string[] args)
    {
        AnsiConsole.Write(new Rule("[bold yellow]Анализатор логов[/]"));
        
        // Запрос пути к файлу
        var filePath = AnsiConsole.Prompt(
            new TextPrompt("Введите путь к файлу лога:")
                .PromptStyle("cyan")
                .Validate(path => File.Exists(path) ? ValidationResult.Success() : ValidationResult.Error("[red]Файл не найден[/]")));

        // Чтение и парсинг логов с прогрессом
        var logs = new List();
        var errors = new List();
        
        await AnsiConsole.Progress()
            .Columns(new TaskDescriptionColumn(), new ProgressBarColumn(), new PercentageColumn())
            .StartAsync(async ctx =>
            {
                var task = ctx.AddTask("[green]Чтение и обработка логов[/]");
                var lines = await File.ReadAllLinesAsync(filePath);
                task.MaxValue = lines.Length;
                
                foreach (var line in lines)
                {
                    var match = Regex.Match(line, @"\[(?\w+)\]\s+(?.+)");
                    if (match.Success)
                    {
                        logs.Add(new LogEntry
                        {
                            Level = match.Groups["level"].Value,
                            Message = match.Groups["message"].Value
                        });
                        
                        if (match.Groups["level"].Value == "ERROR")
                            errors.Add(match.Groups["message"].Value);
                    }
                    task.Increment(1);
                    await Task.Delay(5); // Имитация обработки
                }
            });

        // Статистика в таблице
        var table = new Table();
        table.Border(TableBorder.Rounded);
        table.AddColumn("[bold]Уровень[/]");
        table.AddColumn("[bold]Количество[/]");
        
        var groups = logs.GroupBy(l => l.Level);
        foreach (var group in groups)
        {
            var color = group.Key switch
            {
                "ERROR" => "red",
                "WARN" => "yellow",
                "INFO" => "green",
                _ => "white"
            };
            table.AddRow($"[{color}]{group.Key}[/]", group.Count().ToString());
        }
        
        AnsiConsole.Write(new Rule("[bold]Статистика[/]"));
        AnsiConsole.Write(table);

        // Дерево ошибок
        if (errors.Any())
        {
            AnsiConsole.Write(new Rule("[bold red]Ошибки[/]"));
            var tree = new Tree($"[red]Найдено {errors.Count} ошибок[/]");
            foreach (var error in errors.Take(10)) // Показываем первые 10
            {
                tree.AddNode($"[red]{error}[/]");
            }
            if (errors.Count > 10)
                tree.AddNode($"[grey]... и еще {errors.Count - 10} ошибок[/]");
            AnsiConsole.Write(tree);
        }
        
        // Интерактивный запрос на сохранение отчета
        if (AnsiConsole.Confirm("Сохранить отчет?"))
        {
            var reportPath = "report.txt";
            await File.WriteAllTextAsync(reportPath, $"Всего записей: {logs.Count}\nОшибок: {errors.Count}");
            AnsiConsole.MarkupLine($"[green]Отчет сохранен в {reportPath}[/]");
        }
    }

    class LogEntry
    {
        public string Level { get; set; }
        public string Message { get; set; }
    }
}

Советы по производительности и типичные ошибки

Производительность

  • Избегайте частых вызовов AnsiConsole.Write внутри циклов. Вместо этого собирайте виджеты в список и выводите их одним вызовом.
  • Используйте MarkupLine вместо форматирования строк. Разметка обрабатывается быстрее, чем ручное создание Style объектов.
  • Для прогресс-баров с большим количеством итераций уменьшайте частоту обновления (например, Increment(10) вместо Increment(1)).
  • Отключайте анимацию в CI/CD средах с помощью AnsiConsole.Profile.Capabilities.Interactive = false;.

Типичные ошибки

  • Забыли using Spectre.Console; — самая частая ошибка новичков.
  • Неправильный синтаксис разметки. Всегда закрывайте теги: [bold]текст[/], а не [bold]текст.
  • Использование AnsiConsole.MarkupLine с обычным текстом. Если текст содержит квадратные скобки, их нужно экранировать: [[] и []].
  • Попытка вывести слишком много строк в таблицу. Таблицы Spectre.Console рендерятся целиком в памяти, что может вызвать проблемы с производительностью при тысячах строк. Используйте пагинацию.
  • Игнорирование асинхронных методов. Методы типа StartAsync должны вызываться с await, иначе прогресс-бар не будет обновляться.

Ссылки

Рекомендаций