スキップしてメイン コンテンツに移動

pipenv の zsh 補完

Django の開発中の不満の一つが pipenv run の補完だ。 zsh は補完が凄いと聞いていたのに bash から移行してストレスが増えた。 bash はとりあえずファイルがそこにあればいつでも補完が効く。 pipenv run python と打った後にはカレントの manage.py が補完できる。 それに対して、zsh の補完はもっと inteligent で文脈を理解するのだが、何の指定もない文脈では何も補完できない。 本当に使えない。 まあお気づきかと思うが、zsh が使えないというより設定ファイルが酷い、という話、のはずだ。

調査

まずは状況を確認しよう。 Mac の homebrew で pipenv を入れていて、シェルは zsh。 .zshrc で補完の設定は最低限のものだけ。

autoload -Uz compinit && compinit
zstyle ':completion:*' completer _complete _ignored _files
.zshrc

pipenv の補完設定は /usr/local/share/zsh/site-functions/_pipenv にあるが、これは /usr/local/Cellar/pipenv/2023.11.15/share/zsh/site-functions/_pipenv へのシンボリックリンクだ。 homebrew がこの設定ファイルをどこから持ってきているかというと、pipenv.rb の中で次の補助関数で生成している。

generate_completions_from_executable(libexec/"bin/pipenv", shells:                 [:fish, :zsh],
                                                           shell_parameter_format: :click)
pipenv.rb

generate_completions_from_executable は、formula.rb に定義がある。 shell_parameter_format を :click にして shell に :zsh を渡すと最終的に env _PIPENV_COMPLETE=zsh_source pipenv というような呼び出しが行われる。 pipenv のドキュメントにある eval "$(_PIPENV_COMPLETE=zsh_source pipenv)" にたどり着くのだ。 つまり、pipenv 側が用意した設定だと言って良い。

click とは何ぞや?

Click is a Python package for creating beautiful command line interfaces in a composable way with as little code as necessary. It’s the “Command Line Interface Creation Kit”. It’s highly configurable but comes with sensible defaults out of the box.

Welcome to Click

Python で CLI を作るときのツールキットらしい。 シェルの補完設定も作れる(Shell Completion)。 つまり、pipenv はこれを利用して作られている、と。 コードを確認してみよう。

@cli.command(
    short_help="Spawns a command installed into the virtualenv.",
    context_settings=subcommand_context_no_interspersion,
)
@common_options
@argument("command")
@argument("args", nargs=-1)
@pass_state
def run(state, command, args):
    """Spawns a command installed into the virtualenv."""
    from pipenv.routines.shell import do_run

    do_run(
        state.project,
        command=command,
        args=args,
        python=state.python,
        pypi_mirror=state.pypi_mirror,
    )
pipenv/cli/command.py

このデコレーターたちが click のもので、デコレーターの引数によって補完の挙動が変わるようだ(詳細は把握していない)。 ざっとドキュメントを見た感じ、残りはただのコマンドラインみたいな指定ができる方法が見当たらない。 ということでこの仕組みのまま直す方法は無さそう…。

解決

pipenv に手を入れて解決するのは難しそうなので、zsh の補完で pipenv run にだけ適用するパターンを作りたい。 遠い昔に買った「zshの本」に nice などに適用される方法が載っていたのでそれを参考にする。

#compdef -p pipenv

local cur in_run
cur=$CURRENT
in_run=0
while (( cur - 2 )) do
    if [[ $words[$(( $cur - 1 ))] == "run" ]]; then
        in_run=1
        break
    fi
    (( cur-- ))
done
if [[ $in_run == 1 ]]; then
    while (( cur - 1 )) do
        shift words
        (( CURRENT-- ))
        (( cur-- ))
    done
    _normal
else
    _pipenv
fi

大雑把に説明すると、pipenv の引数に run が入っている場合に、それより前の(グローバルオプションなども含む)部分を無視した文脈で補完し直す、という方針になる。

注意点1:「pipenv run にだけ適用」を zsh 側で書けなかったので、pipenv に適用する補完関数として、run が入ってなければ _pipenv での補完に戻る、という書き方をしている。

注意点2: 先頭行の「#compdef -p pipenv」は、"#compdef pipenv" と書いたら pipenv に対する補完の定義、ということになると思うのだが、既に _pipenv が定義されていて二つめは受け付けてもらえないようだったので、ファイルパターン(glob パターン)で指定した。

あとはこのファイルを /some/where/_pipenv_run としておいて、compinit の前で fpath に /some/where を追加したら、起動時に読み込んでもらえる。

もう少し効率よく書けたらいいと思うのだが、ひとまずはこれで良しとしよう。

このブログの人気の投稿

「函数」音訳説について探してみる

Twitter に書こうかと思ったが、ちょっと長くなるのでこっちに書くことにする。 「函数」が音訳というデマと、本当の語源 というブログ記事について ツイートされてるの を見た。 詳しい内容はリンクを辿って読んでみて欲しい。 その記事中の「函数」音訳説という部分で、引用されているものが意外と新しい1980年代以降のものなので、さすがにもっと古いだろう、と探してみた。 1. 武部良明「漢字の用法」(1976) このほか武部良明『漢字の用法』(角川書店1976)にも同様の記述があるという(未確認)。 とあって、たまたま同書(三版)を持っているので調べてみた。 「関数」ではなく「関・函」の項にあった。 「函」は「いれる」意味。 したがって、「函数」という漢字の組み合わせからは、「対応して定まる数」という意味は出てこない。 「函数」については function という原語の音 fun を、「函」の音カン(現代中国語 han)で写したものとされている。 これに対し現代表記の「関数」は、全体の意味を「かかりあう数」と考え、「函」の部分を、同音で「かかりあう」意味の「関」に書き換えたものである。 2. 遠山啓「関数を考える」(1972) 近所の図書館で見つけた。 会話体の文章の中で、先生役が言っている。 はっきりはわからないが, 中国語の発音では function と似ていて, しかも意味も近いらしいのだ. 3. 遠山啓「数学は変貌する」(1971) 2012年にちくま学芸文庫から出た「現代数学入門」所収。 「関数を考える」が中学生ぐらいを対象にしたシリーズの一冊なので、そこでいきなり初披露ということは無いだろうと見当をつけて遡って探した。 機能とは簡単にいうと働きです。 ライプニッツが初めてファンクションという言葉を使った. ライプニッツはドイツ人ですが, ドイツ語は当時はいなかの言葉みたいで, フランスが文化の中心であったから, フランス語で書いてありますので, フォンクション (fonction) です. もとは日本では「函数」, あとになって「関数」と改めたのです. なぜこんな函の数という妙な言葉を使ったか, これは中国からきた字です. 中国人は各国語を音まで似せて訳すことがうまい. フォンクションは中国語で函数を中国読みしますと非常に似ているのだそ...

2025年の読書

読書メーターの記録 によるとマンガも入れて101冊。 久しぶりに年100冊に乗った。 前年からの勢いのままに菌類関係の本をいくつか読んだが深澤遊の「キノコとカビの生態学」「枯木ワンダーランド」は面白かった。 後半はファーブル昆虫記を読破した。岩波文庫の古い訳で読んだのが良かったのか良くなかったのか。 数学関係は、前半表現論の本を読んで表現とは加群のことであるという言葉で何だかすっきりした。 年の終わり近くはラングランズ予想というか楕円曲線と保型形式とガロワ表現とみたいな内容の本をいくつか。「ラングランズ予想」というそのものズバリなタイトルの本も出たのでその内読みたい。 マンガは、「ヴィンランド・サガ」も「アルテ」も「推しが武道館に行ってくれたら死ぬ」も完結して、読み続けるタイトルが少なくなってきた。

Schur Number

Youtube の Numberphile チャンネルで最近出た Schur Number の動画を見た、 そこでのシューア数の定義は OEIS の "version 1" の方 Smallest number such that for any n-coloring of the integers 1, ..., a(n) no color is sum-free, that is, some color contains a triple x + y = z. だったが、 "version 2" の Largest number such that there is an n-coloring of the integers 1, ..., a(n) such that each color is sum-free, that is, no color contains a triple x + y = z. の方が解り易いからこちらで話を進める。version 2 の方が 1 小さい値になるというだけの違いがある。 動画の内容は \(a(n) = 160\) になる証明が2ペタバイトのデータ量で凄い、みたいなこと。 結局、全ての n-coloring を確認するしかないとか何とか。 ここではそういう難しい部分の話はせずに、1, 4, 13, 44, 160 という増え方が大体 3 倍を超えるぐらいというのを納得する議論をしてみたい。 よりはっきり言うと、1, 4, 13 と増える \(a_{n+1} = 3a_n + 1\) という数列が下限になることを示す。 一つ注意を述べると、n-coloring を考えるというより \(\{1,\ldots,k\}\) の和抜き(sum-free)集合による被覆を考えれば良い。被覆ができれば彩色を作れる(自由度を失うだけ)ので。 そういうわけで \(\{1\}\) の被覆を考え始めるわけだが、そちらも \(\{1\}\) で良い。 次に \(\{1, 2\}\) の被覆を考えるが \(1+1=2\) なので \(\{1,2\}\) は和抜きではない。よって \(\{1, 2\}\) の和抜き集合による被覆は \(...