.NET でコンソールアプリケーションを開発するときの悩みの種が、コマンドライン引数の解析。 .NET Framework がコマンドラインパーサーを提供してくれないので、 仕方なくオレオレパーサーを書いたり、Mono にあるライブラリを使ったりしてきたけど、 .NET Core 時代になってようやく Microsoft がライブラリを提供してくれた。
cURL みたいな、サブコマンドを持たない CLI を書くならこんな感じになる。
using System; using Microsoft.Extensions.CommandLineUtils; namespace SampleCli { class Program { static void Main(string[] args) { var app = new CommandLineApplication(throwOnUnexpectedArg: false); app.Name = nameof(SampleCli); app.Description = "cURL みたいな単機能 CLI"; app.HelpOption("-h|--help"); var methodOption = app.Option( template: "--method", description: "HTTP メソッド", optionType: CommandOptionType.SingleValue); var urlArgument = app.Argument( name: "url", description: "URL", multipleValues: false); app.OnExecute(() => { if (urlArgument.Value == null) { app.ShowHelp(); return 1; } var method = "GET"; if (methodOption.HasValue()) { method = methodOption.Value(); } Console.WriteLine($"{method} {urlArgument.Value}"); return 0; }); app.Execute(args); } } }
Command メソッドを使えばサブコマンドも追加できるので、Git みたいな多機能な CLI も夢じゃない。
using System; using Microsoft.Extensions.CommandLineUtils; namespace SampleCli { class Program { static int Main(string[] args) { var app = new CommandLineApplication(throwOnUnexpectedArg: false); app.Name = nameof(SampleCli); app.Description = "Git みたいな多機能 CLI"; app.HelpOption("-h|--help"); app.Command("clone", command => { command.Description = "リポジトリをクローンします。"; command.HelpOption("-h|--help"); var urlArgument = command.Argument("url", "リポジトリの URL"); command.OnExecute(() => { if (urlArgument.Value == null) { command.ShowHelp(); return 1; } Console.WriteLine($"clone {urlArgument.Value}"); return 0; }); }); app.Command("remote", remoteCommand => { remoteCommand.Description = "リモート一覧を表示します。"; remoteCommand.HelpOption("-h|--help"); remoteCommand.Command("add", addCommand => { addCommand.Description = "リモートを追加します。"; addCommand.HelpOption("-h|--help"); var nameArgument = addCommand.Argument("name", "リモートの名前"); var urlArgument = addCommand.Argument("url", "リポジトリの URL"); addCommand.OnExecute(() => { if (nameArgument.Value == null || urlArgument.Value == null) { addCommand.ShowHelp(); return 1; } Console.WriteLine($"remote add {nameArgument.Value} {urlArgument.Value}"); return 0; }); }); remoteCommand.OnExecute(() => { Console.WriteLine("origin"); Console.WriteLine("staging"); Console.WriteLine("production"); return 0; }); }); return app.Execute(args); } } }
サブコマンドにも対応できるくらいに高機能。自分のユースケースは全て満たしているので、もうこればかり使っている。オレオレパーサーは窓から投げ捨てた。