この記事は、Perl 5 Advent Calendar 2016 - Qiitaの3日目の記事です。
昨日はnqounetさんのPerl5で文字列操作でした。
Perlの文字列操作はかなり強力なのを再認識できますね。
今日は、引数解析の拙作モジュールGetopt::Kingpin
に関する記事です。
Goal
Getopt::Kingpin
を使ったコマンドラインスクリプトのサンプルを提示する。
背景
前の記事(perlスクリプトの引数処理モジュールGetopt::Kingpin
の紹介)で、モジュールを紹介しました。
今回は、実際の使用例/実装例を示したい。
ドキュメント/サンプルソースを充実させたいので、そのついでとしてという意味も。
お題 ≒ 今回作るもの
wc
コマンドを作成してみます。
https://infohost.nmt.edu/tcc/cgi/manweb.cgi?p=wc によると、ヘルプは以下のような感じ。
これをざっくりコピーする。
NAME
wc - print the number of newlines, words, and bytes in files
SYNOPSIS
wc [OPTION]... [FILE]...
DESCRIPTION
Print newline, word, and byte counts for each FILE, and a total line if more than one FILE is specified.
With no FILE, or when FILE is -, read standard input.
-c, --bytes
print the byte counts
-m, --chars
print the character counts
-l, --lines
print the newline counts
-L, --max-line-length
print the length of the longest line
-w, --words
print the word counts
--help
display this help and exit
--version
output version information and exit
作ってみる
基本
Getopt::Kingpin
は、なるべくオリジナルであるgolang版に近い動き/記述ができるように作ってあります。
SYNOPSISに従いGetopt::Kingpin->new()
して、flag()
およびarg()
を追加していくのが基本となります。
それぞれ以下のようなイメージです。
- new('script name', 'script description')
- parserを生成します
- flag('flag name', 'flag description')
-
--words
や-w
等の-
から始まるフラグ(≒オプション)を設定する - arg('arg name', 'arg description')
- 引数を表す(
flag
を除くもの(典型的には-
から始まらないもの))
ここまで設定できたらparse()
を実行し引数を処理します。
もう少し詳細
例えば$kingpin->flag('lines', 'print the newline counts')->short('l')->bool
であれば以下のイメージです。
- flagの名前/指定の仕方は
--lines
- 詳細は
print the newline counts
- flagのshort_nameは
-l
- 型はbool (0 or 非0)
引数の解析
引数の解析部まで、以下に示します。
use Getopt::Kingpin;
my $kingpin = Getopt::Kingpin->new($0, 'print the number of newlines, words, and bytes in files');
my $flag = {
lines => $kingpin->flag('lines', 'print the newline counts')->short('l')->bool,
words => $kingpin->flag('words', 'print the word counts')->short('w')->bool,
chars => $kingpin->flag('chars', 'print the character counts')->short('m')->bool,
bytes => $kingpin->flag('bytes', 'print the byte counts')->short('c')->bool,
max_line_length => $kingpin->flag('max-line-length', 'print the length of the longest line')->short('L')->bool,
};
my $files = $kingpin->arg('file', 'target file[s]')->required->existing_file_list;
$kingpin->version('0.02');
$kingpin->parse;
ここまで書いた時点でヘルプの実装はできていて、以下のように表示できます。
$ perl wc.pl --help
usage: wc.pl [<flags>] <file>
print the number of newlines, words, and bytes in files
Flags:
--help Show context-sensitive help.
-l, --lines print the newline counts
-w, --words print the word counts
-m, --chars print the character counts
-c, --bytes print the byte counts
-L, --max-line-length print the length of the longest line
--version Show application version.
Args:
<file> target file[s]
参考) golang版
うーん、そっくり(当たり前)。
実装もヘルプも、ほとんど同じになるように作っています。
package main
import (
"gopkg.in/alecthomas/kingpin.v2"
"os"
)
var (
app = kingpin.New("wc", "print the number of newlines, words, and bytes in files")
lines = app.Flag("lines", "print the newline counts").Short('l').Bool()
words = app.Flag("words", "print the word counts").Short('w').Bool()
chars = app.Flag("chars", "print the character counts").Short('m').Bool()
bytes = app.Flag("bytes", "print the byte counts").Short('c').Bool()
max_line_length = app.Flag("max-line-length", "print the length of the longest line").Short('L').Bool()
files = app.Arg("file", "target file[s]").Required().ExistingFiles()
)
func main() {
app.Version("0.02")
app.Parse(os.Args[1:])
}
usage: wc [<flags>] <file>...
print the number of newlines, words, and bytes in files
Flags:
--help Show context-sensitive help (also try --help-long and
--help-man).
-l, --lines print the newline counts
-w, --words print the word counts
-m, --chars print the character counts
-c, --bytes print the byte counts
-L, --max-line-length print the length of the longest line
--version Show application version.
Args:
<file> target file[s]
スクリプト完成版
以下に完成版を置いてますので、適宜参照してください。
注意点
以下のように*_list
という名前の型を使うと、引数file
を複数(0個以上)指定することができます。
また、$files
は、Getopt::Kingin::Argオブジェクト
のarray refとなります。
my $files = $kingpin->arg('file', 'target file[s]')->required->existing_file_list;
なので、使うときにはデリファレンスが必要で、以下のようにします。
my @files = @{$files->value};
このようにすると、@files
は Path::Tinyオブジェクトの配列
となります。
後は煮るなり焼くなりどうぞ。
#この部分の作りは、もっといいやり方に直したいとは思っているが、いい方法が浮かばない
まとめ
自分が作ったものを使っているので当たり前ですが、自分にとっては使いやすいです。
golang版kingpinに馴染んでいる人ならある程度違和感なく使えるかと思います。
ということで、是非使ってみてください。