jumon - サブコマンドのための小さなフレームワーク

jumon

jumon はサブコマンドを実装するためにフレームワークです。 パッケージの階層構造をそのままサブコマンドとして実装することができます。 jumon は渡された引数から import可能なサブモジュールを探し出し そのモジュールに定義されているmain()に引数を渡して実行します。

あなたがサブコマンドを作成しようとしているのであれば ぜひ jumon の導入を検討してみてください。 きっとあなたのサブコマンドは見通しがよくなるでしょう。

例えば次ようなコマンドを想定します。

  • git
    • git remote
      • git remote update

このようなサブコマンドを作成する場合、 次のようなファイル構成でパッケージを作成できます。

.
|-- __init__.py
`-- remote/
    |-- __init__.py
    `-- update.py

そして最上位の __init__.py に次を記述します。:

import jumon

def main():
    jumon.entry(__name__)

そしてコマンドとして実行するファイルはこのmain()をcallするようにします。(仮にこのパッケージをaaaとしておきましょう):

from aaa import main

if __name__ == '__main__':
    main()

あとは実行したいモジュールのmain()を定義していくだけです。 remote.pyにmain()を定義しておきましょう。:

def main(argv):
    print 'OK'

第一引数は必ず渡されますので受け取るようにしてください (上記例ではargvで受け取っています)。

サブコマンド認識のためのモジュール探索

例えば git remote update を実行する場合 jumon は次の順番でimport可能なモジュールを探します。

  1. TOPMODULE.remote.update
  2. TOPMODULE.remote
  3. TOPMODULE

ヒント

このサブコマンド認識のためのモジュール探索は深い順に探索します。

この時 TOPMODULEjumon.entry() を設置したモジュールになります。 (そのために __name__ を渡しているのです。)

そしてimport可能なモジュールにmain()が定義されているかを確認します。 もし定義されているのであればそれをサブコマンドとして認識します。 サブコマンドとして認識した場合、そのmain()を呼び出します。 この時サブコマンドとして認識した文字列よりも後ろにあるコマンド引数のリストを main()に渡します。

ノート

通常のオプション解析でいうところの sys.argv[1:] が渡されているのと同じ感覚です。 main()内では optparseparseargs などで 適宜オプション解析をしてください。

main()が定義されていない場合はサブコマンドとして認識しません。 その場合、サブコマンド認識のためのモジュール探索が続くことになります。

当然ですが全ての処理をmain()に書かなければならない訳ではなく main()から更に細かい処理に分岐させることができます(むしろそうするべきです)。

jumon.entry() を設置するファイルは __init___.py である必要があります。 サブコマンドをモジュールの階層で定義するというのが jumon の考え方です。 __init__.py でなければサブモジュールを定義できないからです。 しかしパッケージの最上位の __init__.py である必要はありません。

例えば次のようにパッケージを定義したとします。:

.
|-- __init__.py
|-- utils.py
`-- command/
    |-- __init__.py
    `-- remote/
        |-- __init__.py
        `-- update.py

この中の TOPMODULE/command/__init__.pyjumon.entry() を設置することもできます。 この場合 TOPMODULE.command がサブコマンド認識のための探索の最上位となります。

モジュール名と同じ名前の引数を期待するコマンドを実装することはできない

モジュール名と同じ名前の引数を期待するコマンドを実装することはできません。 tree:

.
|-- __init__.py
`-- remote/
    |-- __init__.py
    `-- update.py

__init__.py:

import jumon

def main():
    jumon.entry(__name__)

remote/__init__.py:

def main(argv):
    try:
        if argv[0] == 'remote':
            print 'REMOTE'
    except IndexError:
        pass

remote/update.py:

def main(argv):
    print 'OK'

上記の例で git remote update を実行した時 実行されるのは TOPMODULE.remote.update.main() です。 従って TOPMODULE.remote.main() の中の

print ‘REMOTE’ の行は絶対に実行されません。

なぜならサブコマンド認識のためのモジュール探索は深い方から探索するため TOPMODULE.remote.update.main() で刈り取られてしまうからです。

しかしこのようなケースに遭遇することは稀でしょう。

ノート

オプションでどちらを実行するか指定したいというケースは考えられなくもないですが その場合 jumon では対応できないでしょう。

inserted by FC2 system