例えば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番目かで挙動を変える例
