bundle outdatedコマンドの出力を汎用的な形式に変換するgemを作りました

Rubyist の皆さん、こんにちは。

この度、[bundle_outdated_formatter](https://github.com/emsk/bundle_outdated_formatter) という便利 gem を作ってみましたのでご紹介します。

## 解決したい課題

日々の開発にて Bundler は既にお使いになっていることと思いますが、その Bundler の機能のひとつ、[`bundle outdated` コマンド](http://bundler.io/v1.15/bundle_outdated.html)をご存知でしょうか。
`bundle outdated` は、バンドルしている gem のうち、最新でない gem をリストアップしてくれる便利なコマンドです。

ただ、その出力結果は独自の形式になっているため、例えば、

* 更新が必要な gem の数を Jenkins のような CI ツール上でグラフ化し、日々の推移を分析したい
* 更新が必要な gem の一覧を Markdown が有効な Wiki 等に視覚的に見やすい形で記載したい

というようなことを思っても、出力を加工する一手間が必要でした。
怠惰なプログラマである皆さんは、手間は極力減らしていきたいと思うのは当然ですよね。

## bundle_outdated_formatter とは

bundle_outdated_formatter は、`bundle outdated` の独自形式の出力を汎用性の高い形式に変換して出力してくれる、シンプルなコマンドラインツールです。
bundle_outdated_formatter を使って出力を整形することで、前述のような課題を解決できます。

現時点（v0.3.0）でサポートしている形式は、下記の通りです。

* Markdown
* JSON
* YAML
* CSV
* TSV
* XML
* HTML

なお、実行には Ruby 2.0 以上が必要です。

## インストール方法

他の gem と同様に、Gemfile に下記のように記載して `bundle install` コマンドを実行してください。

```ruby:Gemfile
gem 'bundle_outdated_formatter'
```

下記のように `gem` コマンドでもインストールできます。

```
gem install bundle_outdated_formatter
```

## 使い方

下記のように `bundle outdated` コマンドの後ろにパイプでつなげて `bof` を指定するだけです。

```
bundle outdated | bof
```

※オプションを指定しない場合、デフォルトの Markdown 形式で出力されます

## オプション

| オプション | エイリアス | 説明 | デフォルト値 |
| :----- | :---- | :---------- | :------ |
| `--format` | `-f` | 変換後の形式です。<br>`markdown`、`json`、`yaml`、`csv`、`tsv`、`xml`、`html`のどれかを指定してください。 | `markdown` |
| `--pretty` | `-p` | 整った形で出力したい場合は `true` を指定してください。<br>このオプションは、`json`、`xml`、`html` 形式の場合のみ有効です。 | `false` |

## 実行例

`bundle outdated` コマンドで下記のように出力された場合の変換例を示します。

```
Fetching gem metadata from https://rubygems.org/..........
Fetching version metadata from https://rubygems.org/...
Fetching dependency metadata from https://rubygems.org/..
Resolving dependencies...

Outdated gems included in the bundle:
* faker (newest 1.6.6, installed 1.6.5, requested ~> 1.4) in groups "development, test"
* hashie (newest 3.4.6, installed 1.2.0, requested = 1.2.0) in groups "default"
* headless (newest 2.3.1, installed 2.2.3)
```

### Markdown 形式に変換

```
bundle outdated | bof --format markdown
```

```
| gem | newest | installed | requested | groups |
| --- | --- | --- | --- | --- |
| faker | 1.6.6 | 1.6.5 | ~> 1.4 | development, test |
| hashie | 3.4.6 | 1.2.0 | = 1.2.0 | default |
| headless | 2.3.1 | 2.2.3 | | |
```

### JSON 形式に変換

```
bundle outdated | bof --format json
```

```
[{"gem":"faker","newest":"1.6.6","installed":"1.6.5","requested":"~> 1.4","groups":"development, test"},{"gem":"hashie","newest":"3.4.6","installed":"1.2.0","requested":"= 1.2.0","groups":"default"},{"gem":"headless","newest":"2.3.1","installed":"2.2.3","requested":"","groups":""}]
```

`--pretty` オプションを指定した場合:

```
bundle outdated | bof --format json --pretty
```

```
[
  {
    "gem": "faker",
    "newest": "1.6.6",
    "installed": "1.6.5",
    "requested": "~> 1.4",
    "groups": "development, test"
  },
  {
    "gem": "hashie",
    "newest": "3.4.6",
    "installed": "1.2.0",
    "requested": "= 1.2.0",
    "groups": "default"
  },
  {
    "gem": "headless",
    "newest": "2.3.1",
    "installed": "2.2.3",
    "requested": "",
    "groups": ""
  }
]
```

### YAML 形式に変換

```
bundle outdated | bof --format yaml
```

```
---
- gem: faker
  newest: 1.6.6
  installed: 1.6.5
  requested: "~> 1.4"
  groups: development, test
- gem: hashie
  newest: 3.4.6
  installed: 1.2.0
  requested: "= 1.2.0"
  groups: default
- gem: headless
  newest: 2.3.1
  installed: 2.2.3
  requested: ''
  groups: ''
```

### CSV 形式に変換

```
bundle outdated | bof --format csv
```

```
"gem","newest","installed","requested","groups"
"faker","1.6.6","1.6.5","~> 1.4","development, test"
"hashie","3.4.6","1.2.0","= 1.2.0","default"
"headless","2.3.1","2.2.3","",""
```

### TSV 形式に変換

```
bundle outdated | bof --format tsv
```

```
"gem"	"newest"	"installed"	"requested"	"groups"
"faker"	"1.6.6"	"1.6.5"	"~> 1.4"	"development, test"
"hashie"	"3.4.6"	"1.2.0"	"= 1.2.0"	"default"
"headless"	"2.3.1"	"2.2.3"	""	""
```

### XML 形式に変換

```
bundle outdated | bof --format xml
```

```
<?xml version='1.0' encoding='UTF-8'?><gems><outdated><gem>faker</gem><newest>1.6.6</newest><installed>1.6.5</installed><requested>~> 1.4</requested><groups>development, test</groups></outdated><outdated><gem>hashie</gem><newest>3.4.6</newest><installed>1.2.0</installed><requested>= 1.2.0</requested><groups>default</groups></outdated><outdated><gem>headless</gem><newest>2.3.1</newest><installed>2.2.3</installed><requested></requested><groups></groups></outdated></gems>
```

`--pretty` オプションを指定した場合:

```
bundle outdated | bof --format xml --pretty
```

```
<?xml version='1.0' encoding='UTF-8'?>
<gems>
  <outdated>
    <gem>faker</gem>
    <newest>1.6.6</newest>
    <installed>1.6.5</installed>
    <requested>~> 1.4</requested>
    <groups>development, test</groups>
  </outdated>
  <outdated>
    <gem>hashie</gem>
    <newest>3.4.6</newest>
    <installed>1.2.0</installed>
    <requested>= 1.2.0</requested>
    <groups>default</groups>
  </outdated>
  <outdated>
    <gem>headless</gem>
    <newest>2.3.1</newest>
    <installed>2.2.3</installed>
    <requested></requested>
    <groups></groups>
  </outdated>
</gems>
```

### HTML 形式に変換

```
bundle outdated | bof --format html
```

```
<table><tr><th>gem</th><th>newest</th><th>installed</th><th>requested</th><th>groups</th></tr><tr><td>faker</td><td>1.6.6</td><td>1.6.5</td><td>~> 1.4</td><td>development, test</td></tr><tr><td>hashie</td><td>3.4.6</td><td>1.2.0</td><td>= 1.2.0</td><td>default</td></tr><tr><td>headless</td><td>2.3.1</td><td>2.2.3</td><td></td><td></td></tr></table>
```

`--pretty` オプションを指定した場合:

```
bundle outdated | bof --format html --pretty
```

```
<table>
  <tr>
    <th>gem</th>
    <th>newest</th>
    <th>installed</th>
    <th>requested</th>
    <th>groups</th>
  </tr>
  <tr>
    <td>faker</td>
    <td>1.6.6</td>
    <td>1.6.5</td>
    <td>~> 1.4</td>
    <td>development, test</td>
  </tr>
  <tr>
    <td>hashie</td>
    <td>3.4.6</td>
    <td>1.2.0</td>
    <td>= 1.2.0</td>
    <td>default</td>
  </tr>
  <tr>
    <td>headless</td>
    <td>2.3.1</td>
    <td>2.2.3</td>
    <td></td>
    <td></td>
  </tr>
</table>
```

## 仕組み

仕組みとしてはシンプルで、`bundle outdated` の出力を正規表現でパースした後、csv や rexml 等のライブラリを利用して出力用に組み直しているだけです。

ただし Ruby 2.0 で YAML 形式で出力する場合、標準の yaml ライブラリが使用している psych ライブラリのバージョンが古く、他の Ruby のバージョンと異なる出力となってしまう問題があったため、あえて [psych の依存関係を gemspec に指定しています](https://github.com/emsk/bundle_outdated_formatter/commit/615f291f2e1e8b82031c6d895bc1107a7025e9f1)。

## まとめ

bundle_outdated_formatter はひとまず自分の課題を解決するために作ったもので、もしかすると皆さんの課題を解決するツールにはなっていないかもしれません。
ですので、もしよろしければ使ってみていただき、もう少しココを便利にして欲しい等のご要望がありましたらお気軽にご連絡ください！
ぜひ対応を検討させていただきたいと思います:smile: