Pipenvをアップデートしたらcompletionが使えなくなった！？

# 事の起こり
本事象を確認したPipenvのバージョンは以下です。

```bash
$ pipenv --version
pipenv, version 2021.11.9
```

* その他環境情報
  * macOS Monterey バージョン 12.0.1
  * Python 3.8.11, 3.9.6, 3.10.0b4

Homebrewでアップデートを実行したところ、Pipenvが最新版にアップデートされました。その後、シェルの起動時に以下のエラーメッセージが出力される様になりました。。。 :scream: :scream_cat: 

```bash
Usage: pipenv [OPTIONS] COMMAND [ARGS]...
Try 'pipenv -h' for help.

Error: No such option: --completion Did you mean --python?
yuhkiyano:~ $ pipenv
```

Pipenv実行時にTAB補完が出来る様にするために、`--completion`オプションが用意されていますが、どうやら使えない様なのです。よもや`--python`オプションに統合されてしまったのか。。。試しに.bashrcの中の設定を以下の様に変更してみました。

```diff
- eval "$(pipenv --completion)"
+ eval "$(pipenv --python)"
```

案の上ですが、以下のメッセージが表示されました。。。 :scream: :scream_cat: 

```bash
$ . .bashrc 
Error: Option '--python' requires an argument.
```

# 公式ドキュメントを参照したところオプションが変更されていた。。。
公式ドキュメントに救いを求めたところ、何とオプションが変更されていました！！

>☤ Shell Completion
To enable completion in fish, add this to your configuration:
>
eval (env _PIPENV_COMPLETE=fish_source pipenv)
Alternatively, with zsh, add this to your configuration:
>
eval "$(_PIPENV_COMPLETE=zsh_source pipenv)"
Alternatively, with bash, add this to your configuration:
>
eval "$(_PIPENV_COMPLETE=bash_source pipenv)"
Magic shell completions are now enabled!

https://pipenv.pypa.io/en/latest/advanced/#shell-completion

# 公式ドキュメント通りにしてはみたものの今度は別の問題が発生
公式ドキュメント通りに`.bashrc`に設定を追記しました。しかし、シェル起動時に以下のエラーが出力され、問題は解決しませんでした。MacにはそもそもBashのバージョン3がデフォルトでインストールされています。しかし、Pipenvの最新版からcompletionがBashのバージョン3は非対応となり、バージョン4.x以降が必要となりました。よって、筆者のMacにはバージョン5がインストールされています。通常なら動作するはずなのですが。。。

```bash
Traceback (most recent call last):
  File "/usr/local/Cellar/pipenv/2021.11.9/libexec/bin/pipenv", line 8, in <module>
    sys.exit(cli())
  File "/usr/local/Cellar/pipenv/2021.11.9/libexec/lib/python3.10/site-packages/pipenv/vendor/click/core.py", line 1137, in __call__
    return self.main(*args, **kwargs)
  File "/usr/local/Cellar/pipenv/2021.11.9/libexec/lib/python3.10/site-packages/pipenv/vendor/click/core.py", line 1057, in main
    self._main_shell_completion(extra, prog_name, complete_var)
  File "/usr/local/Cellar/pipenv/2021.11.9/libexec/lib/python3.10/site-packages/pipenv/vendor/click/core.py", line 1132, in _main_shell_completion
    rv = shell_complete(self, ctx_args, prog_name, complete_var, instruction)
  File "/usr/local/Cellar/pipenv/2021.11.9/libexec/lib/python3.10/site-packages/pipenv/vendor/click/shell_completion.py", line 45, in shell_complete
    echo(comp.source())
  File "/usr/local/Cellar/pipenv/2021.11.9/libexec/lib/python3.10/site-packages/pipenv/vendor/click/shell_completion.py", line 324, in source
    self._check_version()
  File "/usr/local/Cellar/pipenv/2021.11.9/libexec/lib/python3.10/site-packages/pipenv/vendor/click/shell_completion.py", line 319, in _check_version
    raise RuntimeError(
RuntimeError: Couldn't detect Bash version, shell completion is not supported.
```

# 結論: completionのソースを修正する必要が有りました
エラーの原因となっているソースを開いて見てみると、バージョンの数値を取ってくる処理の部分で転けていることが分かりました。原因となっている箇所(shell_completion.pyの319行目近辺)を以下に示します。

```python
class BashComplete(ShellComplete):
    """Shell completion for Bash."""

    name = "bash"
    source_template = _SOURCE_BASH

    def _check_version(self) -> None:
        import subprocess

        output = subprocess.run(["bash", "--version"], stdout=subprocess.PIPE)
        match = re.search(r"version (\d)\.(\d)\.\d", output.stdout.decode())

        if match is not None:
            major, minor = match.groups()

            if major < "4" or major == "4" and minor < "4":
                raise RuntimeError(
                    _(
                        "Shell completion is not supported for Bash"
                        " versions older than 4.4."
                    )
                )
        else:
            raise RuntimeError(
                _("Couldn't detect Bash version, shell completion is not supported.")
            )

```

標準出力に出力されているエラーメッセージが文字列としてコーディングされている箇所が該当箇所です。よく見ると、`bash --version`コマンドを実行し、実行結果を文字列として取得しています。ここで、Bashバージョン3とBashバージョン5での`bash --version`コマンドの実行結果を示します。

:warning: どうやらバージョン4での実行結果はバージョン3と同じ様なフォーマットの出力になる様です。

```bash:Bashバージョン3
$ /bin/bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin21)
Copyright (C) 2007 Free Software Foundation, Inc.
```

```bash:Bashバージョン5
$ bash --version
GNU bash, バージョン 5.1.8(1)-release (x86_64-apple-darwin20.3.0)
Copyright (C) 2020 Free Software Foundation, Inc.
ライセンス GPLv3+: GNU GPL バージョン 3 またはそれ以降 <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
```

上記の`shell_completion.py`のソース抜粋の11行目で正規表現でマッチングを実行していますが、バージョン5での実行結果に`version`という文言が出て来ておらず、日本語で`バージョン`という表記になっています。故に実行結果が`None`となり、エラー終了していたことが判明しました。

# 正規表現の修正
正規表現の修正が必要になりました。最初は、`version`を取り去るだけで済ませようかとも思いましたが、それで誤った部分がマッチングしてしまうことは避けなければなりません。少し考えて以下の様に修正しました。

```diff:shell_completion.pyの修正
output = subprocess.run(["bash", "--version"], stdout=subprocess.PIPE)
- match = re.search(r"version (\d)\.(\d)\.\d", output.stdout.decode())
+ match = re.search(r"(\d)\.(\d)\.\d+\(1\)-release", output.stdout.decode())
```

バージョン番号(1)-releaseと言う部分は、新旧のバージョン間で変わらなかったので、この部分でマッチングを実行することにしました。

# 修正結果
Pipenvを正常に実行できました。TAB補完も生きています。

```bash
$ pipenv 
Usage: pipenv [OPTIONS] COMMAND [ARGS]...

Options:
  --where                         Output project home information.
  --venv                          Output virtualenv information.
  --py                            Output Python interpreter information.
  --envs                          Output Environment Variable options.
  --rm                            Remove the virtualenv.
  --bare                          Minimal output.
  --man                           Display manpage.
  --support                       Output diagnostic information for use in
                                  GitHub issues.
  --site-packages / --no-site-packages
                                  Enable site-packages for the virtualenv.
                                  [env var: PIPENV_SITE_PACKAGES]
  --python TEXT                   Specify which version of Python virtualenv
                                  should use.
  --three / --two                 Use Python 3/2 when creating virtualenv.
  --clear                         Clears caches (pipenv, pip).  [env var:
                                  PIPENV_CLEAR]
  -v, --verbose                   Verbose mode.
  --pypi-mirror TEXT              Specify a PyPI mirror.
  --version                       Show the version and exit.
  -h, --help                      Show this message and exit.


Usage Examples:
   Create a new project using Python 3.7, specifically:
   $ pipenv --python 3.7

   Remove project virtualenv (inferred from current directory):
   $ pipenv --rm

   Install all dependencies for a project (including dev):
   $ pipenv install --dev

   Create a lockfile containing pre-releases:
   $ pipenv lock --pre

   Show a graph of your installed dependencies:
   $ pipenv graph

   Check your installed dependencies for security vulnerabilities:
   $ pipenv check

   Install a local setup.py into your virtual environment/Pipfile:
   $ pipenv install -e .

   Use a lower-level pip command:
   $ pipenv run pip freeze

Commands:
  check      Checks for PyUp Safety security vulnerabilities and against PEP
             508 markers provided in Pipfile.
  clean      Uninstalls all packages not specified in Pipfile.lock.
  graph      Displays currently-installed dependency graph information.
  install    Installs provided packages and adds them to Pipfile, or (if no
             packages are given), installs all packages from Pipfile.
  lock       Generates Pipfile.lock.
  open       View a given module in your editor.
  run        Spawns a command installed into the virtualenv.
  scripts    Lists scripts in current environment config.
  shell      Spawns a shell within the virtualenv.
  sync       Installs all packages specified in Pipfile.lock.
  uninstall  Uninstalls a provided package and removes it from Pipfile.
  update     Runs lock, then sync.
```

# まとめ
アップデート直後にシェルを再起動したら、Pipenvのcompletionに関するエラーメッセージが表示されることに端を発したトラブルの解決は、最初は簡単に直るだろうと高をくくっていたのですが、予想外に時間が掛かりました。本記事では割愛していますが、subprocessでの実行をechoコマンドに変更してみたり、環境変数を直接Python取る方式での修正を試みたりしたものの、思う様な成果が得られず、最終的に辿り着いた修正が本記事での修正です。折角なので、本家GitHubにIssueを発行して、序でにソースの修正もCommit & Pushしようと思っています。

# 2021/11/14追記: 本家は修正されていました。
記事投稿後、本家のソースを調べてみたところ、修正されていました。どうやらHomebrewでインストールされるバージョン固有の問題の可能性が出て来ました。しばし静観することとします。

![master](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469079/0e8777d9-97b6-58af-7b28-6676015622a9.png)