2024/02/13 22:13:39

Perl で CLI コマンドを書く

[perl] [cli]

Perl で CLI コマンドを書く方法はいろいろある。

  1. スクラッチで書く
  2. CLI 用フレームワークにのる
  3. パーツを組み合わせる

ここでは、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 fooSomeCommand/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 が楽に書けると、アプリケーション開発/運用におけるヘルパーなどを気軽に書けて良い。

サイト内検索