@kyanny's blog

My life. Opinions are my own.

Getopt::Long::Descriptive を使ってみた

MooseX::Getopt のドキュメントを読んでいたら Getopt::Long::Descriptive というのがあるのを知ったので少しいじってみた。

weatherhacks.pl · GitHub

#!/usr/bin/env perl
use strict;
use warnings;
use Getopt::Long::Descriptive;
use WebService::Simple;
use Encode;
use Term::Encoding qw(term_encoding);

my $base_url = 'http://weather.livedoor.com/forecast/webservice/rest/v1';

my $format = 'Usage: %c %o';
my @opts = (
    ['help' => 'print this message'],
    ['city=i' => 'city id', {
        required => 1,
    }],
    ['day=s' => '[today|tomorrow|dayaftertomorrow]', {
        default => 'today',
    }],
);
my ($opts, $usage) = describe_options($format, @opts);
$usage->die if $opts->{help};

my $service = WebService::Simple->new(
    base_url => $base_url,
);
my $response = $service->get($opts);
my $obj = $response->parse_response;
print encode(term_encoding, $obj->{description});
  • Getopt::Long と同等のことができて(たぶん)、簡易なパラメータのバリデーションをやってくれる。あと Usage: ... を楽に出せる。
  • GetOptions() にあたるのが describe_options()
  • describe_options() の第一引数は Usage: ... を出力するときのフォーマット。
  • describe_options() 二つ目以降の引数が GetOptions() で 'foo=s' みたいにやってたのに相当する ArrayRef の配列。
  • describe_options() が返す引数のうち、一番目がパースされたコマンドライン引数の HashRef で、二つ目が Usage を出力するためのもの。
  • describe_options() に渡すオプション指定のための ArrayRef は、一つ目の要素がロングオプション名、二つ目の要素が Usage で表示される説明文(これが楽に書けて便利だと思う)、三つ目にオプショナルで HashRef を渡せる。
  • そのオプショナルな HashRef には、 required とか implies とか default とか、簡単なパラメータのバリデーションの指示や初期化の指示が書ける(素朴なことしか書けない)。
  • one_of はなんかよくわからなかった。どういうときにこれが便利なのかイメージできなかった。

使ってみていいなと思ったのは、

  • Usage: が楽に書ける、出力できる。特に、 --help print this message みたいに、オプションの短い説明文を添えて表示したいときに(頻繁にある)、 Pod::Usage を使っているとスクリプト本体と離れた pod の中に全部まとめて書くことになるので、新しいオプションを追加したり、逆にオプションを無くしたりしたときにドキュメントが追随しておらず、せっかくの Usage が役に立たない(ので結局ソースを読むハメになる)ことがよくある。これが、 GLD (Getopt::Long::Descriptive の略称らしい)を使っていると説明文をスクリプトの中に書くので一緒にアップデートしやすくなりそう。
  • default は Getopt::Long にも似たようなのがありそうな気がするけど、 required はなかったかもしれない (foo{n,m} を使えばできるのかも?)。ごくごく簡単なチェックしかできないけど、それでも値のチェックを全部自前でやるよりは多少は楽だ。

逆に、もっとこうだったらいいのにと思ったのは、

  • implies がちょっとわかりにくい ... ってのは勉強しろってことでおいといて、 implies は「foo オプションが指定されるときは bar オプションも指定されていなければならない(そして bar オプションに指定された値は baz でなければならない)」とかの用途で使うものだと思うけど、「foo オプションは bar, baz, quux のいずれかの中から選ばなければならない」みたいなのも欲しくなる。とか、もうちょっと値のバリデーションのバリエーション(紛らわしいカタカナ語の連続)を増やして欲しい。

結論として、それなりに便利だなとは思うけど、 Getopt::Long + Pod::Usage は標準モジュールだという強みがあり、環境をほぼ気にせずに自由に使ってスクリプトを書ける、しかし GLD はインストールされていない環境も多いはずで、そのへんが仕事では気軽に使えないかもしれなくてネックになるかなと思った(ちょっとしたスクリプトを少し楽に書くために、仕事で使う何台かのサーバに、上司の許可をとったりして新しいモジュールをインストールして、インストール手順書をアップデートして ... みたいな手間に比べれば、標準モジュールを使っていつも通りのちょっと面倒くさい書き方をするほうが楽だったりする)。まあ、どうせ他にもいろいろモジュール使うんだから一個追加するくらいどうってこともないはずだけど・・・。

あと、ぐぐっても全然情報が出てこなかった。。誰も使ってないんだろーか。たぶんこの記事が日本語での GLD 解説第一号になるはず!(俺調べ)。 pod 読んですぐ使えるから解説なんて書くまでもないですよ、ってことだったらちょっと悲しい。

あと、 Usage が楽に書けていいとか褒めておいてなんだけど、これ使うと当然 Usage のために pod 書く必要ないんだから pod 書かないよねー短いスクリプトだし、ということでドキュメントがないスクリプトができてしまう。しかもこれの使い方とかわかってないとぱっと見でどういうオプションがあるかとかがわからないかもしれない。自分で書いて月に一度くらい起動するスクリプトなら、「使い方なんて一切覚えてないけど俺のことだから引数なしで実行したら Usage くらい出すように書いてあるだろ」なんて気軽に実行できるけど、俺みたいなやつの後任者がこの得体の知れないスクリプトを引き継いだとしたら怖くていきなり実行なんてできないはずで、まずページャで中身をみてみるはずだが、そのとき一切ドキュメントらしい部分がないとかなりガッカリすると思われる。なので、長い目でみた場合、やっぱり Getopt::Long + Pod::Usage のコンビを使って pod をしっかり書く、というほうが実り多いのかもしれない。