🔤

【Dart】コマンドライン引数解析ライブラリargsを試す

2023/12/10に公開

はじめに

この記事dart公式のコマンドライン引数の解析ライブラリである args を触りながらキャッチアップした際の記事になります。

args | Dart Package

このライブラリーはGNUとPOSIXスタイルのオプションをサポートしており、サーバーサイドとクライアントサイドの両方のアプリケーションで動作する。

  • GNUスタイルのオプション

    1. オプションの指定方法:
      • 通常、ハイフン (-)またはダブルハイフン (—) を使ってオプションを指定します
      • 例: -a, --a
    2. オプションに値を渡す場合:
      • イコール(=)かスペースでオプションと値を区別します
      • 例: --file=file.txt-o output.txt
    3. 短縮形の結合:
      • 複数の短縮形オプションは一度に結合できます
      • 例: -abc(これは -a -b -c と同等)
    4. --version--help オプションを最低限つける
  • POSIXスタイルのオプション

    1. オプションの指定方法:
      • ハイフン(-)を使ってオプションを指定します
      • 例: -a
    2. オプションに値を渡す場合:
      • 値とオプションはスペースで区切ります
      • 例: -f file.txt
    3. 短縮形の結合:
      • POSIXでは通常、オプションは一度に一つずつ指定されます

環境構築や準備

各バージョンや環境

$ sw_vers
ProductName: macOS
ProductVersion: 13.4.1
ProductVersionExtra: (c)
BuildVersion: 22F770820d
$ dart --version
Dart SDK version: 3.2.0 (stable) (Tue Nov 14 18:26:59 2023 +0000) on "macos_arm64"

pubspec.yaml

environment:
  sdk: ^3.2.0

dependencies:
  args: ^2.4.2

dev_dependencies:
  lints: ^2.1.0
  test: ^1.24.0

1. プロジェクト作成

argsを試す為にプロジェクトを作成します。

$ dart create -t console dart-args-example
$ cd dart-args-example
$ dart run
Building package executable... 
Built dart_args_example:dart_args_example.
Hello world: 42!

2. argsパッケージ追加

以下コマンドで追加するか、

dart pub add args

pubspec.yaml に以下を追加し dart pub get を実施します。

dependencies:
  args: ^2.4.2

実装

1. シンプルな実装

プロジェクト作成時に作成された bin/dart_args_example.dart を以下に変更します。

import 'package:args/args.dart';

void main(List<String> arguments) {
  var parser = ArgParser();
  parser.addOption('mode', abbr: 'm');
  parser.addFlag('verbose', abbr: 'v');

  var results = parser.parse(arguments);
  print('mode: ${results['mode']}');
  print('verbose: ${results['verbose']}');
}

次のオプションを指定して実行してみます。

$ dart run bin/dart_args_example.dart -m debug -v
mode: debug
verbose: true
# 又は dart run bin/dart_args_example.dart --mode debug --verbose

上記の処理ではまずparse処理を行う ArgParser を作成しています。

次に特定の文字列等を指定する際に使用する addOption とフラグ値(true/false) を指定する際に使用する addFlag を使用してオプションを指定していきます。

最後に parse で引数を解析します。戻り値としては ArgResults が返却されます。

1-1. addOption

  • allowed で設定できる値を指定できます
    • parser.addOption('mode', abbr: 'm', allowed: ['debug', 'release']);
  • defaultsTo でデフォルト値を指定できます
    • parser.addOption('mode', abbr: 'm', defaultsTo: 'debug');

1-2. addFlag

  • デフォルトで、オプションを無効にする「no-」プレフィックスがつく
    • 上記の例だと dart run bin/dart_args_example.dart -m debug --no-verbose と実行すると verboseが false になります
    • longオプションのみ
  • 「no-」プレフィックスを無効にする
    • negatablefalse で指定します。
    • parser.addFlag('verbose', abbr: 'v', negatable: false);
    • --no-verbose を指定すると例外を投げる様になります
  • defaultsTo でデフォルト値を指定できます
    • parser.addFlag('verbose', abbr: 'v', defaultsTo: true);

2. コマンド定義

公式のドキュメントの例にあるように以下のコマンドを定義してみたいと思います。

dart run bin/dart_args_example.dart commit -a

新たに commit コマンドを定義し、 commit コマンドのオプションとして -a を定義します。

コマンドは addCommand で定義できます。

void main(List<String> arguments) {
  var parser = ArgParser();
  parser.addOption('mode', abbr: 'm', allowed: ['debug', 'release']);
  parser.addFlag('verbose', abbr: 'v', defaultsTo: true);

  // command: commit
  var command = parser.addCommand('commit');
  command.addFlag('all', abbr: 'a');

  var results = parser.parse(arguments);
  print('mode: ${results['mode']}');
  print('verbose: ${results['verbose']}');
  var commandResults = results.command;
  if (commandResults != null) {
    print('command: ${commandResults.name}');
    print('command --all: ${commandResults['all']}');
  }
}

以下のコマンドを実施してみます。

$ dart run bin/dart_args_example.dart -m debug -v commit -a
mode: debug
verbose: true
command: commit
command --all: true

コマンドの前のオプション指定はあまり無いかもですが、ちゃんとコマンドが解析できています。

ここで試しに2つコマンドを登録して、2つコマンドを指定して実行したらどうなるか試してみたいと思います。

void main(List<String> arguments) {
  var parser = ArgParser();
  parser.addOption('mode', abbr: 'm', allowed: ['debug', 'release']);
  parser.addFlag('verbose', abbr: 'v', defaultsTo: true);

  // command: commit
  var command = parser.addCommand('commit');
  command.addFlag('all', abbr: 'a');

  // command: stash
  var stash = parser.addCommand('stash');
  stash.addFlag('all', abbr: 'a');

  var results = parser.parse(arguments);
  print('mode: ${results['mode']}');
  print('verbose: ${results['verbose']}');
  var commandResults = results.command;
  if (commandResults != null) {
    print('command: ${commandResults.name}');
    print('command --all: ${commandResults['all']}');
  }
}

先ほどの実装に stash コマンドを追加し、オプションは同じ all を設定してみました。

これで実行してみると、

$ dart run bin/dart_args_example.dart -m debug -v stash --no-all commit -a
mode: debug
verbose: true
command: stash
command --all: true

コマンドに関しては先に指定したコマンドが採用されているのですが、オプションは最後の commit の方の all が採用されています。

3. コマンドのディスパッチ

コマンドベースのアプリケーションを作成する場合は、CommandRunnerクラスとCommandクラスを使用して特化した構成にすることができるそうです。

3-1. CommandRunner

  • コマンドライン引数に基づいてCommandにディスパッチする機能
  • フラグや無効な引数を処理するためのサポート

3-2. Command

  • コマンドにサブコマンドがなく、実行することを目的としている場合、そのコマンドは「リーフ コマンド」と呼ばれる
  • リーフコマンドはrunをオーバーライドする必要がある
  • サブコマンドを含むコマンドは addSubcommand で登録する必要がある

3-3. CommandRunnerとCommandで置き換え

先ほどのサンプルをCommandRunnerとCommandで置き換えてみたいと思います。

先に CommitCommand クラスと StashCommand クラスを作成します。

  • CommitCommandクラス (lib/commit_command.dart)

    import 'package:args/command_runner.dart';
    
    class CommitCommand extends Command {
      
      String get name => 'commit';
    
      
      String get description => 'Record changes to the repository.';
    
      CommitCommand() {
        // ここでコマンド固有の引数を追加できる。argParserは内部で作成される。
        argParser.addFlag('all', abbr: 'a');
      }
    
      
      void run() {
        print("CommitCommand --all: ${argResults?['all']}");
      }
    }
    
  • StashCommandクラス (lib/stash_command.dart)

    import 'package:args/command_runner.dart';
    
    class StashCommand extends Command {
      
      String get name => 'stash';
    
      
      String get description => 'Stash changes in the working directory.';
    
      StashCommand() {
        // ここでコマンド固有の引数を追加できる。argParserは内部で作成される。
        argParser.addFlag('all', abbr: 'a');
      }
    
      
      void run() {
        print("StashCommand --all: ${argResults?['all']}");
      }
    }
    

bin/dart_args_example.dart を以下の様に修正します。

void main(List<String> arguments) {
  var runner = CommandRunner(
      'dgit', "A dart implementation of distributed version control.");
  runner.argParser.addOption('mode', abbr: 'm', allowed: ['debug', 'release']);
  runner.argParser.addFlag('verbose', abbr: 'v', defaultsTo: true);
  runner.addCommand(CommitCommand());
  runner.addCommand(StashCommand());
  runner.run(arguments);
}

ドキュメントのサンプルにある様に dgit というCLIアプリの想定で作成しています。

早速実行してみます。

$ dart run bin/dart_args_example.dart -m debug -v commit -a
CommitCommand --all: true

ちゃんと実行できてそうです!

ちなみに複数コマンドを指定する場合の挙動も変わらずでした。

$ dart run bin/dart_args_example.dart -m debug -v stash -a commit --no-all
StashCommand --all: false

コマンドに関しては先に指定したコマンドが採用されているのですが、オプションは最後の commit の方の all が採用されています。

参考URL

Discussion