ランダムでUNIXコマンドを教えてくれる"cmdsay"を作ってHomebrewに公開するまで

# はじめに
UNIXコマンドって多すぎて覚えられないですよね！そこで、ランダムでコマンドを教えてくれるコマンド**cmdsay**を作りました！
このコマンドは`/usr/bin`配下のコマンドとその説明をランダムで表示します！
`.bashrc`や`.zshrc`にこのコマンドを書いてシェルログインのたびに実行するようにしておけば、あなたの知らないコマンドに巡り会えるかも！

[Kyou13/cmdsay - GitHub](https://github.com/Kyou13/cmdsay)
![cmdsay.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133979/1159125f-4c4c-9eff-5b06-9e78a54711a4.gif)


この記事では**コマンドを作成し、Homebrewパッケージとして公開するまでの手順**を記載しています。

(このコマンド、当初はシェル設定ファイルに直接シェルスクリプト書いており、パッケージとして公開するつもりはなかったのですが、アドベントカレンダーネタとしてつまらないかもと思い、Homebrewパッケージとして公開に至りました。)

# コマンドの作成
**シェルスクリプト**でコマンドの作成を行います。

コマンドの流れは非常にシンプルです。
1. `/usr/bin`配下のコマンドをランダムで1つ選択
2. `whatis`でコマンドの説明を取得
3. 2.を`cowsay`に渡し、シェルに出力

この自作コマンドの中でメインとなっているコマンドは`whatis`と`cowsay`です。
`whatis`はコマンド検索を行うコマンドで、`whatis コマンド名`で該当コマンドと簡易的な説明が表示されます。
`cowsay`は`cowsay 文字列`で、与えた文字列をキャラクターに喋らせます。

`sort`は引数として`-R`を与えるとランダムソートを行います。

```shell:cmdsay.sh
#!/bin/sh
while : ; do
  # ランダムでコマンドを取得
  COMMAND="$(basename $(find /usr/bin -type f | sort -R | head -n 1))"
  # whatisでコマンドの説明を取得
  DESCRIPTIONS="$(whatis $COMMAND)"
  # 取得したコマンドに説明があるとき
  if (echo $DESCRIPTIONS | grep -v "nothing appropriate" > /dev/null); then
    DESCRIPTION=$(echo $DESCRIPTIONS | grep "^$COMMAND" | sort -R | head -n 1)
    if [ -n "$DESCRIPTION" ]; then
      break
    fi
  fi
done
# cowsayのキャラクターをランダムで選択する
COW=$(basename $(ls -1 /usr/local/Cellar/cowsay/*/share/cows/*.cow | sort -R | head -n 1 | sed 's/\.cow$//'))
# 見やすいようにコマンドと説明の間に改行を入れて、最終的な出力をする
echo $DESCRIPTION | sed 's/ - /\'$'\n/' | cowsay -f $COW -n
unset COMMAND
unset DESCRIPTIONS
unset DESCRIPTION
unset COW
```

### 工夫した点
すべてのコマンドに`whatis`による説明があるとは限りません。そのため、ランダムにコマンドを選択する処理を無限ループで包み、`if (echo $DESCRIPTIONS | grep -v "nothing appropriate" > /dev/null);`で説明があるコマンドのみを選択するようにしています。

また、`whatis`は`コマンド名 - 説明`というフォーマットで結果を返すのですが、引数として1つのコマンドを与えても、複数の結果が返ってくることがあります。これは与えたコマンドに関係するコマンドも返すためです。
例えば、`whatis python`を実行すると`python`の他に関係するコマンドの`pydoc`や`pythonw`も返ってきます。

```bash
$ whatis python
pydoc(1)                 - the Python documentation tool
python(1)                - an interpreted, interactive, object-oriented programming language
pythonw(1)               - run python script allowing GUI
```

今回は1つのコマンドの説明を返したいため、この中からから更にランダムで1つ選択するような処理もしています。

`cowsay`は`-f`オプションでキャラクターを指定できます。今回はキャラクターもランダムで表示するようにしています。

# 自作コマンドをHomebrewパッケージの公開
macのパッケージマネージャ**Homebrew**では、`brew tap`を使うことで、公式以外のformulaを追加でき、これによって自作のコマンドやアプリを簡単に公開することが可能です。
formulaとは、ビルド方法を記載したrubyファイルのことです。

パッケージ公開の手順としてはざっくり以下のように行います。
1. 公開するコマンドを作成
2. formulaファイルを作成
3. githubにHomebrew配布用のリポジトリを作成
4. 自作コマンドとformulaファイルをアップロード

### 配布用リポジトリの作成
Homebrew配布用のリポジトリをgithubに作成します。
このとき注意する点として、リポジトリ名を`homwbrew-`から始める必要があります。

また、本来は`homebrew-`から始まるリポジトリを一つ用意すればよいのですが、今回は2つのリポジトリ`homebrew-tap`と`cmdsay`を作成しています。`homebrew-tap`でformulaを管理し、`cmdsay`で自作コマンドのソースコードを管理するためです。

自作コマンドのソースコードをアップロードしたリポジトリで、リリースノートを作り、インストールリンクを作成した後、formulaファイルにインストールリンクを記載します。

### 自作コマンドをアップロード
自作したコマンドをgithubにアップロードします。今回は`cmdsay.sh`をリポジトリ`cmdsay`にpushします。また、後々自作したコマンドをアップデートをすることを想定し、タグを付けます。

```shell:cmdsay
$ git add cmdsay.sh
$ git commit -m 'initial commit'
$ git tag v0.0.1
$ git push origin v0.0.1
```

### Releaseノートの作成
タグをpushしたら、リリースノートを作成します。リポジトリを開いて**releaseタグ**をクリックし、Releaseページに移動したら、**Draft a new release**をクリックします。
<img width="993" alt="Releases_·_Kyou13_cmdsay.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133979/79871c68-3976-f5cb-d22a-d70bd653451b.png">

追加したタグを選択し、タイトルを入力します。そしたらページ下部の**Publish release**をクリックし、Releaseノートが出来ます。ソースコードのリンク先**Source code (tar.gz)**は次のformulaの作成で使用するためコピーしておきます。
<img width="994" alt="Releases_·_Kyou13_cmdsay_.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/133979/40417434-9015-a16a-d15d-ac5105566055.png">


### formulaの作成
`brew`コマンドにはformulaの雛形を生成するコマンドが存在します。引数にはダウンロードリンクを与えます。先程コピーしたものを与えてください。

```
$ brew create [ソースコードのリンク先]
```

するとエディタでformulaが開かれ、編集画面に移ります。
最低限必要なのは`url`,`sha256`と`install`メソッドです。
`url`と`sha256`[^1]は設定済みなので、`install`メソッドを編集していきます。

`install`メソッドでビルド手順を記載します。
`bin.install`にはビルドの結果得られた実行ファイルを指定します。そうすることで、`/usr/local/bin`配下にシンボリックリンクを作成できます。

また、他のformulaと依存関係がある場合は`depends_on`を指定する必要があります。
今回は`cowsay`とシェルスクリプトをコンパイルする`shc(shell script compiler)`を指定します。

最終的なformulaは以下のようになります。

```ruby
class Cmdsay < Formula
  desc "random show cmd description"
  homepage ""
  url "https://github.com/Kyou13/cmdsay/archive/v0.0.1.tar.gz"
  sha256 "[生成されたsha256ハッシュ]"

  depends_on "cowsay"
  depends_on "shc"

  def install
    system "shc", "-f", "cmdsay.sh", "-o", "cmdsay"
    bin.install "cmdsay"
  end
end
```
[^1]: `sha256`が設定済みではない場合やファイルの更新を行った場合は`openssl dgst-sha256 [filename]`を実行して取得する

このformulaを`homebrew-tap/cmdsay.rb`として保存したら、githubのformula管理用リポジトリにアップロードします。


### 自作コマンドをbrewでインストールし確認する
実際にアップロードした自作コマンドをインストールします。

```bash
$ brew tap [githubユーザ名]/tap
$ brew install [githubユーザ名]/tap/[自作コマンド名]
```

私の場合は以下のようになります。

```
$ brew tap Kyou13/tap
$ brew install Kyou13/tap/cmdsay
```

コマンドが実行できるかを確認したら、完了です！

# おわりに
自作パッケージの公開は敷居が高そうと思っていました。しかし、今回始めてHomebrewパッケージを公開してみて、とても簡単な手順で公開することが出来ました！