.Net Core でのコマンドライン解析

コマンドラインのパースがしたいぞ

昔はコマンドライン(CLI)アプリを作ることが多くて,色々コマンドラインを解析するためのライブラリを作ったり探したりしていたのだけれど,久しぶりにコマンドラインで動作するアプリを作る際に,コマンドライン解析してくれるの何か無いかなと探したのでメモ.

ConsoleAppFramework

おなじみCysharpのやつ.Generic Host の上に乗っかってて,構成やらロギング,DIなど ASP.NET Core 周りでおなじみのやり方を使える.コマンド周りは,おなじみのメタプログラミング全開の構成.以前CLIアプリを作った際にはお世話になったが今回はパス.

command-line-api

 何度もコマンドライン解析のライブラリを作ってきては捨てていった Microsoft さん.リポジトリを見ると,なんと名前空間が System.CommandLine .これは本気なのかとちょっと漁ると System.CommandLine の概要 | Microsoft Learn ってすごいところにドキュメント作ってるな,息長く続くのかなとちょっとサンプルを写経してみた.

準備

dotnet new console --use-program-main
dotnet add package System.CommandLine --prerelease
namespace CliSample;

class Program
{
    static void Main(string[] args) {
        Console.WriteLine("Hello, World!");
    }
}

簡単なCatみたいなプログラムを書いてみる.

using System.CommandLine;

namespace CliSample;

class Program
{
    static async Task<int> Main(string[] args) {
        var lineNumbersOption = new Option<bool>(new[] { "--number", "-n" }, "行番号を表示");
        var filesArgument = new Argument<List<FileInfo>>("file", "ファイル");
        var rootCommand = new RootCommand("CliSample") {
            filesArgument,
            lineNumbersOption
        };
        rootCommand.SetHandler(ReadFile, filesArgument, lineNumbersOption);
        return await rootCommand.InvokeAsync(args);
    }

    static void ReadFile(List<FileInfo> files, bool lineNumbers) {
        files.ForEach(ReadFile);
        void ReadFile(FileInfo file) {
            File.ReadLines(file.FullName).Select(Format).ToList().ForEach(Console.WriteLine);
            string Format(string l, int n) {
                return string.Format("{0}{1}", lineNumbers ? $"{n + 1:D4} : " : "", l);
            }
        }
    }
}

オプションとか,引数のオブジェクトを作って,コマンドに渡してあげてハンドラを作って Invoke するのね.なるほど.

$ dotnet run -- --help
Description:
  CliSample

Usage:
  CliSample [<file>...] [options]

Arguments:
  <file>  ファイル

Options:
  -n, --number    行番号を表示
  --version       Show version information
  -?, -h, --help  Show help and usage information

ConsoleAppFramework はパブリッシュするときにトリムすると色々な事になっていたのが, こいつはトリム対応を正式に謳ってるだけあって,プロパティに属性を付けて自動で設定されるとか, クラスに属性を付けて自動でディスパッチされて呼び出されるとかそういった使い方では無く,素直な印象だった.

リポジトリ全体を覗いてみると,引数を解析するだけで無く, コンソールアプリケーションを作成する基本的な仕組みを提供しようとしているように感じられる.

System.CommandLine.Rendering や System.CommandLine.Hosting といった名前空間も見受けられるので もっと色々出来そうだけれど,ひとまずはサクッと作るには良い感じで出来た.

System.CommandLine.DragonFruit がなにげにすごいのだけれど, それは別で紹介してた人が居たので割愛.