MojoliciousでCLI

こんにちは。@[hayajo](https://twitter.com/hayajo)と申します。今月はPHPで開発していますが進捗ダメです。

---

さて、ウェブアプリケーションといってもウェブインターフェースだけではなく、外部データのインポートやエクスポート、データ集計のためのバッチ処理など、コマンドラインインターフェース（CLI）として実装するものも少なくありません。

今回はこのようなコマンド群をMojolicous(::Lite)アプリケーション実行コマンドのサブコマンドとして実装する方法をご紹介します。

ネームスペースの追加する
------------------------

まずはアプリケーションのcommandsに新しくネームスペース（MyApp::CLI）を追加します。

### Mojolicious::Liteアプリの場合

```perl:myapp.pl
#!/usr/bin/env perl
use Mojolicious::Lite;

# 追加ここから --
use FindBin;
BEGIN { unshift @INC, "$FindBin::Bin/lib" }

my $commands = app->commands;
# コマンドのネームスペースとしてMyApp::CLIを追加
push @{ $commands->namespaces }, 'MyApp::CLI';
# -- 追加ここまで

# Documentation browser under "/perldoc"
plugin 'PODRenderer';
...
```

### Mojoliciousアプリの場合

```perl:lib/MyApp.pm
package MyApp;
use Mojo::Base 'Mojolicious';

# 追加ここから --
has commands => sub {
    my $commands = shift->SUPER::commands;
    # コマンドのネームスペースとしてMyApp::CLIを追加
    push @{ $commands->namespaces }, 'MyApp::CLI';
    return $commands;
};
# -- 追加ここまで

# This method will run once at server start
sub startup {
  my $self = shift;
  ...
```

サブコマンドを作成する
----------------------

サブコマンドはMojolicious::Commandを継承して作成します。

例ではnyanサブコマンドを作成しています。

```perl:lib/MyApp/CLI/nyan.pm
package MyApp::CLI::nyan;

# サブコマンドはMojolicious::Commandを継承して作ります
use Mojo::Base qw/Mojolicious::Command/;

use Getopt::Long qw(GetOptionsFromArray :config no_auto_abbrev no_ignore_case);

# コマンド一覧に表示される解説
has description => "say 'nayn'.\n";

# ヘルプメッセージ
has usage => <<EOF;
usage: $0 hoge

These options are available:
  -d --debug debug mode
EOF

# コマンド実態
sub run {
    # コマンドライン引数は@argsに格納されます
    my ($self, @args) = @_;

    # コマンドライン引数をパースします
    GetOptionsFromArray(\@args, 'd|debug' => \(my $debug))
        or die $self->usage;

    if ($debug) {
        # $self->appでMojolicious(::Lite)インスタンスが取得できます。
        say "MOJO_HOME: " . $self->app->home;
        say "MOJO_MODE: " . $self->app->mode;
        say "---";
    }

    say 'nayn';
};

1;
```

下記のオプションはMojolicious側で処理されるので、サブコマンド側でオプションを受け取る場合は注意してください。

* -h, --help
* -m, --mode
* --home


サブコマンドを実行する
----------------------

nyanサブコマンドが認識されているか確認します。（例はMojolicious::Liteアプリ）

```shell
$ ./myapp.pl
usage: ./myapp.pl COMMAND [OPTIONS]

Tip: CGI and PSGI environments can be automatically detected very often and
     work without commands.

These commands are currently available:
  cgi        Start application with CGI.
  cpanify    Upload distribution to CPAN.
  daemon     Start application with HTTP and WebSocket server.
  eval       Run code against application.
  generate   Generate files and directories from templates.
  get        Perform HTTP request.
  inflate    Inflate embedded files to real files.
  prefork    Start application with preforking HTTP and WebSocket server.
  psgi       Start application with PSGI.
  routes     Show available routes.
  test       Run unit tests.
  version    Show versions of installed modules.
  nyan       say 'nayn'.

These options are available for all commands:
    -h, --help          Get more information on a specific command.
        --home <path>   Path to your applications home directory, defaults to
                        the value of MOJO_HOME or auto detection.
    -m, --mode <name>   Operating mode for your application, defaults to the
                        value of MOJO_MODE/PLACK_ENV or "development".

See './myapp.pl help COMMAND' for more information on a specific command.
```
nyanサブコマンドが追加されていますね。実行してみましょう。

```shell
$ ./myapp.pl nyan --help
usage: ./myapp.pl hoge

These options are available:
  -d --debug debug mode
$ ./myapp.pl nyan
nayn
$ ./myapp.pl nyan --debug
MOJO_HOME: /Users/hayajo/work/my_app
MOJO_MODE: development
---
nayn
```

サブコマンドとして実行できていますね。


CLIだけの場合
-------------

CLIだけが必要であれば下記のようなスクリプトを用意します。

```perl
#!/usr/bin/env perl

use strict;
use warnings;

use FindBin;
BEGIN {
    # パスは環境にあわせて変更してください
    unshift @INC, "$FindBin::Bin/lib";
    $ENV{MOJO_HOME} = $FindBin::Bin
      unless $ENV{MOJO_HOME};
}

require Mojolicious::Commands;
my $cmd = Mojolicious::Commands->new;
$cmd->namespaces([qw/MyApp::CLI/]); # Mojolicious::Command::* は利用しない

$cmd->run(@ARGV);
```

あとは前述のようにサブコマンドを用意しください。この場合の`$self->app`は素のMojolicious::Liteインスタンスになります。


ちなみに...
-----------

あまりお勧めはしませんが、ネームスペースが気にならないのであればサブコマンドをMojolicious::Command::nyanとして実装してしまうことも可能です。

まとめ
------

ウェブアプリケーションの脇を固めるコマンド群は単独のコマンドとして作っても問題無いと思いますが、このようにしてサブコマンドとして実装すると、コマンドが散らばらずにとてもスッキリします。
また、設計によってはウェブアプリケーションとコマンドの処理や設定の共通化もできるので、コードもスッキリすると思われます。

Mojolicious::Commands自体はCLIのフレームワーク的に利用するこができます。Mojoliciousの便利なモジュールと組み合わせたポータブルなコマンドを作成することも可能です。

Mojolicious、なかなか奥が深いですね。

---

[@yusukebe](https://twitter.com/yusukebe)さん、声をかけていただきありがとうございました。

参加するか迷っていたところだったので、ちょうど良いタイミングでした。

---

今年の娘ちゃん（小４）のクリスマスプレゼントは[iPod Touch](http://www.apple.com/jp/ipod-touch/)です。私が同じ年齢のときは[ゾイドのアイアンコング](https://www.google.co.jp/search?q=%E3%82%BE%E3%82%A4%E3%83%89%E3%80%80%E3%82%A2%E3%82%A4%E3%82%A2%E3%83%B3%E3%82%B3%E3%83%B3%E3%82%B0&source=lnms&tbm=isch&sa=X&ei=e_e2UoaPLMWikwWviIDgCg&ved=0CAkQ_AUoAQ&biw=1366&bih=647)を貰ったよなうな気が…

![e95448e06b1111e3b8c81286a8b0a4b1_8.jpg](https://qiita-image-store.s3.amazonaws.com/0/10459/3eee1bc5-a178-2208-5c9f-cac2981d28ba.jpeg)