LoginSignup
0
0

作業用スクリプトをPrologで書く2

Posted at

作業用スクリプトをPrologで書く2

前回の記事を書いてから知見が溜まり、テンプレートのようなものができたので、ここに載せておきます。

テンプレート

#!/bin/sh swipl

% get_command_line_arguments/1
%   Arguments (string list) : 実行時の引数のリスト
get_command_line_arguments(Arguments) :-
    current_prolog_flag(argv, Arguments).

% run_command/1
%   Command (string) : 実行するコマンド
run_command(Command) :-
  shell(Command, _).

% run_command/2
%   Command (string) : 実行するコマンド
%   ExitCode (int)   : 終了コード
run_command(Command, ExitCode) :-
  shell(Command, ExitCode).

is_empty_string(String) :-
    atom_length(String, 0).

% run_command_with_output/2
%   外部コマンドを実行する(実行結果も取得)
run_command_with_output(Command, Output) :-
    run_command_with_output(Command, Output, ErrorOutput),
    (is_empty_string(ErrorOutput) -> true ; writeln(ErrorOutput)).

% run_command_with_output/3
run_command_with_output(Command, Output, ErrorOutput) :-
    process_create(
        path(sh),
        [ '-c', Command ],
        [ stdout(pipe(Out)), stderr(pipe(Err)) ]
    ),
    (
        read_string(Out, _, Output),
        % Out, Errの両方のStreamをクローズするとエラーになる
        % close(Out),
        read_string(Err, _, ErrorOutput),
        close(Err)
    ).

% help/0
help :-
  format('NAME\n', []),
  format('\t~w - ~w\n', ['mycmd', 'コマンドの説明']),
  format('DESCRIPTION\n', []),
  format('\t~w ~w\n', ['-l', '[directory]']),
  format('\t\t ~w\n', ['ls コマンドを実行する']),
  format('\t~w \t ~w\n', ['-p', 'pwd コマンドを実行する']).

% concat_strings/2
concat_strings([], Result) :- Result = ''.
concat_strings([X], Result) :- Result = X.
concat_strings([X1, X2 | Rest], Result) :-
  atom_concat(X1, X2, ConcatResult),
  concat_strings([ConcatResult | Rest], Result).

% do_command/1
do_command('-l') :- run_command('ls'), !.
do_command('-p') :- run_command('pwd'), !.
do_command(Arg) :-
  atom_concat('invalid argument/1 : ', Arg, Msg),
  writeln(Msg),
  writeln(''),
  help.

% do_command/2
%   オプションに加えて引数がある場合はこちらに書く
do_command('-l', Dir) :-
  concat_strings(['ls ', Dir], Cmd),
  run_command(Cmd), !.
do_command(Arg1, Arg2) :-
  concat_strings(['invalid argument/2 : ', Arg1, ' ', Arg2], Msg),
  writeln(Msg),
  writeln(''),
  help.

% do_command/0
do_command() :-
  writeln('no arguments.'),
  writeln(''),
  help.

% start/0
%   メイン処理 (VSCodeのデバッグのために名称はstartにしておく)
start :-
    get_command_line_arguments(Arguments),
    length(Arguments, Length),
    (
      Length =:= 0 -> do_command()
      ;
      Length =:= 1 ->
        [Arg1] = Arguments,
        do_command(Arg1)
      ;
      Length =:= 2 ->
        [Arg1, Arg2] = Arguments,
        do_command(Arg1, Arg2)
    ).

% メインを実行
:- initialization(start, main).

簡単な説明

設計方針は以下のようにしました。

  1. 引数の数に応じて do_command を呼び分ける
  2. それぞれの do_command で各引数に対応する実際の処理を書く
  3. do_commandの引数の数が同じ述語をまとめて書く
    • ※ まとめて書いておかないとワーニングが表示されるため
  4. どのオプションにも該当しない場合はエラーメッセージを表示する
    • どこでエラーになったかわかりにくいので、do_commandの引数の数に応じてそれぞれ書いておく

まとめ

上記のテンプレートで大体のケースは対応できるかと思います。
今回のテンプレートにはありませんが、可変長引数を利用する場合は単純に引数をリストにして書けばよいので、そこまで複雑にはならないかと思います。
複数のオプションで可変長引数を利用するケースなどは、対応がめんどくさくなると思うので、スクリプトの設計方針を変更したほうが良いかもしれません。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0