Perl で CLI コマンドを書く
Perl で CLI コマンドを書く方法はいろいろある。
- スクラッチで書く
- CLI 用フレームワークにのる
- パーツを組み合わせる
ここでは、3番目の「パーツを組み合わせて」書く方法を解説する。CLI 用フレームワークに乗ると、簡単に書くことができるようになるが、副作用としてフレームワークにない要求に応えたくなったとき、実現するのが逆に手間だったりするので、できる限り必要な要素ごとにモジュールを組み合わせて CLI を構築する方法をとりたい。そうしておくと、なにか面倒な要求に応える場合にも、スクラッチで書かなければいけない箇所をパーツごとに限定できる。
CLI スクリプトの構成
一枚スクリプト
小さなものは一枚スクリプトでも OK かもしれないが、将来的に拡張するのであればコマンドから内部ロジックをモジュールに出した方が良い。
モジュール化
以下のように、some_command の内部ロジックを SomeCommand.pm に切り出す。
script/some_command
lib/SomeCommand.pm
t/some_command.t
コマンドでは、コマンドの起動のみを扱って、処理は .pm に書く。テストも書こう。
複数モジュール化
例えばサブコマンドで処理がいくつもある場合、対応したサブクラスに処理をディスパッチするように書くと収まりが良い。
script/some_command
lib/SomeCommand.pm
/SomeCommand/Foo.pm
/SomeCommand/Bar.pm
t/some_command.t
some_command foo
は SomeCommand/Foo.pm
で処理される感じ。
CLI の実践
Docopt
Docopt は CLI の help や man で表示されるようなドキュメントをもとに、コマンドラインをパースしてくれる。
docopt の Perl 実装として Docopt がある。
例えば、以下のようにコードとドキュメントを書くと、
#/usr/bin/perl
use strict;
use warnings;
use Docopt;
use Data::Dumper;
my $opt = docopt(
argv => scalar(@ARGV) ? \@ARGV : ['--help' => 1],
);
warn Dumper($opt);
=head1 SYNOPSIS
some_command [--module=<module> | --file=<file>] [--result=<result>] [--sort]
OPTIONS:
--module local module name
--file local file path
--sort sort result table
--result kind of result(module*|methods|cc|lines|files) *default
-h --help show this help
=cut
以下のようにハッシュで取得できる。
$ some_command --module Encode --result method
$VAR1 = {
'--sort' => undef,
'--result' => 'method',
'--file' => undef,
'--module' => 'Encode',
};
Getopt::Long でやっていたようなことが、コードベースを少なく、しかもドキュメントをベースに行えるのが便利。
CLI::Dispatch::Docopt
CLI::Dispatch::Docopt はサブコマンドを使った CLI のディスパッチを楽にしてくれる。
例えば、some_command
というコマンドラインツールの中身が以下のようだとします。
use Docopt;
use CLI::Dispatch::Docopt;
my $opt = docopt(argv => \@ARGV);
run('MyApp::CLI' => $opt);
__END__
=head1 NAME
some_command
=head1 SYNOPSIS
some_command <sub_command> [--foo]
=cut
CLI::Dispatch::Docopt がエクスポートした run 関数にディスパッチのベースになるクラスと、Docopt で パーズしたハッシュを渡します。
そして、ディスパッチ先のモジュールを以下のような感じで MyApp::CLI::Qux
として用意しておきます。
package MyApp::CLI::Qux;
use Data::Dumper;
sub run {
my ($self, $opt) = @_;
warn __PACKAGE__. " run!\n". Dumper($opt);
}
1;
んでもって、コマンドラインで some_command
を以下のように叩きます。
$ some_command qux --foo
MyApp::CLI::Qux run!
$VAR1 = {
'<sub_command>' => 'qux',
'--foo' => bless( do{\(my $o = '1')}, 'boolean' )
};
qux というサブコマンドが、MyApp::CLI::Qux#run
にディスパッチされました。説明は長いですが、実際のコードベースは小さくて楽ですね。
Config::CmdRC
Config::CmdRC は CLI によくある .somerc
という rcfile の面倒を見てくれるモジュール。コマンドラインオプションのデフォルト設定をユーザに簡単に提供できる。
なにせ use するだけ。
use Config::CmdRC '.somerc';
こうするだけで、/etc/.somerc
~/.somerc
./.somerc
を探索して読んでパーズして RC
という関数でハッシュが取得できるようになる。
探索ディレクトリやファイル名を複数書いたりといったこともできるがここでは割愛。
まとめ
CLI が楽に書けると、アプリケーション開発/運用におけるヘルパーなどを気軽に書けて良い。