C#でCUIアプリケーションを制作する際に役立つライブラリを作成したので紹介します。
C#で軽いスクリプトなどを書く機会が多々あったので作りました。
勿論大規模なソフトウェアに組み込むことも可能です。
ライブラリ情報
- 名称: CuiLib
- 使用言語: C#
- Nuget: https://www.nuget.org/packages/CuiLib
- GitHub: https://github.com/Funny-Silkie/CuiLib
- 対象バージョン: .NET7.0
- ライセンス: MIT
機能
- コマンドを表す
Command
クラスの実装- サブコマンドも実装可能
-
OnExecution()
(同期)またはOnExecutionAsync()
(非同期)メソッドをオーバーライドすることでコマンドの処理を記述 - ヘルプメッセージの出力を実装
- 引数解析の実装
- オプションを表す
Option<T>
クラスとオプション以外の値を表すParameter<T>
(パラメータ)クラスの定義 - オプションの名前は
-n
と--name
の2種類を定義可能 -
tar -czvf FILE
のような複数のフラグ+値を表す短いオプションの組み合わせの読み取りも可能 -
ValueConverter<TIn, TOut>
の実装によって文字列からの値(オプション・パラメータ)の変換をカスタマイズ可能 -
ValueChecker<T>
の実装によって値のチェック&例外スローをカスタマイズ可能
- オプションを表す
- OS依存なし(普通はそう)
とご覧になってお分かりの通り,引数の解析がメインです。
Main
メソッドに与えられる string[] args
を解析して実行すべき適切なコマンドを選択し,オプションとパラメータの値を与えた上で処理を実行します。
サンプルコード
文章でうだうだ述べるよりかはサンプルを見たほうが分かりやすいと思うので幾つか置いておきます。
何れのサンプルも以下の想定です。
- NuGetでライブラリをインストール
- アセンブリ名は
Sample
,出力実行ファイル名はSample.exe
- 実行環境はWindows10
サブコマンド無し,パラメータ取得
名前をパラメータとして入力すると最初の名前について「[一人目], you're the first!」と出力し,二つ目以降の名前をその他として出力するサンプルプログラム。
using CuiLib;
using CuiLib.Commands;
using CuiLib.Log;
using CuiLib.Options;
internal class MainCommand : Command
{
// ロガー
private readonly Logger logger;
// オプション
private readonly FlagOption optionHelp;
// パラメータ
private readonly Parameter<string> paramFirst;
private readonly Parameter<string> paramOthers;
public MainCommand() : base("Sample")
{
logger = new Logger()
{
ConsoleStdoutLogEnabled = true, // 標準出力にリダイレクト
};
Description = "サンプルコマンドです";
// -hまたは--helpのフラグオプション
optionHelp = new FlagOption('h', "help")
{
Description = "ヘルプを出力します",
};
// オプションの登録
Options.Add(optionHelp);
// 最初の一つのパラメータの値
paramFirst = Parameter.Create<string>("1st", 0);
paramFirst.Description = "最初の値";
// 二つ目以降のパラメータの値
paramOthers = Parameter.CreateAsArray<string>("others", 1);
paramOthers.Description = "二つ目以降の値";
// パラメータの登録
Parameters.Add(paramFirst);
Parameters.Add(paramOthers);
}
protected override void OnExecution()
{
// フラグはValueプロパティで指定されたか否かを表す
if (optionHelp.Value)
{
WriteHelp(logger);
return;
}
// 一つの値を取るパラメータはValueプロパティから値を取得
string first = paramFirst.Value;
// 複数の値を取るパラメータはValuesプロパティから値を取得
string[] others = paramOthers.Values ?? Array.Empty<string>();
Console.WriteLine($"{first}, you're the first!");
Console.WriteLine("--others--");
Console.WriteLine($"{string.Join(", ", others)}");
}
private static void Main(string[] args)
{
var command = new MainCommand();
try
{
// コマンドの実行
command.Invoke(args);
}
// 引数解析エラー時にスロー
catch (ArgumentAnalysisException e)
{
Console.Error.WriteLine("引数解析エラー!");
Console.Error.WriteLine(e.Message);
}
}
}
実行例
> .\Sample.exe -h
Sample
Description:
サンプルコマンドです
Usage:
Sample [-h] <1st> <others ..>
Options:
-h, --help ヘルプを出力します
Parameters:
1st 最初の値
others 二つ目以降の値
> .\Sample.exe takahashi suzuki sato
takahashi, you're the first!
--others--
suzuki, sato
サブコマンド無し,オプション取得
0から指定した値までの自然数を列挙するサンプルプログラムです。
--max
オプションで最大値(ここでは1-30)を,-o
or --out
オプションで出力先ファイル(無指定の場合は標準出力)を指定します。
internal class MainCommand : Command
{
// ロガー
private readonly Logger logger;
// オプション
private readonly FlagOption optionHelp;
private readonly SingleValueOption<TextWriter?> optionOut;
private readonly SingleValueOption<int> optionMax;
public MainCommand() : base("Sample")
{
logger = new Logger()
{
ConsoleStdoutLogEnabled = true, // 標準出力にリダイレクト
};
Description = "サンプルコマンドです";
// -hまたは--helpのフラグオプション
optionHelp = new FlagOption('h', "help")
{
Description = "ヘルプを出力します",
};
// -oまたは--outのオプション
optionOut = new SingleValueOption<TextWriter?>('o', "out")
{
Description = "出力先のテキストファイル\n無指定で標準出力",
DefaultValue = null, // DefaultValueで既定値を設定
};
// --maxのオプション
optionMax = new SingleValueOption<int>("max")
{
Description = "列挙する値の最大値(30以内の正の実数)",
Required = true,
// 値の条件はAND(&)やOR(|)で結合可能
Checker = ValueChecker.Larger(0) & ValueChecker.LowerOrEqual(30),
};
// オプションの登録
Options.Add(optionHelp);
Options.Add(optionOut);
Options.Add(optionMax);
}
protected override void OnExecution()
{
// フラグはValueプロパティで指定されたか否かを表す
if (optionHelp.Value)
{
WriteHelp(logger);
return;
}
// TextWriterのオプションは指定したファイルへ出力するインスタンスを返す
using TextWriter dstWriter = optionOut.Value ?? Console.Out;
int max = optionMax.Value;
for (int i = 0; i <= max; i++)
{
dstWriter.WriteLine(i);
}
}
private static void Main(string[] args)
{
var command = new MainCommand();
try
{
// コマンドの実行
command.Invoke(args);
}
// 引数解析エラー時にスロー
catch (ArgumentAnalysisException e)
{
Console.Error.WriteLine("引数解析エラー!");
Console.Error.WriteLine(e.Message);
}
}
}
実行例
> .\Sample.exe -h
Sample
Description:
サンプルコマンドです
Usage:
Sample [-h] [-o file] --max int
Options:
-h, --help ヘルプを出力します
-o, --out 出力先のテキストファイル
無指定で標準出力
--max 列挙する値の最大値(30以内の正の実数)
> .\Sample.exe
引数解析エラー!
オプション'--max'を入力してください
> .\Sample.exe --max 5
0
1
2
3
4
5
> .\Sample.exe --max 50
引数解析エラー!
値が30より大きいです
サブコマンドあり
サブコマンド cmdA
と cmdB
を定義したサンプルプログラムです。
internal class MainCommand : Command
{
// ロガー
private readonly Logger logger;
// オプション
private readonly FlagOption optionHelp;
public MainCommand() : base("Sample")
{
logger = new Logger()
{
ConsoleStdoutLogEnabled = true, // 標準出力にリダイレクト
};
Description = "サンプルコマンドです";
// サブコマンド登録
Children.Add(new CommandA());
Children.Add(new CommandB());
// -hまたは--helpのフラグオプション
optionHelp = new FlagOption('h', "help")
{
Description = "ヘルプを出力します",
};
// オプションの登録
Options.Add(optionHelp);
}
protected override void OnExecution()
{
// フラグはValueプロパティで指定されたか否かを表す
if (optionHelp.Value)
{
WriteHelp(logger);
return;
}
Console.Error.WriteLine("サブコマンドを指定してください");
}
private static void Main(string[] args)
{
var command = new MainCommand();
try
{
// コマンドの実行
command.Invoke(args);
}
// 引数解析エラー時にスロー
catch (ArgumentAnalysisException e)
{
Console.Error.WriteLine("引数解析エラー!");
Console.Error.WriteLine(e.Message);
}
}
}
// サブコマンド
internal class CommandA : Command
{
// ロガー
private readonly Logger logger;
// オプション
private readonly FlagOption optionHelp;
public CommandA() : base("cmdA")
{
logger = new Logger()
{
ConsoleStdoutLogEnabled = true,
};
Description = "サブコマンドA";
// -hまたは--helpのフラグオプション
optionHelp = new FlagOption('h', "help")
{
Description = "ヘルプを出力します",
};
// オプションの登録
Options.Add(optionHelp);
}
protected override void OnExecution()
{
// フラグはValueプロパティで指定されたか否かを表す
if (optionHelp.Value)
{
WriteHelp(logger);
return;
}
Console.WriteLine("サブコマンドAが実行されました");
}
}
// サブコマンド
internal class CommandB : Command
{
// ロガー
private readonly Logger logger;
// オプション
private readonly FlagOption optionHelp;
public CommandB() : base("cmdB")
{
logger = new Logger()
{
ConsoleStdoutLogEnabled = true,
};
Description = "サブコマンドB";
// -hまたは--helpのフラグオプション
optionHelp = new FlagOption('h', "help")
{
Description = "ヘルプを出力します",
};
// オプションの登録
Options.Add(optionHelp);
}
protected override void OnExecution()
{
// フラグはValueプロパティで指定されたか否かを表す
if (optionHelp.Value)
{
WriteHelp(logger);
return;
}
Console.WriteLine("サブコマンドBが実行されました");
}
}
実行例
> .\Sample.exe -h
Sample
Description:
サンプルコマンドです
Usage:
Sample [-h] [Subcommand]
Options:
-h, --help ヘルプを出力します
Subcommands:
cmdA サブコマンドA
cmdB サブコマンドB
> .\Sample.exe cmdA
サブコマンドAが実行されました
> .\Sample.exe cmdA -h
cmdA
Description:
サブコマンドA
Usage:
cmdA [-h]
Options:
-h, --help ヘルプを出力します
> .\Sample.exe cmdB
サブコマンドBが実行されました
> .\Sample.exe
サブコマンドを指定してください
あとがき
質問や意見,改良などがありましたがGitHubでガンガンIssueなりPull Requestなりを飛ばしてくれると大変喜びます。
このライブラリがC#でCUIアプリを作りたい民のお役に立てば幸いです。