おそらくはそれさえも平凡な日々

Perlでコマンドラインモジュールを書くときのクラス設計

コマンドラインから引数が渡すようなモジュールでも以下のような要件があったりする。

  • コマンドラインからだけじゃなくて、コード内で直接オブジェクトをnewしたい場合もある
  • 複数のコマンドラインツールを組み合わせて使うような場合に@ARGVを何度か渡すケースがある

growthforecast.plみたいなサーバースクリプトだとよくあると思っていて、スクリプト単体で引数渡して起動できるけど、自分でGrowthForecast::Web->newしたくなることもある。これが前者。

growthforecast.plにPlackとGrowthForecastのオプションをコマンドラインから同時に渡したいみたいなのがある。これが後者。

GrowthForecastはあくまで例えです。念のため。

それに対しては以下のようにすればよいのかなーとなった。

  1. parse_optionsというクラスメソッドで@ARGVの分解を行う
  2. 1の結果を普通にnewに食わせる
  3. ついでに、1,2を同時にやるnew_with_optionsを生やしておくと便利そう

コード例としては以下。

use MyApp;
my ($opt, $rest_argv) = MyApp->parse_options(@ARGV);
my $app = MyApp->new($opt);
...

or

use MyApp;
my $app = MyApp->new_with_options(@ARGV);

$rest_argvには、@ARGVの残りが入ってくる。それを他のparse_optionsに食わせたりとかできるという寸法。

parse_optionsは例えば以下の様な感じ。

use Getopt::Long ();
use Pod::Usage;
use Hash::Rename qw/hash_rename/;
sub parse_options {
    my ($class, @argv) = @_;

    my $p = Getopt::Long::Parser->new(
        config => [qw/posix_default no_ignore_case auto_help bundling pass_through/]
    );
    $p->getoptionsfromarray(\@argv, \%opt, qw/
        hoge=s
        fuga=s
        dry-run
    /) or pod2usage;

    hash_rename %opt, code => sub {tr/-/_/};
    (\%opt, \@argv);
}

Getopt::Long::Parserを使うとconfigを局所化できるし、getoptionsfromarray使うといちいち@ARGVをローカライズとかしなくていいので、この使い方が最近のオレオレGetopt::Longプラクティス。

乱暴だけどHash::Renameでハイフン区切りのキーを一括でsnake_caseに変換とかしてみた。Hash::Rename便利だった。

色々つらつらと考えた末、こうするのがいいんじゃないかなーと思った。いかがでしょうか?

created at
last modified at
comments powered by Disqus