Click(Python) のサブコマンドをファイルを分割して実装する

Python の Click、結構便利で簡単に CLI ツールが作れて LGTM ですね（← 使い方あってる？）。
その Click を使ってサブコマンドを実装するときに、ファイルを分割して実装するのに少々苦戦したので、そのことについて適当な例を使って実装の流れを書いてみようと思う。
間違いの指摘や、ここもっとこうした方がいいよ等の助言がありましたら、コメント頂けると幸いです。

ちなみに、サブコマンドを複数ファイルに分割して実装しようと思ったのは、「関心事は分離してそれぞれのファイルに分けた方が良いかなぁ」と思ったからである。

## TL;DR

```py:subcmd.py
@click.command()
def cmd():
    pass
```

```py:cmd.py
from subcmd import cmd as subcmd

@click.group()
def cmd():
    pass

cmd.add_command(subcmd)
```

## バージョン

* Python: 3.8.0
* Click: 7.0

## サンプルとして実装するもの

良い例が思いつかなかったので、足し算と引き算ができる `calculate` コマンドを作ってみようと思う。

```terminal:実行例
$ calculate addition 4 3
7
$ calculate subtraction 4 3
1
```

## ディレクトリ構成

多少順番を変えたり省略したりしているが、下記のような感じである。
ご覧の様(？)に Pipenv を使っている（そろそろ Poetry か何かに乗り換えようかと思っているが、それはまた別のお話）。

```terminal
calculate
├─ calculate
│  ├─ __init__.py
│  ├─ __main__.py
│  └─ cli
│     ├─ __init__.py
│     ├─ command.py
│     ├─ addition
│     │  ├─ __init__.py
│     │  └─ command.py
│     └─ subtraction
│        ├─ __init__.py
│        └─ command.py
├─ Pipfile
├─ Pipfile.lock
└─ setup.py
```

## 下準備

### setup.py を書く

Pipenv を好んで使っている理由に、 `pipenv install -de .` を実行して仮想環境に開発中のツールを編集可能な状態でインストールすることができる点がある（他のパッケージ管理ツールでもできるのかな？）。
そうすることで、仮想環境をアクティベートしたときにコマンドを実行できる。

コマンドを実行するためのエントリーポイントを setup.py に記述する。
下記の例では必要最低限しか記述していない。

```py:setup.py
from setuptools import find_packages, setup

setup(
    name="calculate",
    version="0.0.1",  # 適当
    entry_points={
        "console_scripts": ["calculate=calculate.__main__:main"],
    }
)
```

### パッケージをインストールする

```terminal
$ pipenv install click
$ pipenv install -de .
```

## コマンドの実装

では、順番に実装を進めていこうと思う。
まずはエントリーポイントを用意する。

```py:__main__.py
def main():
    # TODO: コマンドを呼び出す
    pass

# `python -m calculate` という形で呼び出されたときのことを考慮
if __name__ == "__main__":
    main()
```

これで、`calculate` コマンドを実行したときに、`__main__.py` にある `main` 関数が呼び出される。
今はまだコマンドを実装していないので、`main` 関数の中身は空っぽにしておく。

次に `main` 関数で呼び出す最初のコマンドを実装する。

```py:cli.commnad.py
import click

@click.group()
def cmd():
    pass
```

`@click.group()` でデコレートすることで、`cmd` に対してサブコマンドを追加できるようになる。
`calculate` コマンドに相当する関数が実装できたので、先ほどの `__main__.py` の `main` 関数から呼び出すようにする。

```py:__main__.py
from .cli.command import cmd

def main():
    cmd()

# `python -m calculate` という形で呼び出されたときのことを考慮
if __name__ == "__main__":
    main()
```

これで実行してみると、さも CLI ツールのような出力が得られる。

```terminal
$ calculate
Usage: calculate [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.
```

やったね🎉
おっと、まだ目的は達成していなかった。

## サブコマンドの実装

CLI ツールとしての基盤(？)が出来上がったので、次に足し算サブコマンドを実装していく。

```py:cli.addition.command.py
import click

@click.command(name="addition")
@click.argument("augend", type=click.INT)
@click.argument("addend", type=click.INT)
def cmd(augend, addend):
    click.echo(augend + addend)
```

`@click.command` に `name` を渡しているが、こうすることでコマンドに任意の名前を付けることができる。
`@click.argument` では `type` を渡すことで、バリデーションチェックを行ってくれるようになる。
`click.echo` で結果を出力している。
ちなみに、augend は足される数、 addend は足す数、という意味である（TDD 本で知った）。

これで、足し算サブコマンドが実装できたので、`calculate` コマンドに追加してみる。

```py:cli.command.py
import click
from .addition.command import cmd as addition_cmd

@click.group()
def cmd():
    pass

cmd.add_command(addition_cmd)
```

グループにサブコマンドを追加するには、`add_command` メソッドを呼び出し、その引数にサブコマンドとして追加する関数を与えるだけである。
ここまで長々と書いてきたが、**これだけ**である。
このメソッドを見つけるまでにとても時間がかかった。。。

引き算サブコマンドの実装も同様である。
コマンドを実装して、

```py:cli.subtraction.command.py
import click

@click.command(name="subtraction")
@click.argument("minuend", type=click.INT)
@click.argument("subtrahend", type=click.INT)
def cmd(minuend, subtrahend):
    click.echo(minuend - subtrahend)
```

`add_command` で追加する。

```py:cli.command.py
import click
from .addition.command import cmd as addition_cmd
from .subtraction.command import cmd as subtraction_cmd

@click.group()
def cmd():
    pass

cmd.add_command(addition_cmd)
cmd.add_command(subtraction_cmd)
```

これで実行してみると、足し算、引き算の結果が得られる。

```terminal
$ calculate
Usage: calculate [OPTIONS] COMMAND [ARGS]...

Options:
  --help  Show this message and exit.

Commands:
  addition
  subtraction

$ calculate addition 4 3
7

$ calculate subtraction 4 3
1
```

今度こそ、やったね🎉🎉

## 余談

`@click.command(name=<another_name>)` の `name` には、記号も与えることができるみたい。
なので、四則演算を実装した場合、次のように書ける。

```terminal
$ calculate + 4 3
$ calculate - 4 3
$ calculate "*" 4 3
$ calculate / 4 3
```

`*` はワイルドカードと認識されるので、クォーテーションで囲まないといけないし、`/` は問題なく動くがシェルによってはルートと認識して色がつく。
需要があるかわからないけど、ちょっと面白いなと思ったので載せておきます。
試してないけど、他の記号も色々使えるはず。

## まとめ

順番に実装の流れを書いていったので長くなったが、求めていた結果を得るために必要なものは、`add_command` だった。

間違いの指摘や、ここもっとこうした方がいいよ等の助言がありましたら、コメント頂けると幸いです。