=encoding utf8

=head1 NAME

optex - 汎用コマンドオプションラッパー

=head1 VERSION

Version 1.04

=head1 SYNOPSIS

B<optex> I<command> [ B<-M>I<module> ] ...

または I<command> -> B<optex> のシンボリックリンク、あるいは

B<optex> I<options> [ -l | -m ] ...

    --link,   --ln  create symlink
    --unlink, --rm  remove symlink
    --ls            list link files
    --rc            list rc files
    --nop, -x       disable option processing
    --[no]module    disable module option on arguments

=cut
=head1 DESCRIPTION

B<optex> は Perl モジュール L<Getopt::EX> を利用した汎用のコマンドオプション処理ラッパーです。ユーザーはシステム上のあらゆるコマンドに対して独自のオプションエイリアスを定義でき、モジュール式の拡張性を提供します。

対象コマンドは引数として与えます:

    % optex command

または B<optex> へのシンボリックリンクされたファイルとして:

    command -> optex

設定ファイル F<~/.optex.d/>I<command>F<.rc> が存在する場合は、実行前に評価され、それを用いてコマンド引数が前処理されます。


=head2 OPTION ALIASES

macOS の C<date> コマンドには C<-I[TIMESPEC]> オプションがありません。B<optex> を使えば、F<~/.optex.d/date.rc> に以下の設定を用意することで実装できます。

    option -I        -Idate
    option -Idate    +%F
    option -Iseconds +%FT%T%z
    option -Iminutes +%FT%H:%M%z
    option -Ihours   +%FT%H%z

    option --iso-8601         -I
    option --iso-8601=date    -Idate
    option --iso-8601=seconds -Iseconds
    option --iso-8601=minutes -Iminutes
    option --iso-8601=hours   -Ihours

その後、次のコマンドは期待どおり動作します。

    % optex date -Iseconds

コマンド検索パスに C<< date -> optex >> のシンボリックリンクが見つかれば、未対応のオプション付きでも標準コマンドと同様に使用できます。

    % date -Iseconds

共通設定は F<~/.optex.d/default.rc> に保存され、B<optex> を介して実行されるすべてのコマンドにそのルールが適用されます。

実際、C<--iso-8601> オプションは次のようにより簡単に定義できます:

    option --iso-8601 -I$<shift>

これはほとんどの場合うまく動作しますが、次のように他のオプションに先行して単独の C<--iso-8601> オプションだけがある場合は失敗します:

    % date --iso-8601 -u

=head2 COMMAND ALIASES

B<optex> のコマンドエイリアスはシェルの alias 機能と変わりませんが、ツールやスクリプトからコマンドとして実行でき、設定ファイルで一元管理できる点が有効です。

コマンドエイリアスは次のように設定ファイル (F<~/.optex.d/config.toml>) で設定できます:

    [alias]
        tc = "optex -Mtextconv"

次のように C<tc> から C<optex> へのシンボリックリンクを作成できます:

    % optex --ln tc

そして F<$HOME/.optex.d/bin> を C<PATH> 環境に含めてください。

C<textconv> モジュールは引数として与えたファイルをプレーンテキストに変換するために使用できます。このように定義すれば、Word ファイルを次のように比較できます。

    % tc diff A.docx B.docx

エイリアス名は rc ファイルとモジュールディレクトリの検索に使用されます。上記の例では F<~/.optex.d/tc.rc> と F<~/.optex.d/tc/> が参照されます。

設定ファイルにシェルスクリプトを書くことも可能です。次の例は C シェルの C<repeat> コマンドの実装です。

    [alias]
            repeat = [ 'bash', '-c', '''
                while getopts 'c:i:' OPT; do
                    case $OPT in
                        c) count=$OPTARG;;
                        i) sleep=$OPTARG;;
                    esac
                done; shift $((OPTIND - 1))
                case $1 in
                    [0-9]*) count=$1; shift;;
                esac
                while ((count--)); do
                    eval "$*"
                    [ "$sleep" ] && (( count > 0 )) && sleep $sleep
                done
            ''', 'repeat' ]

L<CONFIGURATION FILE> セクションを参照してください。

=head2 MACROS

マクロ C<define> を使って複雑な文字列を構成できます。次の例は F<~/.optex.d/awk.rc> に記述する、テキスト中の母音を数える awk スクリプトです。

    define __delete__ /[bcdfgkmnpsrtvwyz]e( |$)/
    define __match__  /ey|y[aeiou]*|[aeiou]+/
    define __count_vowels__ <<EOS
    {
        s = tolower($0);
        gsub(__delete__, " ", s);
        for (count=0; match(s, __match__); count++) {
            s=substr(s, RSTART + RLENGTH);
        }
        print count " " $0;
    }
    EOS
    option --vowels __count_vowels__

これは次のように使用できます:

    % awk --vowels /usr/share/dict/words

複雑なオプションを設定する場合、C<expand> ディレクティブが有用です。C<expand> は C<option> とほぼ同様に動作しますが、ファイルスコープ内でのみ有効で、コマンドラインオプションとしては利用できません。

    expand repository	( -name .git -o -name .svn -o -name RCS )
    expand no_dots	! -name .*
    expand no_version	! -name *,v
    expand no_backup	! -name *~
    expand no_image 	! -iname *.jpg  ! -iname *.jpeg \
			! -iname *.gif  ! -iname *.png
    expand no_archive	! -iname *.tar  ! -iname *.tbz  ! -iname *.tgz
    expand no_pdf	! -iname *.pdf

    option --clean \
            repository -prune -o \
            -type f \
            no_dots \
            no_version no_backup \
            no_image \
            no_archive \
            no_pdf

    % find . --clean -print


=head2 MODULES

B<optex> はモジュール拡張にも対応しています。C<date> の例では、モジュールファイルは F<~/.optex.d/date/> ディレクトリにあります。デフォルトモジュール F<~/.optex.d/date/default.pm> が存在すれば、実行のたびに自動的に読み込まれます。

これは通常のPerlモジュールなので、package宣言と最後の真値が必要です。これらの間には、あらゆる種類のPerlコードを記述できます。たとえば、次のプログラムは、C<date> コマンドを実行する前に、環境変数 C<LANG> を C<C> に設定します。

    package default;
    $ENV{LANG} = 'C';
    1;

    % /bin/date
    2017年 10月22日 日曜日 18時00分00秒 JST

    % date
    Sun Oct 22 18:00:00 JST 2017

他のモジュールは C<-M> オプションで読み込みます。他のオプションと異なり、C<-M> は引数リストの先頭に置かなければなりません。F<~/.optex.d/date/> ディレクトリ内のモジュールファイルは C<date> コマンドに対してのみ使用されます。モジュールが F<~/.optex.d/> ディレクトリに置かれている場合は、すべてのコマンドから使用できます。

C<-Mes> モジュールを使いたい場合は、次の内容で F<~/.optex.d/es.pm> ファイルを作成してください。

    package es;
    $ENV{LANG} = 'es_ES';
    1;

    % date -Mes
    domingo, 22 de octubre de 2017, 18:00:00 JST

指定されたモジュールがライブラリパスで見つからない場合、B<optex> はそのオプションを無視し、即座に引数処理を停止します。無視されたオプションは対象コマンドへそのまま渡されます。

モジュールはサブルーチン呼び出しでも使用されます。F<~/.optex.d/env.pm> モジュールが次のようだとします:

    package env;
    sub setenv {
        while (($a, $b) = splice @_, 0, 2) {
            $ENV{$a} = $b;
        }
    }
    1;

すると、より汎用的に使えるようになります。次の例では、最初の形式は読みやすく、2つ目はエスケープすべき特殊文字がないため入力しやすい形式です。

    % date -Menv::setenv(LANG=de_DE) # need shell quote
    % date -Menv::setenv=LANG=de_DE  # alternative format
    So 22 Okt 2017 18:00:00 JST

オプションのエイリアスは、ファイル末尾の特別なリテラル C<__DATA__> に続けてモジュール内で宣言できます。これを使うと、用途に応じて複数のオプションセットを用意できます。汎用的な B<i18n> モジュールを考えてみましょう:

    package i18n;
    1;
    __DATA__
    option --cn -Menv::setenv(LANG=zh_CN) // 中国語 - 簡体字
    option --tw -Menv::setenv(LANG=zh_TW) // 中国語 - 繁体字
    option --us -Menv::setenv(LANG=en_US) // 英語
    option --fr -Menv::setenv(LANG=fr_FR) // フランス語
    option --de -Menv::setenv(LANG=de_DE) // ドイツ語
    option --it -Menv::setenv(LANG=it_IT) // イタリア語
    option --jp -Menv::setenv(LANG=ja_JP) // 日本語
    option --kr -Menv::setenv(LANG=ko_KR) // 韓国語
    option --br -Menv::setenv(LANG=pt_BR) // ポルトガル語 - ブラジル
    option --es -Menv::setenv(LANG=es_ES) // スペイン語
    option --ru -Menv::setenv(LANG=ru_RU) // ロシア語

これは次のように使えます:

    % date -Mi18n --tw
    2017年10月22日 週日 18時00分00秒 JST

F<~/.optex.d/optex.rc> で自動読み込みモジュールを次のように宣言できます:

    autoload -Mi18n --cn --tw --us --fr --de --it --jp --kr --br --es --ru

するとモジュールオプションなしで使えるようになります。この場合、オプション C<--ru> は自動的に C<-Mi18n --ru> に置き換えられます。

    % date --ru
    воскресенье, 22 октября 2017 г. 18:00:00 (JST)

モジュール C<i18n> は L<Getopt::EX::i18n> として実装され、この配布物に含まれています。したがって、追加インストールなしで上記のように使用できます。

モジュールは C<__DATA__> セクション内の C<builtin> ディレクティブを使って組み込みオプションも定義できます。組み込みオプションは L<Getopt::Long> によって処理され、対象コマンド名の前に指定しなければなりません。例:

    optex -Mxform --xform-visible=2 cat file

ここで C<--xform-visible> は C<xform> モジュールで定義された組み込みオプションです。

=head1 STANDARD MODULES

標準モジュールは C<App::optex> にインストールされており、C<App::optex> プレフィックスあり／なしのどちらでも指定できます。

=over 4

=item -MB<help>

利用可能なオプション一覧を表示します。オプション名は置換形、または定義されていればヘルプメッセージとともに出力されます。ヘルプメッセージを省くには B<-x> オプションを使用します。

オプション B<--man> または B<-h> は、可能であればドキュメントを表示します。オプション B<-l> はモジュールのパスを表示します。オプション B<-m> はモジュール本体を表示します。他のモジュール指定の後に使うと、最後に宣言されたモジュールに関する情報を表示します。次のコマンドは B<second> モジュールに関するドキュメントを表示します。

    % optex -Mfirst -Msecond -Mhelp --man

=item -MB<debug>

デバッグメッセージを出力します。

=item -MB<util::argv>

コマンド引数を操作するモジュール。詳細は L<App::optex::util::argv> を参照してください。

=item -MB<util::filter>

コマンドの入出力フィルタを実装するモジュール。詳細は L<App::optex::util::filter> を参照してください。

=back

=head1 Getopt::EX MODULES

独自のモジュールに加えて、B<optex> は C<Getopt::EX> のモジュールも使用できます。標準の C<Getopt::EX> モジュールとしては次のものがインストールされています。

=over 4

=item -MB<i18n> (L<Getopt::EX::i18n>)

次のようにすると、ギリシャ暦を表示できます:

    optex -Mi18n cal --gr

=back

=head1 OPTIONS

これらのオプションは、B<optex> がシンボリックリンクから実行された場合は有効ではありません。

=over 4


=item B<--link>, B<--ln> [ I<command> ]

F<~/.optex.d/bin> ディレクトリにシンボリックリンクを作成します。


=item B<--unlink>, B<--rm> [ B<-f> ] [ I<command> ]

F<~/.optex.d/bin> ディレクトリのシンボリックリンクを削除します。


=item B<--ls> [ B<-l> ] [ I<command> ]

F<~/.optex.d/bin> ディレクトリのシンボリックリンクファイルを一覧表示します。


=item B<--rc> [ B<-l> ] [ B<-m> ] [ I<command> ]

F<~/.optex.d> ディレクトリの rc ファイルを一覧表示します。


=item B<--nop>, B<-x> I<command>

オプション操作を停止します。そうでない場合はフルパス名を使用してください。


=item B<-->[B<no>]B<module>

B<optex> はデフォルトで対象コマンドに対するモジュールオプション (-M) を扱います。しかし、同じオプションを独自の目的で使用するコマンドもあります。オプション B<--nomodule> はその動作を無効化します。他のオプション解釈は引き続き有効で、rc やモジュールファイルでモジュールオプションを使用しても問題ありません。


=item B<--exit> I<status>

通常 B<optex> は実行したコマンドの終了ステータスで終了します。このオプションはそれを上書きし、指定したステータスコードで終了するように強制します。


=back


=head1 CONFIGURATION FILE

起動時に、B<optex> は TOML 形式で記述される想定の設定ファイル F<~/.optex.d/config.toml> を読み込みます。

=head2 PARAMETERS

=over 4

=item B<no-module>

B<optex> がモジュールオプション B<-M> を解釈しないコマンドを設定します。対象コマンドがこのリストに見つかった場合、B<optex> に B<--no-module> オプションが与えられたかのように実行されます。

    no-module = [
        "greple",
        "pgrep",
    ]

=item B<alias>

コマンドエイリアスを設定します。例:

    [alias]
        pgrep = [ "greple", "-Mperl", "--code" ]
        hello = "echo -n 'hello world!'"

コマンドエイリアスはシンボリックリンクからでもコマンド引数からでも呼び出せます。

=back


=head1 FILES AND DIRECTORIES

=over 4


=item F<PERLLIB/App/optex>

システムモジュールディレクトリ。


=item F<~/.optex.d/>

個人用ルートディレクトリ。


=item F<~/.optex.d/config.toml>

設定ファイル。


=item F<~/.optex.d/default.rc>

共通の起動ファイル。


=item F<~/.optex.d/>I<command>F<.rc>

I<command> 用の起動ファイル。


=item F<~/.optex.d/>I<command>F</>

I<command> 用のモジュールディレクトリ。


=item F<~/.optex.d/>I<command>F</default.pm>

I<command> 用のデフォルトモジュール。


=item F<~/.optex.d/bin>

シンボリックリンクを保存するデフォルトのディレクトリ。

必須ではありませんが、B<optex> 用のシンボリックリンクを収める特別なディレクトリを作成し、コマンド検索パスに配置するのはよい考えです。そうすれば、パスへの追加/削除やシンボリックリンクの作成/削除が容易になります。

=back


=head1 ENVIRONMENT

=over 4

=item OPTEX_ROOT

デフォルトのルートディレクトリ F<~/.optex.d> を上書きします。

=item OPTEX_CONFIG

デフォルトの設定ファイル F<OPTEX_ROOT/config.toml> を上書きします。

=item OPTEX_MODULE_PATH

コロン (C<:>) 区切りでモジュールパスを設定します。これらは標準パスの前に挿入されます。

=item OPTEX_BINDIR

デフォルトのシンボリックリンクディレクトリ F<OPTEX_ROOT/bin> を上書きします。

=back


=head1 SEE ALSO

L<Getopt::EX>, L<Getopt::EX::Loader>, L<Getopt::EX::Module>

L<App::optex::textconv>

L<App::optex::xform>

=head1 AUTHOR

Kazumasa Utashiro


=head1 LICENSE

You can redistribute it and/or modify it under the same terms
as Perl itself.

Copyright ©︎ 2017-2025 Kazumasa Utashiro


=cut
