# Perl で CLI コマンドを書く {{tag: perl, cli}} Perl で CLI コマンドを書く方法はいろいろある。 1. スクラッチで書く 1. CLI 用フレームワークにのる 1. パーツを組み合わせる ここでは、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 {{cpan: Docopt}} は CLI の help や man で表示されるようなドキュメントをもとに、コマンドラインをパースしてくれる。 [[http://docopt.org/|docopt]] の Perl 実装として {{cpan: 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= | --file=] [--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', }; {{cpan: Getopt::Long}} でやっていたようなことが、コードベースを少なく、しかもドキュメントをベースに行えるのが便利。 ### CLI::Dispatch::Docopt {{cpan: 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 [--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 = { '' => 'qux', '--foo' => bless( do{\(my $o = '1')}, 'boolean' ) }; qux というサブコマンドが、`MyApp::CLI::Qux#run` にディスパッチされました。説明は長いですが、実際のコードベースは小さくて楽ですね。 ### Config::CmdRC {{cpan: Config::CmdRC}} は CLI によくある `.somerc` という rcfile の面倒を見てくれるモジュール。コマンドラインオプションのデフォルト設定をユーザに簡単に提供できる。 なにせ use するだけ。 use Config::CmdRC '.somerc'; こうするだけで、`/etc/.somerc` `~/.somerc` `./.somerc` を探索して読んでパーズして `RC` という関数でハッシュが取得できるようになる。 探索ディレクトリやファイル名を複数書いたりといったこともできるがここでは割愛。 ## まとめ CLI が楽に書けると、アプリケーション開発/運用におけるヘルパーなどを気軽に書けて良い。