Spectre.Console

.NETのための美しいコンソールアプリケーションを作成するライブラリ。リッチなテーブル、チャート、プロンプトを提供。

csharpclicommand-lineuiconsole

GitHub概要

spectreconsole/spectre.console

A .NET library that makes it easier to create beautiful console applications.

スター10,443
ウォッチ82
フォーク576
作成日:2020年7月21日
言語:C#
ライセンス:MIT License

トピックス

ansi-colorsconsoleconsole-tablesconsole-visualizationdotnetdotnet-coredotnet-standard

スター履歴

spectreconsole/spectre.console Star History
データ取得日時: 2025/7/25 11:09

フレームワーク

Spectre.Console

概要

Spectre.Consoleは、.NETのための美しいコンソールアプリケーションを作成するライブラリです。リッチなテーブル、チャート、プロンプトを提供し、美しいCLI出力と対話的な機能により人気が急上昇しています。モダンな.NETアプリケーションで広く採用されており、従来の単調なコンソール出力を劇的に改善します。

詳細

Spectre.Consoleは、コンソールアプリケーションに豊かな視覚体験をもたらすモダンなライブラリです。テキストスタイリング、テーブル、プログレスバー、チャート、対話的プロンプトなど、多様なUI要素を提供します。.NET 5+、.NET Framework、.NET Standardで利用可能で、クロスプラットフォーム対応しています。

主な特徴

  • 豊富なテキストスタイリング: 色、太字、斜体、下線など多様なスタイル
  • テーブル表示: 美しく整理されたテーブル表示機能
  • プログレスバー: 単一・複数のプログレスバー表示
  • 対話的プロンプト: 選択、入力、確認などのユーザー対話
  • チャート: バーチャート、ブレークダウンチャートなど
  • レイアウト: パネル、グリッド、カラムなどのレイアウト機能
  • ライブ表示: リアルタイムでの表示更新
  • マークアップ記法: HTMLライクなマークアップでのスタイリング
  • カラーサポート: 豊富な色のサポートと自動色調整

メリット・デメリット

メリット

  • 美しい出力: プロフェッショナルで視覚的に魅力的なコンソール出力
  • 豊富なUI要素: テーブル、チャート、プロンプトなど多様な要素
  • 使いやすいAPI: 直感的で学習しやすいAPIデザイン
  • クロスプラットフォーム: Windows、Linux、macOSで動作
  • パフォーマンス: 高速で効率的な描画エンジン
  • 拡張性: カスタムレンダラーやスタイルの作成が可能
  • アクティブな開発: 継続的な機能追加とバグ修正

デメリット

  • 学習コスト: 豊富な機能ゆえに習得に時間が必要
  • 依存関係: 外部ライブラリへの依存が追加される
  • ターミナル依存: ターミナルの機能に依存する部分がある
  • オーバーヘッド: シンプルなアプリケーションには過剰な場合がある

主要リンク

書き方の例

基本的なテキストスタイリング

using Spectre.Console;

class Program
{
    static void Main(string[] args)
    {
        // 基本的なテキストスタイリング
        AnsiConsole.WriteLine("普通のテキスト");
        AnsiConsole.MarkupLine("[red]赤いテキスト[/]");
        AnsiConsole.MarkupLine("[bold blue]太字の青いテキスト[/]");
        AnsiConsole.MarkupLine("[italic green]斜体の緑のテキスト[/]");
        AnsiConsole.MarkupLine("[underline yellow]下線付きの黄色いテキスト[/]");

        // 背景色付き
        AnsiConsole.MarkupLine("[white on red]白文字に赤背景[/]");
        
        // 複合スタイル
        AnsiConsole.MarkupLine("[bold italic underline red]すべてのスタイル適用[/]");

        // Ruleで区切り線
        AnsiConsole.Write(new Rule("[green]セクション区切り[/]"));

        // Panelでボックス表示
        AnsiConsole.Write(
            new Panel("[yellow]これはパネル内のテキストです[/]")
                .Header("情報パネル")
                .Border(BoxBorder.Rounded)
                .BorderColor(Color.Blue));
    }
}

テーブル表示の例

using Spectre.Console;

class Program
{
    static void Main(string[] args)
    {
        // 基本的なテーブル
        var table = new Table();
        
        // カラムを追加
        table.AddColumn("名前");
        table.AddColumn(new TableColumn("年齢").Centered());
        table.AddColumn(new TableColumn("部署").RightAligned());

        // 行を追加
        table.AddRow("田中太郎", "30", "開発部");
        table.AddRow("佐藤花子", "25", "営業部");
        table.AddRow("鈴木一郎", "35", "企画部");

        // スタイルを設定
        table.Border(TableBorder.Rounded);
        table.BorderColor(Color.Blue);
        table.Title("社員一覧");
        table.Caption("2024年度データ");

        AnsiConsole.Write(table);

        AnsiConsole.WriteLine();

        // より高度なテーブル
        var advancedTable = new Table()
            .Border(TableBorder.Square)
            .BorderColor(Color.Red)
            .Title("[yellow]プロジェクト進捗[/]");

        advancedTable.AddColumn("[green]プロジェクト名[/]");
        advancedTable.AddColumn(new TableColumn("[blue]進捗率[/]").Centered());
        advancedTable.AddColumn(new TableColumn("[red]ステータス[/]").RightAligned());
        advancedTable.AddColumn("[purple]担当者[/]");

        // 条件付きでスタイルを適用
        advancedTable.AddRow(
            "Webサイト改修", 
            "[green]85%[/]", 
            "[green]進行中[/]", 
            "田中");
        
        advancedTable.AddRow(
            "モバイルアプリ", 
            "[yellow]60%[/]", 
            "[yellow]遅延[/]", 
            "佐藤");
        
        advancedTable.AddRow(
            "API開発", 
            "[red]30%[/]", 
            "[red]要注意[/]", 
            "鈴木");

        AnsiConsole.Write(advancedTable);
    }
}

プログレスバーの例

using Spectre.Console;
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // 単一プログレスバー
        await AnsiConsole.Progress()
            .StartAsync(async context =>
            {
                var task = context.AddTask("[green]ファイルをダウンロード中...[/]");

                while (!task.IsFinished)
                {
                    await Task.Delay(100);
                    task.Increment(2);
                }
            });

        AnsiConsole.MarkupLine("[green]ダウンロード完了![/]");
        AnsiConsole.WriteLine();

        // 複数プログレスバー
        await AnsiConsole.Progress()
            .Columns(new ProgressColumn[] 
            {
                new TaskDescriptionColumn(),    // タスク説明
                new ProgressBarColumn(),        // プログレスバー
                new PercentageColumn(),         // パーセンテージ
                new RemainingTimeColumn(),      // 残り時間
                new SpinnerColumn(),           // スピナー
            })
            .StartAsync(async context =>
            {
                var downloadTask = context.AddTask("[blue]ファイルダウンロード[/]", maxValue: 100);
                var processTask = context.AddTask("[yellow]データ処理[/]", maxValue: 50);
                var uploadTask = context.AddTask("[green]アップロード[/]", maxValue: 30);

                // 並行処理をシミュレート
                var tasks = new[]
                {
                    SimulateWork(downloadTask, 50),
                    SimulateWork(processTask, 80),  
                    SimulateWork(uploadTask, 120)
                };

                await Task.WhenAll(tasks);
            });

        AnsiConsole.MarkupLine("[green]すべての処理が完了しました![/]");
    }

    static async Task SimulateWork(ProgressTask task, int delay)
    {
        while (!task.IsFinished)
        {
            await Task.Delay(delay);
            task.Increment(1);
        }
    }
}

対話的プロンプトの例

using Spectre.Console;
using System;
using System.ComponentModel.DataAnnotations;

class Program
{
    static void Main(string[] args)
    {
        AnsiConsole.MarkupLine("[bold blue]ユーザー情報の入力[/]");
        AnsiConsole.WriteLine();

        // テキスト入力
        var name = AnsiConsole.Ask<string>("お名前を入力してください:");

        // パスワード入力(マスク)
        var password = AnsiConsole.Prompt(
            new TextPrompt<string>("パスワードを入力してください:")
                .PromptStyle("red")
                .Secret());

        // 数値入力(バリデーション付き)
        var age = AnsiConsole.Prompt(
            new TextPrompt<int>("年齢を入力してください:")
                .PromptStyle("green")
                .ValidationErrorMessage("[red]有効な数値を入力してください[/]")
                .Validate(age =>
                {
                    return age switch
                    {
                        <= 0 => ValidationResult.Error("[red]年齢は正の数である必要があります[/]"),
                        >= 150 => ValidationResult.Error("[red]年齢が現実的ではありません[/]"),
                        _ => ValidationResult.Success(),
                    };
                }));

        // 選択プロンプト
        var department = AnsiConsole.Prompt(
            new SelectionPrompt<string>()
                .Title("所属部署を選択してください:")
                .PageSize(10)
                .MoreChoicesText("[grey](上下キーで選択、Enterで決定)[/]")
                .AddChoices(new[] {
                    "開発部", "営業部", "企画部", "人事部", "経理部", "総務部"
                }));

        // 複数選択プロンプト
        var skills = AnsiConsole.Prompt(
            new MultiSelectionPrompt<string>()
                .Title("スキルを選択してください(複数選択可):")
                .NotRequired() // 必須ではない
                .PageSize(10)
                .MoreChoicesText("[grey](スペースで選択/選択解除、Enterで決定)[/]")
                .InstructionsText(
                    "[grey](スペースキーで選択、上下キーで移動、Enterで決定)[/]")
                .AddChoices(new[] {
                    "C#", "JavaScript", "Python", "Java", "Go", 
                    "React", "Vue.js", "Angular", "Docker", "Kubernetes"
                }));

        // 確認プロンプト
        var confirm = AnsiConsole.Confirm("入力内容を保存しますか?");

        // 結果表示
        AnsiConsole.WriteLine();
        var panel = new Panel(
            $"[bold]名前:[/] {name}\n" +
            $"[bold]年齢:[/] {age}\n" +
            $"[bold]部署:[/] {department}\n" +
            $"[bold]スキル:[/] {string.Join(", ", skills)}\n" +
            $"[bold]保存:[/] {(confirm ? "[green]はい[/]" : "[red]いいえ[/]")}")
            .Header("入力結果")
            .Border(BoxBorder.Rounded)
            .BorderColor(Color.Green);

        AnsiConsole.Write(panel);

        if (confirm)
        {
            // 保存処理をシミュレート
            AnsiConsole.Status()
                .Start("データを保存中...", ctx =>
                {
                    ctx.Spinner(Spinner.Known.Star);
                    ctx.SpinnerStyle(Style.Parse("green"));
                    
                    System.Threading.Thread.Sleep(2000);
                });

            AnsiConsole.MarkupLine("[green]データが正常に保存されました![/]");
        }
    }
}

チャートとライブ表示の例

using Spectre.Console;
using System;
using System.Threading;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // バーチャート
        AnsiConsole.Write(
            new BarChart()
                .Width(60)
                .Label("[green bold underline]売上実績[/]")
                .CenterLabel()
                .AddItem("1月", 12.5, Color.Yellow)
                .AddItem("2月", 15.8, Color.Green)
                .AddItem("3月", 10.2, Color.Red)
                .AddItem("4月", 18.9, Color.Blue)
                .AddItem("5月", 22.1, Color.Purple));

        AnsiConsole.WriteLine();

        // ブレークダウンチャート
        AnsiConsole.Write(
            new BreakdownChart()
                .Width(100)
                .AddItem("開発", 45, Color.Red)
                .AddItem("テスト", 25, Color.Green)
                .AddItem("設計", 20, Color.Blue)
                .AddItem("その他", 10, Color.Yellow));

        AnsiConsole.WriteLine();

        // ライブ表示の例
        await AnsiConsole.Live(CreateStatusDisplay())
            .StartAsync(async context =>
            {
                for (int i = 0; i <= 100; i += 5)
                {
                    // 表示を更新
                    context.UpdateTarget(CreateStatusDisplay(i));
                    await Task.Delay(200);
                }
            });

        AnsiConsole.MarkupLine("[green]処理完了![/]");
    }

    static Panel CreateStatusDisplay(int progress = 0)
    {
        var grid = new Grid()
            .AddColumn()
            .AddColumn();

        grid.AddRow(
            new Panel("[bold]システム状態[/]")
                .BorderColor(Color.Blue),
            new Panel($"[bold]進行状況: {progress}%[/]")
                .BorderColor(Color.Green));

        // プログレスバーを追加
        if (progress > 0)
        {
            var progressBar = new ProgressBar()
                .Value(progress)
                .MaxValue(100);
            
            grid.AddRow(
                new Panel(progressBar)
                    .Header("処理進行中")
                    .BorderColor(Color.Yellow),
                new Panel($"[bold]残り時間: {(100-progress)/5}秒[/]")
                    .BorderColor(Color.Purple));
        }

        return new Panel(grid)
            .Header("[bold yellow]ダッシュボード[/]")
            .Border(BoxBorder.Double)
            .BorderColor(Color.Cyan1);
    }
}

カスタムレンダラーの例

using Spectre.Console;
using Spectre.Console.Rendering;
using System;
using System.Collections.Generic;

// カスタムレンダラークラス
public class CustomStatusRenderer : Renderable
{
    private readonly string _title;
    private readonly Dictionary<string, (int current, int total, Color color)> _items;

    public CustomStatusRenderer(string title)
    {
        _title = title;
        _items = new Dictionary<string, (int, int, Color)>();
    }

    public CustomStatusRenderer AddItem(string name, int current, int total, Color color)
    {
        _items[name] = (current, total, color);
        return this;
    }

    protected override IEnumerable<Segment> Render(RenderOptions options, int maxWidth)
    {
        var segments = new List<Segment>();

        // タイトル
        segments.Add(new Segment(_title, new Style(Color.Yellow, decoration: Decoration.Bold)));
        segments.Add(Segment.LineBreak);
        segments.Add(Segment.LineBreak);

        // 各アイテムのプログレス
        foreach (var (name, (current, total, color)) in _items)
        {
            var percentage = (double)current / total * 100;
            var barWidth = (int)(maxWidth * 0.6);
            var filledWidth = (int)(barWidth * current / total);

            // アイテム名
            segments.Add(new Segment($"{name}: ", new Style(Color.White)));

            // プログレスバー
            segments.Add(new Segment(new string('█', filledWidth), new Style(color)));
            segments.Add(new Segment(new string('░', barWidth - filledWidth), new Style(Color.Grey19)));

            // パーセンテージ
            segments.Add(new Segment($" {percentage:F1}% ({current}/{total})", new Style(Color.Grey)));
            segments.Add(Segment.LineBreak);
        }

        return segments;
    }
}

class Program
{
    static void Main(string[] args)
    {
        var customStatus = new CustomStatusRenderer("プロジェクト進捗状況")
            .AddItem("フロントエンド", 8, 10, Color.Green)
            .AddItem("バックエンド", 6, 12, Color.Yellow)
            .AddItem("データベース", 3, 5, Color.Red)
            .AddItem("テスト", 2, 8, Color.Blue);

        AnsiConsole.Write(customStatus);

        AnsiConsole.WriteLine();
        AnsiConsole.MarkupLine("[green]カスタムレンダラーの例[/]");
    }
}

設定とプロファイル管理の例

using Spectre.Console;
using System;
using System.IO;
using System.Text.Json;

public class UserProfile
{
    public string Name { get; set; }
    public string Email { get; set; }
    public string Department { get; set; }
    public string[] Skills { get; set; }
    public ConsoleTheme Theme { get; set; }
}

public enum ConsoleTheme
{
    Default,
    Dark,
    Light,
    Colorful
}

class Program
{
    static void Main(string[] args)
    {
        AnsiConsole.Write(
            new FigletText("Profile Manager")
                .Centered()
                .Color(Color.Green));

        var choice = AnsiConsole.Prompt(
            new SelectionPrompt<string>()
                .Title("操作を選択してください:")
                .AddChoices(new[] {
                    "新規プロファイル作成",
                    "既存プロファイル読み込み",
                    "プロファイル一覧表示",
                    "テーマ設定",
                    "終了"
                }));

        switch (choice)
        {
            case "新規プロファイル作成":
                CreateNewProfile();
                break;
            case "既存プロファイル読み込み":
                LoadExistingProfile();
                break;
            case "プロファイル一覧表示":
                DisplayProfiles();
                break;
            case "テーマ設定":
                ConfigureTheme();
                break;
            case "終了":
                AnsiConsole.MarkupLine("[yellow]アプリケーションを終了します[/]");
                break;
        }
    }

    static void CreateNewProfile()
    {
        AnsiConsole.MarkupLine("[bold blue]新規プロファイル作成[/]");
        
        var profile = new UserProfile
        {
            Name = AnsiConsole.Ask<string>("名前:"),
            Email = AnsiConsole.Ask<string>("メールアドレス:"),
            Department = AnsiConsole.Prompt(
                new SelectionPrompt<string>()
                    .Title("部署:")
                    .AddChoices("開発部", "営業部", "企画部", "人事部")),
            Skills = AnsiConsole.Prompt(
                new MultiSelectionPrompt<string>()
                    .Title("スキル:")
                    .AddChoices("C#", "JavaScript", "Python", "Docker", "AWS"))
                    .ToArray(),
            Theme = AnsiConsole.Prompt(
                new SelectionPrompt<ConsoleTheme>()
                    .Title("テーマ:")
                    .AddChoices(Enum.GetValues<ConsoleTheme>()))
        };

        // プロファイルを保存
        SaveProfile(profile);
        DisplayProfile(profile);
    }

    static void LoadExistingProfile()
    {
        // 実装省略
        AnsiConsole.MarkupLine("[yellow]この機能は未実装です[/]");
    }

    static void DisplayProfiles()
    {
        // 実装省略
        AnsiConsole.MarkupLine("[yellow]この機能は未実装です[/]");
    }

    static void ConfigureTheme()
    {
        // 実装省略
        AnsiConsole.MarkupLine("[yellow]この機能は未実装です[/]");
    }

    static void SaveProfile(UserProfile profile)
    {
        var json = JsonSerializer.Serialize(profile, new JsonSerializerOptions { WriteIndented = true });
        File.WriteAllText($"{profile.Name}_profile.json", json);
        
        AnsiConsole.MarkupLine($"[green]プロファイルを保存しました: {profile.Name}_profile.json[/]");
    }

    static void DisplayProfile(UserProfile profile)
    {
        var table = new Table()
            .Border(TableBorder.Rounded)
            .BorderColor(Color.Blue)
            .Title("プロファイル情報");

        table.AddColumn("項目");
        table.AddColumn("値");

        table.AddRow("名前", $"[bold]{profile.Name}[/]");
        table.AddRow("メール", profile.Email);
        table.AddRow("部署", $"[blue]{profile.Department}[/]");
        table.AddRow("スキル", string.Join(", ", profile.Skills));
        table.AddRow("テーマ", $"[green]{profile.Theme}[/]");

        AnsiConsole.Write(table);
    }
}

プロジェクトファイル例

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Spectre.Console" Version="0.47.0" />
    <PackageReference Include="System.Text.Json" Version="6.0.0" />
  </ItemGroup>

</Project>