どう書く.org #205に投稿できなかった

お題は起動オプションの解析なのだけど、Erlangってそもそもコマンドラインオプションを解析するライブラリとかは持って無いので、Rubyでいうところのoptparseを自前で書くか、という話になってしまう。
お題にでてくるような単純なケースなら、関数型言語であるErlangならば、

書式:cmdopt -o [-q] [-d{0|1|2}] 文字列 [文字列 ...]

に対して、特にアタマを使わずとも、parserらしきものをでっち上げるのはそれほど困難ではないようだ。

%%
%% パーサ関数 loop(引数リスト, オプションのaccumlator, ファイルリストのaccumlator),
%%

loop([],O,F) -> {O, F};
loop("-o" | L, O, F) -> loop(L, [{$o, "ON"} ++ O], F);
loop("-q" | L, O, F) -> loop(L, [{$q, "ON"} ++ O], F);
loop("-d" | L, O, F) -> loop_wait_N(L, O, F)
loop("-d1" | L, O, F) -> loop(L, [{$d, $1} ++ O], F);
loop("-d2" | L, O, F) -> loop(L, [{$d, $2} ++ O], F);
loop("-d3" | L, O, F) -> loop(L, [{$d, $3} ++ O], F);
loop("-" ++ L, O, F) -> usage(),
                exit(eNotallowdoption);
loop([H | L], O, F) -> loop(L, O, [H ++ F]).

loop_wait_N($1 | L, O, F) -> loop(L, [{$d, $1} ++ O], F);
loop_wait_N($2 | L, O, F) -> loop(L, [{$d, $2} ++ O], F);
loop_wait_N($3 | L, O, F) -> loop(L, [{$d, $3} ++ O], F);
loop(_, O, F) -> usage(),
                exit(eIllegaldebuglevel);

しかし、一方、お題にはクックブックというタグもついているのである。Erlangではコマンドラインオプションなどは普通使わないと思われるのは、プログラミングErlangのp.150で紹介されているような設定ファイルを使うのが便利に見えるからだ。設定ファイルをどう料理するかは、同書のp.338のlib_chanのコードが参考になる。
ミソはfile:consult/1というライブラリ関数だ。これが、1行に1タプルを並べた設定ファイルを読み込んで、タプルのリストを返してくれる。
で、自分が書いたコードは↓だ。

-module(cmdopt).
-export([main/0]).

main()->
        case os:getenv("HOME") of
                false ->
                        exit({ebadenv, "HOME"});
                Home ->
                start_process(Home ++ "/.dokaku.205.conf")
        end.


start_process(ConfigFile) ->
        case file:consult(ConfigFile) of
                {ok, ConfigData} ->
                        case check_terms(ConfigData) of
                                [] ->
                                        exec_service(ConfigData);
                                Errors ->
                                        exit({eServiceConfig, Errors})
                        end;
                {error, Why} ->
                        exit({eServiceConfig, Why})
        end.

exec_service(Conf) ->
        lists:map(fun showconfig/1, Conf).


check_terms(ConfigData) ->
        put("outputflag", false),
        put("filesflag", false),
        L = lists:keysort(1,lists:map(fun check_term/1, ConfigData)),
        case get("outputflag") of
                true ->
                        case get("filesflag") of
                                true -> [X || {error, X} <- L];
                                false -> exit(eMissingfiles)
                        end;
                false -> exit(eMissingoutput)
        end.

check_term({output, "ON"}) -> put("outputflag", true), {ok};
check_term({debug, 1}) -> {ok};
check_term({debug, 2}) -> {ok};
check_term({debug, 3}) -> {ok};
check_term({quiet, "ON"}) -> {ok};
check_term({quiet, "OFF"}) -> {ok};
check_term({files, _FileList}) -> put("filesflag", true), {ok}.

showconfig({files, Content}) ->
        io:format("files :~nnumber of files: ~p~n~p~n", [length(Content), Content]);
showconfig({Name, Content}) ->
        io:format("~p : ~p~n", [Name, Content]).

実行例は次のようになる。~/.dokaku.205.confというのが設定ファイルで、それぞれの行に1つづつタプルが定義されている。タプルの形式は{オプションの名称のatom, オプションの設定値}. となっていて、ピリオドで終えることを忘れないこと。

$ cat ~/.dokaku.205.conf | sort -r
{quiet, "OFF"}.
{output, "ON"}.
{files, ["A", "b"]}.
{debug, 2}.
$ erlc cmdopt.erl
$ erl -noshell -s cmdopt main -s init stop
quiet : "OFF"
output : "ON"
files :
number of files: 2
["A","b"]
debug : 2

プログラミングErlang

プログラミングErlang