Haskellでコマンドラインパーサを使う
コマンドライン引数のパースがだいたいどう転んでも面倒くさい。特にCとかJavaとかは本当に泣きたくなりますよね。まあJavaでコマンドラインのプログラムを書く事なんてあんまし無いかもしれませんけど。いや、ありますけど。泣きながら書いてました。
Haskellでもまあ別に普通に面倒くさいんですけど、いくつかライブラリがあって、その中で一番目についたというか使いやすそうだったライブラリがcmdargsでした。
使い方。
{-# LANGUAGE DeriveDataTypeable #-} module Main where import System.Console.CmdArgs data Option = Option { label :: String , size :: Int } deriving (Show, Data, Typeable) option :: Option option = Option { label = "no label" , size = 0 } main :: IO () main = do opt <- cmdArgs option print opt
とりあえずサンプルそのまんまです。
これだけでも最低限の機能は持っていて、実行するとこうなる。
% runghc Main Option {label = "no label", size = 0}
これはコマンドライン引数を何も指定していないので、optionで定義したデフォルト値がそのまま返ってきている。
引数を指定するとこうなる。
% runghc Main --label kirari --size 186 Option {label = "kirari", size = 186}
デフォルトではラベルの名前がそのまま引数の名前になります。
それとこの時点で--helpと--versionは実装されています。
% runghc Main --version The option program % runghc Main --help The option program option [OPTIONS] Common flags: -l --label=ITEM -s --size=INT -? --help Display help message -V --version Print version information
Intの変換は勝手にやってくれるので、それはちょっと楽です。
で、カスタマイズ。
多くの場合ではOptionのラベル名をそのまま引数の名前として使われると面倒くさいんじゃないかと思います。例えばOptionの定義がこんな場合。
data Option = Option { optLabel :: String , optSize :: Int } deriving (Show, Data, Typeable)
この場合はオプションがこんなになってしまいます。
% runghc Main --help {略} Common flags: --optlabel=ITEM --optsize=Int
面倒くさいし、ショートオプションなくなってるし。
あ、このライブラリではショートオプションはロングの最初の文字から取られるようです。
で、この時にどうするかというと、デフォルト値にアノテーションを付けて、それをcmdArgsが解釈することでなんかうまくいくみたいです。
コードにするとこんな感じ。option関数のみ。
option :: Option option = Option { optLabel = "no label" &= name "label" , optSize = 0 &= name "size" }
仕組みは全くわかりませんが、これでうまくいく。
% runghc Main --help {略} Common flags: -l --label=ITEM --optlabel -s --size=INT --optsize
元の引数名を完全に消したいならexplicitをつける。
option :: Option option = Option { optLabel = "no label" &= name "label" &= explicit , optSize = 0 &= name "size" &= explicit }
ヘルプを書きたいならさらにhelpを足す。
option :: Option option = Option { optLabel = "no label" &= name "label" &= explicit &= help "Name of label" , optSize = 0 &= name "size" &= explicit &= help "Size of struct" }
全体の説明を書きたいならOptionにsummaryをつける。
option :: Option option = Option { optLabel = "no label" &= name "label" &= explicit &= help "Name of label" , optSize = 0 &= name "size" &= explicit &= help "Size of struct" } &= summary "CmdArgs sample"
実行例のプログラム名を変更したいならprogramで指定する。
option :: Option option = Option { optLabel = "no label" &= name "label" &= explicit &= help "Name of label" , optSize = 0 &= name "size" &= explicit &= help "Size of struct" } &= summary "CmdArgs sample" &= program "cmdargs"
という感じで、要素を付け足したりしていけばいいみたいです。
% runghc Main --help CmdArgs sample cmdargs [OPTIONS] Common flags: --label=ITEM Name of label --size=INT Size of struct -? --help Display help message -V --version Print version information
あれ、ショートオプションなくなってる……。まあよくわかりません。
今のところByteStringやBoolには対応してないっぽいです。
あと気になるのは、requiredな引数を作りたい時になんか楽にやれないのかということ。
一応、Maybe Intとかの型にしておいてもIntと同じように読んでくれたりするので、requiredなオプションはMaybe値にしておいて、自分でチェックすれば良さそうです。
というかこのMaybe対応は地味にすごい気がします。さっきのアノテーションだって本当に何をやってるのかさっぱりわかりません。
でも使うのは簡単なので使いましょう。世界は広いな。