Dartでコマンドライン引数を受け取る

DartはサーバーサイドでもスタンドアロンVMにより実行可能ですので、当然コマンドライン引数を受け取ることが出来ます。

# 引数を文字列として受け取る
単純にコマンドライン引数を文字列のリストとして受け取る方法です。

```dart:simple_args.dart
void main(List<String> args){
  print(args);
}
```

```:command-line
$ dart simple_args.dart --test simple -r hogehoge
```

```:stdout
[--test, sample, -r, hogehoge]
```

`main()`の引数の型は`List<String>`です。『配列じゃないのか』と思うかもしれませんが __Dartに配列はないです__ 。また、言うまでもありませんが`main(List args)`や`main(args)`でもOKです。  

受け取るのは簡単ですがこのままだと文字列なので扱いづらいですね。受け取った文字列配列をいい感じにパースしてくれるライブラリがあります。`args`( https://pub.dartlang.org/packages/args )です。

# argsパッケージを使う
まずはpubでargsをインストールしてください。

```
$ pub install args
```

## ArgParserを定義する
コマンドライン引数から受け取るオプション（--key value）やフラグ（--flag）を定義します。省略形も簡単に定義できます。  

今回は複数のjsファイルまたはcssファイルを結合・圧縮するminifyプログラムを想定します。次のコマンドをパースして4つの引数を取得してみます。

```bash
$ dart concat_args.dart --output out.min.js -t js -c input1.js input2.js
```

- `--output`, `-o` : 出力先ファイル名（オプション, 無制限）
- `--type`, `-t` : 形式（オプション, js | css）
- `--compress`, `-c` : 圧縮するかどうか（フラグ, 規定値:false）
- 結合されるファイル名（複数）

----

まず`output`オプションを定義します。

```dart
  parser.addOption("output", abbr:"o");
```

特に制限のない`--key value`ペアのオプションは`parser.addOption(name)`で定義できます。省略形の定義は`abbr:`引数を渡します。  

----

次に`type`オプションを定義します。`type`オプションは`js`もしくは`css`を受け取ります。

```dart
  parser.addOption("type", abbr:"t", allowed:["js", "css"]);
```  

いくつかの候補から選択させるタイプのオプションは`allowed:[]`引数を渡すことで内容を制限できます。  

----

最後に`compress`フラグを定義します。規定値はfalseとします。

```dart
  parser.addFlag("compress", abbr:"c", defaultsTo:false);
```

規定値を設定したい場合は`defaultsTo`引数を渡します。  

ここまでで次のようなコードになります。

```dart:concat_args.dart
import "package:args/args.dart";

void main(List<String> args){
  var parser = new ArgParser();
  parser.addOption("output", abbr:"o");
  parser.addOption("type", abbr:"t", allowed:["js", "css"]);
  parser.addFlag("compress", abbr:"c", defaultsTo:false);
}
```

## パースする
定義したArgParserを使ってコマンドライン引数をパースします。

パースを行うには`parser.parse`メソッドを呼びます。

```dart
  var results = parser.parse(args);
```

resultsの型はArgResultsです。ArgResultsから値を取り出すにはそれぞれ定義したオプション、フラグの名前を使います。

```dart
  print(results["output"]); //out.min.js
  print(results["type"]); // js
  print(results["compress"]); // true
```

オプション、フラグの後に与えられるrest引数は`results.rest`でアクセスできます。

```dart
  print(results.rest); //[input1.js, input2.js]
```

完成したコードがこちら

```dart:concat_args.dart
import "package:args/args.dart";

void main(List<String> args){
  var parser = new ArgParser();
  parser.addOption("output", abbr:"o");
  parser.addOption("type", abbr:"t", allowed:["js", "css"]);
  parser.addFlag("compress", abbr:"c", defaultsTo:false);

  var results = parser.Parse(args);
  print(results["output"]);
  print(results["type"]);
  print(results["compress"]);
  print(results.rest);
}
```

```:command-line
$ dart concat_args.dart --output out.min.js -t js -c input1.js input2.js
```

```:stdout
out.min.js
js
true
[input1.js, input2.js]
```

## 注意

### 定義していない名前のオプション、フラグが与えられるとFormatExceptionが発生する
予想外の名前のオプション、フラグが与えられるとパースに失敗して例外が発生します。try-catchで処理して、適切なエラーメッセージを返すといいでしょう。

### allowedに含まれていない値が与えられるとFormatExceptionが発生する
上記のプログラムに`-t html`等、許容外の値を与えると例外が発生します。

### フラグに値を与えてはいけない
フラグである`-c`に`-c true`等、値を与えると、`true`はオプションでもフラグでもないrest引数とみなされ、それ以降のパースに影響します。（次の注意を参照）

### rest引数は必ずオプション、フラグの後でなければならない
ArgParserは、オプションでもフラグでもない引数を見つけるとそこから後の引数を全てrest引数であるとみなします。なので前述のフラグに与えられた値は最初のrest引数ということになって、その後の引数もすべてrestになります。  

オプションを順序に関係なく受理するにはArgParserの生成時に`allowTrailingOptions`を渡します。

```dart
  var parser = new ArgParser(allowTrailingOptions: true);
```

## コマンドの定義
例えばgitではcommitやpushなど、gitに続けてコマンド名を渡します。コマンドによってその後のオプションの種類や処理は変わるので、上記のArgParserで定義するのは至難の業です。そこで2番目に与えられるコマンド名によってArgParserを切り替える`command`という仕組みがあります。  

例えば`git commit`を定義する際は次のようにパースを移譲することができます。

```dart:git.dart
  var parser = new ArgParser();
  var commitParser= new ArgParser();
  parser.addCommand('commit', commitParser);
```

実行されたコマンドは`results.command.name`で取得することができ、コマンドに渡されたオプションは`results.command[name]`で取得します。

```dart:git.dart
  var parser = new ArgParser();
  var commitParser= new ArgParser();
  parser.addCommand("commit", commitParser);
  
  commit.addFlag("all", abbr:"a");

  var results = parser.parse(["commit", "-a"]);
  // print(result["all"]); // FormatException
  print(result.command.name); // commit
  print(result.command["all"]); // true
```

コマンド名を取得した後はswitch-caseでコマンドごとに処理をするといいと思います。

## helpを表示する
`--help`で使い方を表示するために、オプションに説明文を付け足すことが出来ます。

```dart
parser.addOption("type", help: "Target file type",
    allowed: ["js", "css"]);
```

```:help
--type            Target file type
                  [js, css]
```

フラグでないオプションにはヘルプ用の値を与えることが出来ます。

```dart
parser.addOption("output", help: "The output path", helpValue: "path");
```

```:help
--output=<path>      The output path
```

あとはヘルプ用のフラグを受け取って、`getUsage()`を出力するだけです。

```dart:git.dart
  var parser = new ArgParser();
  var commitParser= new ArgParser();
  parser.addCommand("commit", commitParser);  
  commit.addFlag("all", abbr:"a", help:"Commit all files");
  
  parser.addFlag("help", abbr:"h"); // helpフラグは親Parserに

  var results = parser.parse(["commit", "--help"]);
  if(results["help"]){
    switch(results.command.name){
      case "commit":
        print(commitParser.getUsage()); // 出力
    }
  }
```

----

その他細かい仕様や使い方は[公式のドキュメント](https://pub.dartlang.org/packages/args)を読んでください。