連載
» 2018年03月16日 05時00分 公開

Dev Basics/Keyword:EntryPoint

EntryPointパッケージを使用することで、プログラムが処理可能なコマンドライン引数を構造化して、簡単に扱えるようになる。

[かわさきしんじ,Insider.NET編集部]
「Dev Basics/Keyword」のインデックス

連載「Dev Basics/Keyword」

 EntryPointはプログラムに与えられたコマンドライン引数を簡単に取り扱えるようにするためのコマンドラインパーサー。.NET Standard 1.6/.NET Framework 4.5以降に対応する。

EntryPointの使い方

 EntryPointを使用するにはNuGetからインストールする。

 EntryPointは「UtilityName [command] [-o | --options] [operands]」形式のコマンドライン引数を処理するためのパッケージだ。[command]はプログラムに与える「コマンド」を(省略可能)、[-o | --options] [operands]の部分はそのコマンド(コマンドを省略した際にはプログラム本体)に渡すオプションとなる。

 まずは[command]がない、つまりプログラムが単機能のユーティリティーの場合のEntryPointの使い方を見てみよう(こちらの方がプログラムの記述がシンプルなため)。

 プログラムに渡すオプションは、EntryPointパッケージで定義されているBaseCliArgumentsクラスの派生クラスとして記述する。例えば、以下のオプションをサポートしたいとしよう。かっこ内は長いオプション名だ。

  • -n(--name)オプション:文字列値
  • -c(--count)オプション:数値
  • -f(--flag)オプション:ブール値

 EntryPointパッケージを使用すると、これらのオプションは次のように記述できる。

public class SampleArguments1 : BaseCliArguments
{
  public SampleArguments1() : base("InsiderNet Sample Utility") { }

  [OptionParameter(ShortName: 'n', LongName: "name")]
  public string Name { get; set; }

  [OptionParameter(ShortName: 'c', LongName: "count")]
  public int Count { get; set; }

  [Option(ShortName: 'f', LongName: "flag")]
  public bool Flag { get; set; }
}

プログラムがサポートするオプションを定義するSampleArguments1クラス

 上で見たオプションの名前はOptionParameter/Option属性にShortName/LongNameパラメーターとして指定する。OptionParameter属性は「-n insider.net」のようにオプション名に続いてそのオプションにパラメーターを与える場合に使用し、「-f」のようにオプションの有無により機能を切り替えるスイッチとして使用する場合にはOption属性を使用する。

 例えば、-n(--name)オプションであればShortNameパラメーターが'o'、LongNameパラメーターが"name"となっている。OptionParameter/Option属性で修飾されたプロパティに、実際にコマンドラインで指定された値が格納される。

 後は、このクラスを使って、プログラム内で引数をパースするだけだ。実際のコード例は次のようになる。

class Program
{
  static void Main(string[] args)
  {
    var arguments = Cli.Parse<SampleArguments1>(args);
    Console.WriteLine($"Name: {arguments.Name}");
    Console.WriteLine($"Count: {arguments.Count}");
    Console.WriteLine($"Flag: {arguments.Flag}");

    Console.WriteLine($"Other: {string.Join(", ", arguments.Operands)}");
    Console.ReadKey();
  }
}

コマンドライン引数のパース

 Mainメソッドに与えられたコマンドライン引数argsをパースするには、EntryPointパッケージが提供するCli.Parseメソッドを呼び出すだけでよい。パース結果は、上で作成したSampleArguments1クラスのインスタンスに保存される。個々のオプションの値には、Name/Count/Flagプロパティを介してアクセスできる。コマンドラインでそれ以外に指定されたものはOperandsプロパティに保存される。

 例えば、このプログラムに「-n insidernet -c 100 -f abc def ghi」など、幾つかのコマンドライン引数を与えて実行した例を以下に示す。

幾つかのコマンドライン引数に指定してプログラムを実行した結果 幾つかのコマンドライン引数に指定してプログラムを実行した結果

 最初の例では、3つのオプションを全て適切に指定して、その他のオペランドも指定している(ので、それらが正しく出力されている)。次の例では、-cオプションに整数以外の値を与えたので、エラーとなっている。最後の例は-fオプションを省略したものだが、これに対応してFlagプロパティの値がfalseになっている。

 EntryPointパッケージを使用すると、このようにユーザーから与えられるコマンドライン引数のパースを行い、それらの整合性を簡単にチェックできる。必須のコマンドライン引数を作りたければ、それに対応するプロパティに対してRequired属性を付加すればよい(興味のある方は実際に試してみよう)。

 さらにEntryPointパッケージは-hオプション(--helpオプション)をデフォルトでサポートしていて、このオプションが指定された場合には、作成したクラスを基に自動的に生成したヘルプドキュメントを表示してくれる。このときに表示するドキュメントはHelp属性で指定することも可能だ。

 次に[command]を指定した場合の処理を見てみよう。これはプログラムが複数の機能を提供していて、[command]の指定によって、それらの機能のいずれかを実行してもらうことを想定している。

 コマンドを処理するにはEntryPointパッケージが提供するBaseCliCommandsクラスの派生クラスを作成し、そこにプログラムに与えるコマンドごとに、それを処理するためのメソッドを定義していく。以下は「func1」「func2」というコマンドを処理するクラスを定義したものだ。

public class SampleCommands : BaseCliCommands
{
  [DefaultCommand]
  [Command("func1")]
  public void Func1(string[] args)
  {
    var arguments = Cli.Parse<SampleArguments1>(args);
    Console.WriteLine($"Name: {arguments.Name}");
    Console.WriteLine($"Count: {arguments.Count}");
    Console.WriteLine($"Flag: {arguments.Flag}");

    Console.WriteLine($"Other: {string.Join(", ", arguments.Operands)}");
  }

  [Command("func2")]
  public void Func2(string[] args)
  {
    var arguments = Cli.Parse<SampleArguments2>(args);
    Console.WriteLine($"Foo: {arguments.Foo}");
    Console.WriteLine($"Other: {string.Join(", ", arguments.Operands)}");
  }
}

コマンドを処理するSampleCommandsクラス

 Func1メソッドには、これがデフォルトで呼び出されることをDefaultCommand属性を使って指定している。また、プログラムに「func1」コマンドが与えられたときにもこれが呼び出されることをCommand属性を使って指定している。同様に、Func2メソッドには、「func2」コマンドが与えられたときに、これが呼び出されることをCommand属性を使って指定している。

 Func1メソッドでは最初の例での処理と同じことをしている。Func2メソッドも同様だが、コマンドライン引数をパースする際の型引数がSampleArguments2クラスになっている。このクラスのコードは省略するが(SampleArguments1クラスから容易に想像できるコードだ)、2つのコマンドで処理する内容が異なり、それに応じてコマンドライン引数が異なるようであれば、ここで行っているように別々のクラスを作成するのが適切だろう。もしfunc1コマンドとfunc2コマンドで同じ意味を持つコマンドライン引数が共通に存在するのであれば、BaseCliArgumentsクラスを直接継承する共通クラスを作成して、そこから個別のコマンドを処理するクラスを派生させるようになるだろう。

 プログラムに与えたコマンドを実行するには、以下のようなコードを記述する。

class Program
{
  static void Main(string[] args)
  {
    Cli.Execute<SampleCommands>(args);
    Console.ReadKey();
  }
}

コマンドを実行するコード

 とても簡単で、Cli.Executeメソッドを呼び出すだけだ。実際に実行した結果を以下に示す。

プログラムにコマンドとそのオプションを与えて実行する例 プログラムにコマンドとそのオプションを与えて実行する例

 最初の例は「func1」コマンドを指定して実行した場合のものだ。次の例はコマンドを省略して実行したもの。3番目の例は「func2」コマンドを指定した場合の実行結果である。最後の例は、「func2」コマンドに「func1」コマンドで指定すべきコマンドライン引数を与えた場合の例。「-nオプションとかないのでコマンドライン引数が正しいか確認してね」というメッセージが表示されている。

 このように、EntryPointパッケージを使うことで、プログラムが処理できるコマンドやそのオプションを属性やプロパティ(の型)の形で構造化して定義できる。また、不適切なコマンドライン引数が与えられた場合の処理も(ある程度)自動で処理をしてくれる。


 EntryPointパッケージを使用することで、プログラムに与えるコマンドライン引数を構造化して定義できる。コマンドラインユーティリティーなどを作成しようという場合には役立つパッケージだ。

参考資料


「Dev Basics/Keyword」のインデックス

Dev Basics/Keyword

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。