やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

rangerでコマンドを自作する方法(commands.pyの書き方)

 rangerはTUIファイラ。

作ってみた

 今回はcommands.pyの詳しい書き方について。

commands.py

所在

 自作コマンドはcommands.pyに実装する。設定ファイル同様~/.config/ranger/配下にある。

 Python3言語で実装する。

ファイル 役割
commands.py 自作コマンドの実装
commands_full.py 全コマンドの実装

 以下コマンドでファイルを作成できる。設定ファイルとともにデフォルト状態で。

ranger --copy-config=all

記法

class 任意コマンド名(Command):
    def execute(self):
        pass

 これを~/.config/ranger/commands.pyに追記する。rangerを再起動したら反映される。rangerを起動して:任意コマンド名とすると実行される。

 コードはPythonにより実装する。Pythonはバージョンによって言語機能にかなり差がある。そこで、まずはrangerが使用しているPythonのバージョンを確認する。

$ ranger --version
ranger version: ranger 1.9.2
Python version: 3.5.3 (default, Sep 27 2018, 17:25:39) [GCC 6.3.0 20170516]
Locale: ja_JP.UTF-8

 Pythonのドキュメントは読みにくい。ついでにPythonのクラス機能はJavaC#などと違う。アクセス修飾子がなくすべて丸見え。JavaScript的。

 上記が基礎。すべてのコマンドはCommandクラスを継承していることがわかる。

api

 上記設定ファイル 

api/commands.py class Command

イベントハンドラ

 継承元のapi/commands.py class Commandを見てみると以下のような部分がある。

class Command(FileManagerAware):
    ...
    def execute(self):
        """Override this"""

    def tab(self, tabnum):
        """Override this"""

    def quick(self):
        """Override this"""

    def cancel(self):
        """Override this"""
    ...

 上記Commandクラスを継承した自作コマンドでは、execute関数にコマンドの実行処理を書く。これは上記クラスのメソッドをオーバーライドしていると思われる。

~/.config/ranger/commands.py

class 任意コマンド名(Command):
    def execute(self):
        pass

 オーバーライドできる関数は4つあるらしい。それぞれの役割は何なのか。以下ファイルの先頭コメントに記述があったので抜粋。

~/.config/ranger/commands_full.py

#   execute():   called when the command is executed.
#   cancel():    called when closing the console.
#   tab(tabnum): called when <TAB> is pressed.
#   quick():     called after each keypress.
関数 呼出契機
execute() コマンドが実行されると呼ばれる(<Enter>
cancel() コンソールを閉じるときに呼ばれる(<ESC>
tab(tabnum) キーが押されたときに呼ばれる
quick() キーを押すたびに呼ばれる

 イベントハンドラだった。<TAB>はコンソールでよくある補完機能を実装するためのもの。

戻り値

 各イベントハンドラの戻り値は以下。

tab()戻り値 意味
なし タブ​​補完なし
文字列 コンソールをこの文字列に変更する
リスト/タプル/ジェネレータ その中のすべてのアイテムを巡回する
quick()戻り値 意味
False 何もしない
True 後からコマンドを実行する

 execute(), cancel()は戻り値不要。

引数

 各コマンドはユーザ入力された引数を受け取ることができる。

引数の取得 意味
self.line コンソールに書かれた行全体。
self.args コマンドへのすべての(スペース区切りの)引数のリスト。
self.quantifier このコマンドがキー "X"にマッピングされていた場合ユーザーが6Xを押すとself.quantifier6になる。
self.arg(n) n番目の引数。存在しない場合は空の文字列。
self.rest(n) n番目の引数に続くすべてのもの。例えばコマンドがsearch foo bar a b cの場合rest(2)bar a b cになる。
self.start(n) n番目の引数の前にあるものすべて。例えばコマンドがsearch foo bar a b cの場合start(2)search fooになる。

 コマンドクラスが参照できる値や処理をいくつか紹介する。

概要
self.fm ほとんどの情報を含むfmオブジェクトへの参照。
self.fm.thisdir 現在の作業ディレクトリ(Fileオブジェクト) self.fm.thisdir.pathとすると文字列を取得できる
self.fm.thisfile 現在のファイル(Fileオブジェクト)

 self.fm.thisdir, self.fm.thisfile, はPython言語におけるファイルオブジェクトを返す。fileObj = open('/tmp/a.txt')

FileObjectメンバ 概要
tfile.path ファイルへのパス。
tfile.basename ベース名のみ。
tfile.load_content() ディレクトリの内容を強制的にロードする。
tfile.is_directory ディレクトリかどうかに応じてTrue, Falseになる。

 ファイルオブジェクトのメンバはPythonドキュメントから見つけられなかった。そこで以下のようにしてメンバを調べた。

$ echo -e "1\n2" > a.txt
$ python3
Python 3.5.3 (default, Sep 27 2018, 17:25:39) 
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 

 Pythonインタプリタ内で以下コードを実行。dir()を使う。

Python 3.5.3 (default, Sep 27 2018, 17:25:39) 
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> fileObj = open('/tmp/work/a.txt')
>>> dir(fileObj)
['_CHUNK_SIZE', '__class__', '__del__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_checkClosed', '_checkReadable', '_checkSeekable', '_checkWritable', '_finalizing', 'buffer', 'close', 'closed', 'detach', 'encoding', 'errors', 'fileno', 'flush', 'isatty', 'line_buffering', 'mode', 'name', 'newlines', 'read', 'readable', 'readline', 'readlines', 'seek', 'seekable', 'tell', 'truncate', 'writable', 'write', 'writelines']
>>> 

 たとえばwriteなどのメンバがあることがわかる。一部メンバの使い方はドキュメントに書いてあった。

処理

 コマンドクラスが参照できる処理をいくつか紹介する。

処理 概要
self.fm.notify(string) 与えられた文字列を画面に表示する。
self.fm.notify(string、bad = True) 与えられた文字列を赤で印刷する。
self.fm.reload_cwd() 現在の作業ディレクトリをリロードする。
self.fm.thistab.get_selection() 選択されているすべてのファイルのリスト。
self.fm.execute_console(string) 文字列をrangerコマンドとして実行する。
self.fm.open_console(string) 与えられた文字列でコンソールを開く。
self.fm.move(direction) 指定した方向にカーソルを移動する。引数例(down=3, up=5, right=1, left=1, to=6)。core/actions.py

 以下のようにコードを追っていけば詳しく分かる。継承クラスを追えばいい。

 処理についてはcore/actions.py class Actionsを見ればいい。

対象環境

  • Raspbierry pi 3 Model B+
  • Raspbian stretch 9.0 2018-11-13
  • bash 4.4.12
  • python 2.7.13, pip 9.0.1
  • python3 3.5.3, pip3 9.0.1
  • ranger 1.9.2
$ uname -a
Linux raspberrypi 4.14.98-v7+ #1200 SMP Tue Feb 12 20:27:48 GMT 2019 armv7l GNU/Linux