C#でバッチ処理作る機会があり、ベースになるフレームワークがないかと探していたらEntryPoint
というライブラリを見つけました。
CLIコマンドを保守する上でとても便利なライブラリだと感じました。ですが、日本語の記事が見つからなかったので実装例をメモしておきます。
Nick-Lucas/EntryPoint
https://github.com/Nick-Lucas/EntryPoint
環境
$ dotnet --version
3.1.200
手元にwindowsがなくmacを使用しました。基本的にwindowsでも同じだと思います。
(前はwindwsでビルドしました。)
プロジェクト作成
$ dotnet new console -n QiitaEntryPoint -o QiitaEntryPoint
cd QiitaEntryPoint
dotnet add package EntryPoint
QiitaEntryPointというプロジェクトを作成し、EntryPointをnugetでインストールします。
ディレクトリ構成
$ tree
.
├── CommandLine
│ ├── CliCommands.cs
│ ├── Primary
│ │ ├── PrimaryCliCommand.cs
│ │ └── PrimaryCliCommandArgs.cs
│ └── Secondary
│ └── SecondaryCliCommand.cs
├── Program.cs
└── QiitaEntryPoint.csproj
最終的には上記のようになります。
サンプルコードを参考にしつつ、コマンドの処理が膨らんでもファイルを作りやすいようにコマンドごとにディレクトリを切りました。
実装
今回はPrimaryコマンドとSecondaryコマンドを用意しました。
Primaryコマンド
Primaryコマンドの処理内容となります。
コマンドライン引数と対応したクラス「PrimaryCliCommandArgs」を引数に取ります。
using System;
namespace QiitaEntryPoint.CommandLine.Primary
{
public class PrimaryCliCommand
{
public void Handle(PrimaryCliCommandArgs args)
{
if (args.AddDay)
{
Console.WriteLine($"Hello. {args.Message} by {DateTime.Today.ToShortDateString()}");
}
else
{
Console.WriteLine(args.Message);
}
}
}
}
こちらはPrimaryコマンドのコマンドライン引数と対応したクラスです。
「BaseCliArguments」を継承します。
using EntryPoint;
namespace QiitaEntryPoint.CommandLine.Primary
{
public class PrimaryCliCommandArgs : BaseCliArguments
{
public PrimaryCliCommandArgs() : base("Primary Command") { }
[Required]
[OptionParameter(ShortName: 'm',
LongName: "message")]
[Help("Output message")]
public string Message { get; set; }
[Option(ShortName: 'd',
LongName: "day")]
[Help("add execute day")]
public bool AddDay { get; set; }
}
}
下記のコマンドでMessage
に対して"I am Engineer"がAddDay
に対して"true"が入ってくるイメージです。
$ dotnet run primary -- -m "I am Engineer" -d
[Required]
を使用することでその引数は必須になります。
$ dotnet run primary
Arguments Error:
The option -m/--message was not included, but is a required option
また、LongNameで引数名を指定して、ShortNameで引数の省略形を設定できます。
他にもいろいろ機能があるので以下のサンプルコードを参考にすると良いです。直感的に理解できると思います。
ヘルプを表示するとこんな感じです。
$ dotnet run primary -- --help
Primary Command v1 Documentation
Usage:
QiitaEntryPoint [ -o | --option ] [ -p VALUE | --parameter VALUE ] [ operands ]
Arguments:
-d --day
add execute day
-h --help
Displays Help information about arguments when set
-m --message [String]
REQUIRED
Output message
Secondaryコマンド
Secondaryコマンドは引数を受け取らないシンプルなコマンドとします。
using System;
namespace QiitaEntryPoint.CommandLine.Secondary
{
public class SecondaryCliCommand
{
public void Handle()
{
Console.WriteLine("Called secondaryCliCommand");
}
}
}
処理の振り分け
このクラスは各コマンドのファサードになっており、各コマンドの処理へと振り分けを行います。
「BaseCliCommands」を継承します。
using EntryPoint;
using QiitaEntryPoint.CommandLine.Primary;
using QiitaEntryPoint.CommandLine.Secondary;
namespace QiitaEntryPoint.CommandLine
{
public class CliCommands : BaseCliCommands
{
[DefaultCommand]
[Command("primary")]
[Help("The Main command")]
public void Primary(string[] args)
{
var options = Cli.Parse<PrimaryCliCommandArgs>(args);
var command = new PrimaryCliCommand();
command.Handle(options);
}
[Command("secondary")]
[Help("The Secondary command")]
public void Secondary(string[] args)
{
var command = new SecondaryCliCommand();
command.Handle();
}
}
}
ここも直感的ですが、CommandAttribute
を設定することで、コマンドと処理の紐付けが行われます。
[DefaultCommand]
を設定すると、コマンド名を省略したときのデフォルト操作を指定することができます。
Cli.Parse<PrimaryCliCommandArgs>(args);
を実行することで、型引数にコマンドライン引数をパースしたインスタンスを取得できます。
ヘルプを表示するとこんな感じです。
$ dotnet run -- --help
Commands for QiitaEntryPoint
Usage:
QiitaEntryPoint [COMMAND] [COMMAND ARGUMENTS]
For Command Help:
QiitaEntryPoint [COMMAND] --help
PRIMARY [DEFAULT]
The Main command
SECONDARY
The Secondary command
Main関数
最後にエントリーポイントを実装します。
using EntryPoint;
using QiitaEntryPoint.CommandLine;
namespace QiitaEntryPoint
{
class Program
{
static void Main(string[] args)
{
Cli.Execute<CliCommands>(args);
}
}
}
実行
3つのパターンで実行してみます。
$ dotnet run -- -m "I am Engineer" -d
Hello. I am Engineer by 2020/11/02
$ dotnet run primary -- -m "I am Engineer"
I am Engineer
$ dotnet run secondary
Called secondaryCliCommand
最後に
バッチ処理の実装方法はいろいろあると思いますが、こちらのライブラリを使用することで初見にも優しいソースコードになると思います。(CommandAttributeから追えば良いので)
また、CLIコマンド特有のヘルプ処理などをAttributeで切り出すことにより、本質的な実装に集中できるとともにクリーンなコードを書くことができると感じました。
今回使用したソースコードです。
https://github.com/ishiyama0530/qiita-EntryPoint