例えばfab task-name:'arg'
の形で実行すると思いますが、タスク名補完したいですよね?うん、したいはず!
デモ
$ fab -l
===========
some header
===========
Available commands:
fetch (yyyymmdd = today)
put
search (yyyymmdd, keyword)
-l
でタスク名一覧と、書いてあればdocが表示されるので、これをごにょって補完出来る様にしたよ!
$ fab <TAB>
tasks
fetch -- (yyyymmdd = today)
search -- (yyyymmdd, keyword)
put
普通のコマンドと同じ様に、一意になるまで入力すれば自動で選ばれるし、Shiftで順次選択も当然出来るよ!
docもサブコマンド説明につっこんだよ!
$ fab -f no_doc.py -l
Available commands:
fetch
search
$ fab -f no_doc.py <TAB>
tasks
fetch search
-f
でデフォルト以外のfabfileを指定していてもちゃんと補完出来るよ!
$ fab -f <TAB>
fabric file
__init__.py fabfile.py no_doc.py
-f
の補完はちゃんとファイルになるよ!
便利!!!!!
解説
結構勉強になったのでまじめに解説
参考にしたページを下部に記載し、細かい説明や他の方法の記載はある程度省略します
この手の資料は大量に既にあるので、詳しく知りたい場合はそちらを参照してください
コマンドに対する補完関数を作成して割り当てる
compdef _fab_tasks fab
function _fab_tasks {
_values 'tasks' 'a' 'b' 'c'
}
- この
xxx.zsh
ファイルをsource xxx.zsh
コマンドで読み込む - これ以降は段階を踏んで書き足していくので、再現してみたい場合は都度
source
を行うこと -
compdef 関数名 コマンド名
で任意のコマンドで<TAB>
を押した時に呼び出す関数を設定できる -
_values 'グループ名' '候補' '候補' ...
で補完対象を追加できる - グループ名は
zstyle ':completion:*' format '%B%d%b'
の様にzshrc
等で設定されていると表示される
$ fab <TAB>
tasks
a b c
補完対象の説明を追加する
_values 'tasks' 'a[do a]' 'b[delete b]' 'c[search c]'
_values
で追加する要素に[xxx]
を結合すると、それが補完対象の説明となる
$ fab <TAB>
tasks
a -- do a
b -- delete b
c -- search c
これだけ知っていれば後はshellの工夫で候補を作れば良い
オプション等の細かい挙動を決める
補完候補の追加はわかったので、今度は補完全体の挙動を設定する
compdef _fab_complete fab
function _fab_tasks {
_values 'tasks' 'a[do a]' 'b[delete b]' 'c[search c]'
}
function _fab_complete {
_arguments \
'*:tasks:_fab_tasks'
}
様々な設定や条件分岐には_arguments
と言う関数を使う
これはいわばScalaやHaskellのmacth-case
の様なものだと解釈した
行毎にパターン:グループ名(略可):命令(略可)
の様な形で記述する
上記のコードだと全てにおいて(*
)_fab_tasks
を呼び出すので、最初に掲載した挙動とかわらない
(compdef
は_fab_complete
に変わった点に注意)
-l
オプションを足す
_arguments \
-l \
'*:tasks:_fab_tasks'
これは上記で言うパターン:略:略
の形で、この-l \
の行だけでfab
コマンドの補完可能なオプションに-l
を増やすことが出来る
fab -<TAB>
の様に-
まで入力してあると(一意なので)自動で-l
が補完される
ちなみに、一切設定を入れていない場合は-
が入力されていてもファイル一覧を探そうとする(何故か何も見つからないけど)
$ fab -<TAB>
`file'
-f
オプションを足す
流石に-l \
だけじゃあ寂しいので、-f
オプションも追加してみる
_arguments \
-l \
-f \
'*:tasks:_fab_tasks'
$ fab -<TAB>
option
-f -l
optionと言うグループ名で候補が選べる様になった
ここからもう少し改良を続けてみる
オプションにも説明を足す
なんとなくお察しかもしれないが、オプションも説明は[xxx]
で設定することが出来る
_arguments \
-l'[list]' \
-f'[fabfile]' \
'*:tasks:_fab_tasks'
$ fab -<TAB>
option
-f -- fabfile
-l -- list
--list
と--file
を足す
-l
と--list
どちらでも同じ挙動をする様にしてみる
_arguments \
{-l,--list}'[list]' \
{-f,--file}'[fabfile]' \
'*:tasks:_fab_tasks'
$ fab -<TAB>
option
--file -f -- fabfile
--list -l -- list
複数のオプションが同じ説明で並んでいるので良さそう
だが、実はちょっと残念な点がある
-l
と--list
の両方を重複して入力できてしまうのだ
$ fab -l -<TAB>
option
--file -f -- fabfile
--list -- list
$ fab -l --file -<TAB>
option
--list -- list
-f -- fabfile
2度は指定しないよね
排他制御をする
パターン
の前に'(x, y)'
の形で排他指定をすることが出来る
_arguments \
'(- *)'{-l,--list}'[list]' \
'(-f --file)'{-f,--file}'[fabfile]' \
'*:tasks:_fab_tasks'
$ fab -<TAB>
option
--file -f -- fabfile
--list -l -- list
補完候補は今まで通りだけど
$ fab -f -<TAB>
option
--list -l -- list
-f
が入力済みの場合は--file
は候補にならない
また、(- *)
の様にすることで、そのオプション以降は何も補完されなくすることも可能
今回の--list
系のオプションや、ヘルプで有効だ
$ fab -l <TAB>
no more arguments
$ fab -f -l <TAB>
no more arguments
オプション毎に次に補完させたい候補を分ける
パターン
に続くグループ名(略可):命令(略可)
の部分を記述すると、そのオプションの次の候補を任意に設定できる
-f
には_files
を設定してみよう
_arguments \
'(- *)'{-l,--list}'[list]' \
'(-f --file)'{-f,--file}'[fabfile]:fabric files:_files' \
'*:tasks:_fab_tasks'
$ fab -f <TAB>
fabric files
__init__.py __init__.pyc fabfile.py fabfile.pyc no_doc.py no_doc.pyc
グループ名がfabric files
で候補がファイル一覧になっている
ちなみに他にも_users
等もあるので、気になったら調べてみるとおもしろいかも
オプション等に関してはここまで
-f fabfile
に応じて補完候補を動的に切り換える
最後に少しだけ、fabricの話に戻ります
基本はfab -l
の結果をパースすれば良いんだけど、一点だけどう実装するべきかわからないことがあってBUFFER
という変数を使ってみた
困った点はfab -l
とfab -f another_fabfile.py -l
の結果が違う点で、
BUFFER
という変数は現在のコマンドラインに入力されている行そのものが入っている
fabfile=`echo $BUFFER | awk '{for(i=1; i <= NF; i++) if($i == "-f") print $(i + 1) }'`
if [ -n "$fabfile" ]; then list=`fab -f $fabfile --list`; else list=`fab --list`; fi
こんな感じで現在入力中の-f
の状態によって-l
の結果を動的に得ることにした
(-f
の次がanother_fabfile.py
であることは、先述の「-f
の次はfabfile
を補完する」という設定である程度保証している)
完成形
compdef _fab_complete fab
function _fab_tasks {
in_header=1
tasks=()
IFS_BK=$IFS
IFS=$'\n'
fabfile=`echo $BUFFER | awk '{for(i=1; i <= NF; i++) if($i == "-f") print $(i + 1) }'`
if [ -n "$fabfile" ]; then list=`fab -f $fabfile --list`; else list=`fab --list`; fi
while read line
do
if [[ $line = '' ]] ;then
continue
fi
if [[ $in_header -ne 1 ]] ;then
tasks+=(`echo $line | awk '{printf("%s", $1); $1=""; if ($0 != "") printf("[%s]", $0)}' | sed -e 's/\[ /\[/g'`)
fi
if [[ $line =~ 'Available commands:' ]]; then
in_header=0
fi
done <<END
$list
END
_values 'tasks' $tasks
IFS=$IFS_BK
}
function _fab_complete {
_arguments \
'(- *)'{-l,--list}'[print list of possible commands and exit]' \
'(-f --file)'{-f,--file}"[python module file to import, e.g. '../other.py']:fabric file:_files" \
'*:tasks:_fab_tasks'
}
shellを書き慣れていないので多分イケてないコードだろうし、すごい時間かかったけど、挙動と得た知識には大変満足している
参考資料
今回参考にしたページ
読んで得た知識を抜粋して箇条書きで掲載する
- zsh の補完関数の自作導入編
- 諸設定
- 補完関数の読込ルール等
- 全体の流れ
- zsh補完関数を自作すると便利 - hakobe-blog
-
compadd
の例 - prefixを設定する例
- 漢のzsh (18) コマンド補完設定 - daemonコマンド編(2) | マイナビニュース
-
_arguments
の例 - zshのある暮らし2 - 補完ファイル
-
_arguments
の排他の例 - ZSH - Writing own completion functions | Ask QL
- 補完しようとしているのがn番目かで挙動を変える例