Dartでコマンドラインツールを作る

「Dart Advent Calendar 2014」 11日目は@zukkunが担当します。

Dartは、JavaScriptの代替として登場しましたが、独自のVMを持ち、コマンドラインからDartのプログラムを実行する環境が提供されています。

今回は、Dartでのコマンドラインツールを作る方法を、以下のページを参考にしながら紹介しようと思います。

[Write Command-Line Apps | Dart: Structured web apps](https://www.dartlang.org/docs/tutorials/cmdline/)

動作確認環境はMac OS X 10.9.5です。(Dartは、Windows、Linuxでも使えます）

## Dartをインストール

Dartがインストールされていなければ、まずDartをインストールします。

Mac環境では、Homebrewを使ってDartをインストールすることができます。

```bash
$ brew tap dart-lang/dart
$ brew install dart
```

はじめに`brew tap`でリポジトリを追加する必要があるので注意です。

参考：[Dart News & Updates: Dart launches support for Homebrew](http://news.dartlang.org/2014/10/dart-launches-support-for-homebrew.html)

インストールができたら、Dartがインストールされたか確認します。

```bash
$ dart --version
Dart VM version: 1.7.2 (Tue Oct 14 06:58:25 2014) on "macos_x64"
```

## 簡単なDartプログラムを書く

まず、簡単なDartのプログラムを書きます。

```dart:hello.dart
main() {
  print('hello');
}
```

これを実行してみます。

```bash
$ dart hello.dart
hello
```

`hello`が出力されます。

これで一応コマンドラインのアプリケーションが作れました。

もう少しコマンドラインツールっぽくしてみましょう。

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

Dartでコマンドライン引数を受け取るには、`main`関数の引数に`List<string>`を型とした引数を加えます。

```dart:hello.dart
main(List<string> arguments) {
  print('hello $arguments');
}
```

引数の内容をそのまま`hello`のあとに表示するようにしてみました。

試しに適当に引数を与えて実行してみます。

```bash
$ dart hello.dart foo bar 123
hello [foo, bar, 123]
```

コマンドライン引数がリストとしてそのまま文字列展開されて出力されました。

## コマンドライン引数を解析する

自力でコマンドライン引数の内容を解析してもいいですが、複雑なものとなるとけっこう大変です。

そんなときは、このパッケージを使いましょう。

[args 0.12.1 | Pub Package Manager](https://pub.dartlang.org/packages/args)

こちら、パッケージとして公開されていますが、Dart Team謹製の公式パッケージです。

パッケージをインストールするために、`pubspec.yaml`を書きます。

```yaml:pubspec.yaml
name: hello
dependencies:
  args: any
```

依存パッケージに`args`を追加しました。

`pub get`で依存パッケージを取得します。

```bash
$ pub get
Resolving dependencies... (2.5s)
+ args 0.12.1
+ collection 1.1.0
Downloading collection 1.1.0...
Changed 2 dependencies!
```

## 真偽値としてオプションを受け付ける

Dartプログラムに、コマンドライン引数の解析の処理を追加します。

```dart:hello.dart
import 'package:args/args.dart';

ArgResults argResults;

main(List<string> arguments) {
  final parser = new ArgParser()
    ..addFlag('hoge', negatable: false, abbr: 'h');

  argResults = parser.parse(arguments);

  print('hello ${argResults['hoge']}');
}
```

`ArgParser`の`addFlag`で「オプションを指定したか、していないか」の真偽値で引数を受け取ることができます。

`negatable`は、否定形の引数を受け付けるかを指定できます。（falseを指定しなければ、`--no-hoge`という指定を受け付ける）

`abbr`はAbbreviationの略で、「省略」を意味します。今回`'h'`としているので、`-h`という指定でも`--hoge`と同じ意味を持たせることが出来ます。

```bash
$ dart hello.dart --hoge
hello true
```
```bash
$ dart hello.dart
hello false
```
```bash
$ dart hello.dart -h
hello true
```

## 値付きのオプションを受け付ける

真偽値ではなく値付きでオプションを受け付ける場合は、`addOption`を使います。

```dart:hello.dart
import 'package:args/args.dart';

ArgResults argResults;

main(List<string> arguments) {
  final parser = new ArgParser()
    ..addFlag('hoge', negatable: false, abbr: 'h')
    ..addOption('foo', abbr: 'f');

  argResults = parser.parse(arguments);

  print('hello ${argResults['foo']}');
}
```

```bash
$ dart hello.dart --foo bar
hello bar
```
```bash
$ dart hello.dart
hello null
```

他にも、デフォルト値の指定や、バリデーションの機能など、盛りだくさんです。

また、GitやRailsなどで見る`<プログラム名> <コマンド名> <パラメータ>`のような「コマンド形式」でのコマンドライン引数の受け取りのための`addCommand`などもあります。

## コマンドラインツールをインストールする

作ったコマンドラインツールを実行するためには`dart hello.dart`のように、`dart`コマンドの引数としてプログラムのファイル名を指定してあげる必要がありましたが、これを`hello`という独立したコマンドとして実行できるように環境にインストールすることができます。

まず、先ほど作った`hello.dart`を、`pubspec.yaml`があるディレクトリから見て`bin/hello.dart`としてファイルを移動させます。(binディレクトリ無ければ作ります)

そして、`pubspec.yaml`に`executables`を追加し、その中に`hello`を追加します。

```yaml:pubspec.yaml
name: hello
dependencies:
  args: any
executables:
  hello:
```

次に`pub global`というコマンドを使用します。

参考：[pub global | Dart: Structured web apps](https://www.dartlang.org/tools/pub/cmd/pub-global.html)

通常は、pub.dartlang.orgに公開された実行可能なパッケージをインストールするときに使用しますが、先ほど作成したhello.dartをインストールしてみます。(pub global上では「インストール」ではなく「アクティベート」と呼んでいますが、便宜上インストールと書きます）

`pub global`のあとに`activate`を指定し、`--source path`オプションをつけ、そのあとに`pubspec.yaml`が存在するパスを指定することで、ローカル環境にあるパッケージを指定してインストールできます。

```bash
$ cd <pubspec.yamlがあるパス>
$ pub global activate --source path .
Activated hello 0.0.0 at path "<pubspec.yamlがあるパス>".
Installed executable hello.
```

これで`hello`というコマンドが使えるようになりました。

```bash
$ hello
hello null
```
```bash
$ hello --foo bar
hello bar
```

`hello`コマンドの実態は`~/.pub-cache/bin/hello`として作られており、中身は以下のようになっています。

```bash:hello
#!/usr/bin/env sh
# This file was created by pub v1.7.2.
# Package: hello
# Version: 0.0.0
# Executable: hello
# Script: hello
pub global run hello:hello "$@"
```

`pub global`のアクティベートまわりや`run`などの仕様の説明については割愛します。

## コマンドラインツールをアンインストールする

インストールしたコマンドラインツールをアンインストールする場合は`pub global deactivate`を使います。

```bash
$ pub global deactivate hello
Deactivated package hello 0.0.0 at path "<先ほどのpubspec.yamlがあったパス>".
```

これで`hello`が使えなくなりました。

```bash
$ hello
zsh: command not found: hello
```

細かい機能や仕様の話など、まだまだ語り尽くせないものがありますので、機会があれば触って解説できたらと思います。

~~明日は…………担当者がいない？！どうする @laco0416 さん！~~

Twitterより引用
@zukkun「@laco0416 Dart Advent Calendarの明日の担当者いません！」
@laco0416「@zukkun 書いてどうぞ！」

明日は@zukkunさんです！俺だ！