人気ブログランキング | 話題のタグを見る

System.Console.GetOpt

前へ 目次 次へ

参照: cabal-install 関連のリンク

前回までの記事で Main.hs の main 関数の定義に出てくる

commandsRun globalCommand commands args

の globalCommand と commands の概要を調べた。大まかに言うと globalCommand は cabal コマンドのオプションに関する情報を納めていて、commands は cabal install .. の install のような cabal のコマンドの情報を納めている。もちろん args は cabal -V や caball install の -V や install などのコマンドライン引数のリストだ。これは Main.hs のなかの getArgs アクションによって取得される。

1. commandsRun

commandsRun 関数は Distribution.Simple.Commnad 標準モジュールに定義されており、その型は次のようになっている。

commandsRun :: CommandUI a -> [Command action] -> [String] -> CommandParse (a, CommandParse action)

第1引数の CommandUI a 型のデータにはコマンドの情報と、コマンドのオプションの情報が納められている。また [Command action] 型のデータはそのコマンドのサブ・コマンドの情報が納められている。[String] はコマンドライン引数のリストだ。戻り値の CommandParse (a, CommandParse action) 型はコマンドライン引数をパースした結果の構文木だ。再帰的なデータ構造になっている。

commandsRun はこのように、cabal のコマンドライン引数のパーサだ。汎用性があるので、Distribution.Simple.Command モジュールをインポートすれば、cabal 以外にも自作のコマンドのパーサとして利用する事ができるだろう。

commandsRun のコードの冒頭部分は次のようになっている。

commandsRun :: CommandUI a
            -> [Command action]
            -> [String]
            -> CommandParse (a, CommandParse action)
commandsRun globalCommand commands args =
  case commandParseArgs globalCommand' True args of

どうやら、commandParseArgs 関数がわからないと解読できないようだ。

2. commandParseArgs

commandParseArgs 関数も Distribution.Simple.Command モジュールで定義されており、その冒頭部分は次のようになる。

commandParseArgs command global args =
  let options = addCommonFlags ParseArgs
        $ commandGetOpts ParseArgs command
    order | global  = GetOpt.RequireOrder
      | otherwise = GetOpt.Permute
  in case GetOpt.getOpt' order options args of

3. GetOpt.getOpt

GetOpt.getOpt' という関数が出てくるがこれは一体何なのだろうか。getOpt' 関数は System.Console.GetOpt 標準モジュールで定義されている。これは System.Environment モジュールの getArgs アクションでリストとして取り込んだコマンドライン引数のリストをパースしてオプションの情報を取り出す関数だ。

ところで、getArgsアクションの動作を確認しておいたほうがそのあとのコード解読がやりやすそうだったので ghci で試してみた。

Prelude> import System.Environment
Prelude System.Environment> let main = getArgs
Prelude System.Environment> :main hello world
["hello","world"]
Prelude System.Environment> :main -c --version
["-c","--version"]

getArgs では、コマンドラインオプションの先頭のハイフンもそのままとりこまれるのが分かる。

4. getOpt のプログラム例

System.Console.GetOpt モジュールの仕様は結構複雑なので、説明を読んでもよくわからない。そこで、モジュールの文書にあったプログラム例を実行してみた。

module Opts1 where
    
import System.Console.GetOpt
import Data.Maybe ( fromMaybe )
    
data Flag
  = Verbose  | Version
  | Input String | Output String | LibDir String
    deriving Show
    
options :: [OptDescr Flag]
options =
  [ Option ['v']     ["verbose"] (NoArg Verbose)       "chatty output on stderr"
  , Option ['V','?'] ["version"] (NoArg Version)       "show version number"
  , Option ['o']     ["output"]  (OptArg outp "FILE")  "output FILE"
  , Option ['c']     []          (OptArg inp  "FILE")  "input FILE"
  , Option ['L']     ["libdir"]  (ReqArg LibDir "DIR") "library directory"
  ]
    
inp,outp :: Maybe String -> Flag
outp = Output . fromMaybe "stdout"
inp  = Input  . fromMaybe "stdin"
    
compilerOpts :: [String] -> IO ([Flag], [String])
compilerOpts argv =
  case getOpt Permute options argv of
    (o,n,[]  ) -> return (o,n)
    (_,_,errs) -> ioError (userError (concat errs ++ usageInfo header options))
  where header = "Usage: ic [OPTION...] files..."

モジュール名が Opts1 なので Opts1.hs というファイルに作成する。ghci で試すと次のようになった。

Prelude> :l Opts1.hs
[1 of 1] Compiling Opts1 ( Opts1.hs, interpreted )
Ok, modules loaded: Opts1.
*Opts1> :browse Opts1
data Flag
= Verbose | Version | Input String | Output String | LibDir String
options :: [OptDescr Flag]
outp :: Maybe String -> Flag
inp :: Maybe String -> Flag
compilerOpts :: [String] -> IO ([Flag], [String])
*Opts1> compilerOpts ["-v"]
([Verbose],[])
*Opts1> compilerOpts ["--libdir", "/var/lib/"]
([LibDir "/var/lib/"],[])
*Opts1> compilerOpts ["not_option"]
([],["not_option"])

このプログラム例ではコマンドラインオプションは種類分けされて Flag 型に収められている。オプションがパラメータを取る場合は、Flag 型のコンストラクタのコンテナに入る。

complierOpts 関数(アクション)は String のリストを引数に取り、オプションをスキャンして、オプションは [Flag] リストにして、またオプション以外のコマンド・ライン引数の文字列は [String] リストにして、それらのリストのペアを IO 型にラッピングして返す。

プログラム例の中で getOpt 関数は、compilerOpts 関数の中で、

case getOpt Permute options argv of

のように使われている。

5. getOpt 関数

getOpt 関数の型は次のようになる。

getOpt :: ArgOrder a -> [OptDescr a] -> [String] -> ([a], [String], [String])

第1引数の ArgOrder a の意味がわからないが、System.Console.GetOpt は記事を改めて探索するのでとりあえず置いておいて、第2引数の [OptDescr a] がオプションのリストで、[String] はコマンド・ライン引数のリストだ。戻値は ([a], [String], [String]) となっているが compilerOpts の動作から見ると [a] がオプションのフラグのリストで、[String] が非オプションの文字列のリストだろう。コンテナの最後の [String] の意味が分からないが、case 文でコマンド・ライン引数に文法エラーがあった場合に利用されている。

結局、getOpt を利用する上での中心は [OptDecr a] のリストでオプションのルールをどのように与えるかということになる。プログラム例のオプションのルールである options の定義を再掲する。

options :: [OptDescr Flag]
options =
  [ Option ['v']     ["verbose"] (NoArg Verbose)       "chatty output on stderr"
  , Option ['V','?'] ["version"] (NoArg Version)       "show version number"
  , Option ['o']     ["output"]  (OptArg outp "FILE")  "output FILE"
  , Option ['c']     []          (OptArg inp  "FILE")  "input FILE"
  , Option ['L']     ["libdir"]  (ReqArg LibDir "DIR") "library directory"
  ]
    

options リストの要素である Option 型はパラメータとして4つのフィールドを持っている。1番目は short option のリスト、第2のパラメータは long option のリスト、3番めはオプションのフラグ (Flag 型)、4番目はオプションの説明文だ。

たとえば options リストの最初のフィールドの値は ['v']、2番めのフィールドは ["verbous"]、3番めのフィールドは (NoArg Verbose) フラグ、4番目のフィールドは "chatty output on stderr" だ。

これらのパラメータは cabal-install の globalCommnad の commandOptions フィールドのデータとも関連しているのだろう。というより、commandOptions フィールドのデータを使って OptDescr flag 型のデータを作成するのだろう。

cabal-install の主要な動作のひとつにコマンド・ライン・オプションの構文解析があるはずだが、GetOpt を利用しているようだ。System.Console.GetOpt は caball-install のコードの中で重要な働きをしているようなので、記事を変えて探索してみる。
by tnomura9 | 2013-12-15 08:01 | Haskell | Comments(0)
<< あきらめずに記憶の糸をたどると... 畳堤の詩 >>