量産型エンジニアの憂鬱

きっと僕は何物にもなれない。

Dangerプラグインを作ろう

Danger

最近流行りつつある? Danger です。 Pull Request に対して色々チェックできるようになります。

先月末にポツポツと紹介記事が出ました。

Dangerとても便利です。 他のプロジェクトでも使えそうなルールなどはプラグインにして使い回ししたいですね。 なのでプラグインを作ってみます。

プラグイン作成も公式ページで詳しく乗ってます。 http://danger.systems/guides/creating_your_first_plugin.html

Dangerのインストー

$ gem install danger

プラグインの初期化

プラグイン名にハイフン(-)は推奨されていません。 単語はアンダースコア(_)で区切りましょう。

$ danger plugins create <plugin_name>

上記コマンドを実行すると、 git config からユーザ名とメールアドレスを取得してよしなに初期化してくれます。

danger-plugin_name/
├── Gemfile
├── Guardfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── danger-plugin_name.gemspec
├── lib
│   ├── danger_plugin.rb
│   ├── danger_plugin_name.rb
│   └── plugin_name
│       ├── gem_version.rb
│       └── plugin.rb
└── spec
    ├── plugin_name_spec.rb
    └── spec_helper.rb

3 directories, 12 files

作成されたディレクトリへ移動します。 danger-<plugin_name> というディレクトリが作成されています。

$ cd danger-plugin_name

テストなどに必要なGemをインストールしておきます。

$ bundle install --path vendor/bundle

また、テストを実行する際にリポジトリの upstream の情報が必要になります。リポジトリを作成して upstream を登録しておきます。

$ git remote add origin <上流のリポジトリURL>
$ git push -u origin master

メソッドを作る

簡単なプラグインを作る場合は lib/<plugin_name>/plugin.rb を変更するだけでできます。

say というメソッドを追加してみます。

lib/<plugin_name>/plugin.rb

diff --git a/lib/plugin_name/plugin.rb b/lib/plugin_name/plugin.rb
index eeda801..cea9600 100644
--- a/lib/plugin_name/plugin.rb
+++ b/lib/plugin_name/plugin.rb
@@ -29,5 +29,12 @@ module Danger
     def warn_on_mondays
       warn 'Trying to merge code on a Monday' if Date.today.wday == 1
     end
+
+    # A method that messages 'Hello Danger.'
+    # @return [Array<String>]
+    #
+    def say
+      message 'Hello Danger.'
+    end
   end
 end

テストを書く

プラグインを作ったらテストをかきましょう。

spec/<plugin_name>_spec.rb

diff --git a/spec/plugin_name_spec.rb b/spec/plugin_name_spec.rb
index 484958f..a3ec8ad 100644
--- a/spec/plugin_name_spec.rb
+++ b/spec/plugin_name_spec.rb
@@ -36,6 +36,11 @@ module Danger
         expect(@dangerfile.status_report[:warnings]).to eq([])
       end

+      it "Messages" do
+        @my_plugin.say
+
+        expect(@dangerfile.status_report[:messages]).to eq(["Hello Danger."])
+      end
     end
   end
 end

テストを書いたら実行します。 以下のコマンドで rspec と RuboCop が実行されます。 RuboCopでは色々指摘されると思うので必要あれば修正してください。

$ bundle exec rake spec
....

Finished in 0.06292 seconds (files took 0.4549 seconds to load)
4 examples, 0 failures

Running RuboCop...
Inspecting 6 files
...CCC

Offenses:

lib/plugin_name/plugin.rb:20:1: C: Extra empty line detected at class body beginning.
lib/plugin_name/plugin.rb:30:12: C: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
      warn 'Trying to merge code on a Monday' if Date.today.wday == 1
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
lib/plugin_name/plugin.rb:37:15: C: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
      message 'Hello Danger.'
              ^^^^^^^^^^^^^^^
spec/plugin_name_spec.rb:42:1: C: Tab detected.
        expect(@dangerfile.status_report[:messages]).to eq(["Hello Danger."])
^^
spec/plugin_name_spec.rb:42:3: C: Inconsistent indentation detected.
        expect(@dangerfile.status_report[:messages]).to eq(["Hello Danger."])
  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
spec/spec_helper.rb:12:23: C: Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
if `git remote -v` == ''
                      ^^

6 files inspected, 6 offenses detected
RuboCop failed!

動作確認したい

これがとてもめんどくさいです。 Pull Requestを用意する必要があります。 確認の場合、既にCloseされている Pull Request でも問題ありません。 個人で開発しているライブラリのリポジトリで確認してみました。

$ git clone https://github.com/duck8823/Slack-RTM-Bot.git
$ cd Slack-RTM-Bot

Dangerとプラグインを使えるようにGemfileを用意してやります。 プラグイン:path を指定することで作成途中のもので試すことができます。

Gemfile

source "https://rubygems.org"

gem 'danger'
gem 'danger-plugin_name', :path => 'path/to/danger-plugin_name'

bundleインストー

$ bundle install --path vendor/bundle

これで準備ができました。

続いて Dangerfile を用意しましょう。 プロジェクト直下に置く必要があります。

Dangerfile

plugin_name.say

Dangerfile ではプラグインインスタンスが利用できます。 インスタンス名は以下の命名規則に従います。 http://danger.systems/guides/creating_your_first_plugin.html#tech-specs

確認は、

$ bundle exec danger local

で可能です。

また、

$ bundle exec danger pr https://github.com/duck8823/Slack-RTM-Bot/pull/20

のようにすることで特定の Pull Request に対して確認することも可能です。

local または pr で実行した場合、以下のような内容が表示されます。

Info:

+------------------+----------------------------------------------------------------------------------------------+
|                                                  Danger v5.3.3                                                  |
|                                                 DSL Attributes                                                  |
+------------------+----------------------------------------------------------------------------------------------+
|    status_report |                                                                                              |
| violation_report |                                                                                              |
|              --- | ---                                                                                          |
|     scm_provider | github                                                                                       |
|             diff | diff --git a/lib/Slack/RTM/Bot/Client.pm b/lib/Slack/RTM/Bot/Client.pm                       |
|                  | index 9b86bc6..4c8ff0d 100644                                                                |
|                  | --- a/lib/Slack/RTM/Bot/Client.pm                                                            |
|                  | +++ b/lib/Slack/RTM/Bot/Client.pm                                                            |
|                  | @@ -248,7 +248,7 @@ sub _listen {                                                            |
|                   |       $channel = $self->find_channel_or_group_name($buffer_obj->{channel});
                        |   $channel ||= $self->_refetch_channel_name($buffer_obj->{channel});
                          | $channel ||= $self->_refetch_group_name($buffer_obj->{channel});
       |           | -      die "There are no channels or groups of such id: $buffer_obj->{user}" unless $user;
 |                 | +      die "There are no channels or groups of such id: $buffer_obj->{channel}" unless $channel;
                                                                                          |
                                             |RTM::Bot::Response->new(
                                                                   |
|                  | @@ -273,4 +273,4 @@ ACTION: for my $action(@{$self->{actions}}){                             |
                                                                                          |
                                                                                          |
                                                                                            |
|                  | -1;                                                                                          |
|                  | \ No newline at end of file                                                                  |
|                  | +1;                                                                                          |
|      added_files | #<Danger::FileList:0x007fe123a9d678>                                                         |
|    deleted_files | #<Danger::FileList:0x007fe123a9cea8>                                                         |
|   modified_files | lib/Slack/RTM/Bot/Client.pm                                                                  |
|    renamed_files | []                                                                                           |
|    lines_of_code | 4                                                                                            |
|        deletions | 2                                                                                            |
|       insertions | 2                                                                                            |
|          commits | b4e3b9fd228752e3c7fffd7c9cb5258959f281a2                                                     |
|              api | Octokit::Client                                                                              |
|          pr_json | [Skipped JSON]                                                                               |
|          mr_json | [Skipped JSON]                                                                               |
|          pr_diff | [Skipped Diff]                                                                               |
|           review | #<Danger::RequestSources::GitHubSource::Review:0x007fe12398a498>                             |
|         pr_title | bugfix channel or group not found (was checking for user instead)                            |
|          pr_body |                                                                                              |
|        pr_author | dada                                                                                         |
|        pr_labels | []                                                                                           |
|  branch_for_base | master                                                                                       |
|  branch_for_head | master                                                                                       |
|      base_commit | 947aeeaf5443494d3e7895292662b617b369bf05                                                     |
|      head_commit | b4e3b9fd228752e3c7fffd7c9cb5258959f281a2                                                     |
|         mr_title | bugfix channel or group not found (was checking for user instead)                            |
|          mr_body |                                                                                              |
|        mr_author | dada                                                                                         |
|        mr_labels | []                                                                                           |
|              say | Violation Hello Danger. { file: , line:  }                                                   |
|     my_attribute |                                                                                              |
|  warn_on_mondays |                                                                                              |
|              --- | ---                                                                                          |
|              SCM | Danger::GitRepo                                                                              |
|           Source | Danger::LocalGitRepo                                                                         |
|         Requests | Danger::RequestSources::GitHub                                                               |
|      Base Commit | commit 947aeeaf5443494d3e7895292662b617b369bf05                                              |
|                  | Author: shunsuke maeda <duck8823@gmail.com>                                                  |
|                  | Date:   Sat Apr 8 12:30:31 2017 +0900                                                        |
|                  |                                                                                              |
|                  |     Checking in changes prior to tagging of version 1.04.                                    |
|                  |                                                                                              |
|                  |     Changelog diff is:                                                                       |
|                  |                                                                                              |
|                  |     diff --git a/Changes b/Changes                                                           |
|                  |     index 311b411..e8e2578 100644                                                            |
|                  |     --- a/Changes                                                                            |
|                  |     +++ b/Changes                                                                            |
|                  |     @@ -2,6 +2,10 @@ Revision history for Perl extension Slack-RTM-Bot                       |
|                  |                                                                                              |
|                  |      {{$NEXT}}                                                                               |
|                  |                                                                                              |
|                  |     +1.04 2017-04-08T03:30:26Z                                                               |
|                  |     +                                                                                        |
|                  |     +   - Fix dying when response to new channel/group                                       |
|                  |     +                                                                                        |
|                  |      1.03 2017-04-05T23:11:07Z                                                               |
|                  |                                                                                              |
|                  |         - Fix a bug that zombies spawn when stop_RTM                                         |
|      Head Commit | commit b4e3b9fd228752e3c7fffd7c9cb5258959f281a2                                              |
|                  | Author: dada <dada@perl.it>                                                                  |
|                  | Date:   Fri May 19 18:07:20 2017 +0200                                                       |
|                  |                                                                                              |
|                  |     bugfix channel or group not found (was checking for user instead)                        |
+------------------+----------------------------------------------------------------------------------------------+

Results:

Messages:
- [ ] Hello Danger.
- [ ] Hello Danger.

Info: ではDangerが取得した Git や GitHub などの情報を閲覧することができます。また、プラグイン内の 引数がないメソッドを自動的に実行し 、結果を表示してくれます。

Results: では messagefail などの DSL を実行した結果を表示してくれます。

上記の例で Hello Danger. が重複して表示されているのは、

  • Dangerfileでの呼び出し
  • Info: で表示するための呼び出し

で二回実行されたからです。

引数なしのメソッドでPull Requestをクローズするなどの処理を実装した場合、local または pr モードで意図しないメソッド呼び出しが起こる場合があるので気をつけましょう。 (記事を書いた時点でDangerのバージョンは 5.3.3 )

また、実行時に

Octokit::TooManyRequests: GET https://api.github.com/repos/duck8823/Slack-RTM-Bot/issues/20: 403 - API rate limit exceeded for

というようなエラーが出た場合は、 環境変数DANGER_GITHUB_API_TOKEN を設定してやることで回避できる場合があります。

利用するトークンは https://github.com/settings/tokens で作成することができます。

READMEの自動生成

Dangerは README を自動的に生成するコマンドも用意してくれています。

$ bundle exec danger plugins readme
### plugin_name

This is your plugin class. Any attributes or methods you expose here will
be available from within your Dangerfile.

To be published on the Danger plugins site, you will need to have
the public interface documented. Danger uses [YARD](http://yardoc.org/)
for generating documentation from your plugin source, and you can verify
by running `danger plugins lint` or `bundle exec rake spec`.

You should replace these comments with a public description of your library.

<blockquote>Ensure people are well warned about merging on Mondays
  <pre>
my_plugin.warn_on_mondays</pre>
</blockquote>



#### Attributes

`my_attribute` - An attribute that you can read/write from your Dangerfile




#### Methods

`warn_on_mondays` - A method that you can call from your Dangerfile

`say` - A method that messages 'Hello Danger.'

plugins readme コマンドは lib/<plugin_name>/plugin.rb のドキュメント(YARD形式)を元にREADMEを作成します。

正しくドキュメントが記述されているかどうかは、

$ bundle exec danger plugins lint

で調べることができます。 例えば、追加したメソッドにドキュメントが存在しない場合には以下のようなエラーが出ます。

[!] Failed


Errors
  - Description - say (method):
    - You should include a description for your method.
    - @see - https://github.com/dbgrandi/danger-prose/blob/v2.0.0/lib/danger_plugin.rb#L40#-L41
    - /private/tmp/danger-plugin_name/lib/plugin_name/plugin.rb:33


Warnings
  - Return Type - say (method):
    - If the function has no useful return value, use ` @return  [void]` - this will be ignored by documentation generators.
    - @see - https://github.com/dbgrandi/danger-prose/blob/v2.0.0/lib/danger_plugin.rb#L46
    - /private/tmp/danger-plugin_name/lib/plugin_name/plugin.rb:33

Failing due to errors

記述に従ってドキュメントを整備しましょう。

公開しましょう

良いプラグインができたら公開しましょう。

公開前に、 danger-<plugin_name>.gemspec を確認しましょう。

danger-<plugin_name>.gemspec

# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'plugin_name/gem_version.rb'

Gem::Specification.new do |spec|
  spec.name          = 'danger-plugin_name'
  spec.version       = PluginName::VERSION
  spec.authors       = ['shunsuke maeda']
  spec.email         = ['duck8823@gmail.com']
  spec.description   = %q{A short description of danger-plugin_name.}
  spec.summary       = %q{A longer description of danger-plugin_name.}
  spec.homepage      = 'https://github.com/duck8823/danger-plugin_name'
  spec.license       = 'MIT'
.
.
.

githubアカウント名と git config の user.name が異なる場合などは適宜変更してください。 spec.homepage のアカウント名部分に user.name が使われるのでスペースがある場合などは gem に登録できません。

修正が完了したら

$ rake release

RubyGems に登録することができます。

さらに Danger本家サイト にプラグインとして紹介してもらいましょう。 以下にプラグインを追加して Merge Request を出せば、光速でマージしてくれます。

https://gitlab.com/danger-systems/danger.systems/blob/master/plugins.json

その後、サイトはそのうち更新されます。

plugins コマンドでも確認できます。

$ danger plugins
Downloading Plugins list...

Available Danger Plugins:

-> danger-prose
     A danger plugin for working with bodies of markdown prose.
     - Gem:     danger-prose
     - URL:     https://github.com/dbgrandi/danger-prose

-> danger-android_lint
     Lint files of a gradle based Android project. This is done using the Android's Lint tool. Results are passed out as tables in markdown.
     - Gem:     danger-android_lint
     - URL:     https://github.com/loadsmart/danger-android_lint
・
・
・

公開したプラグイン

僕が作ったプラグインも掲載されています。 http://danger.systems/plugins/slack.html

message や fail などの内容をSlackに通知するプラグインです。 DangerはGitHub上に HTML 形式でコメントしてくれるので、 GitHub の Slack 連携では HTML は表現されません。 対策としてHTMLコメントでそれぞれの数を表示してくれているのですが微妙な感じ。

f:id:duck8823:20170711164521p:plain

プラグインでは内容も表示し、色によって error などわかるようにしました。

f:id:duck8823:20170711164536p:plain

地味にチャンネルやメンバー一覧を取得するメソッドも用意しています。 サクッと指摘内容をSlackに通知したい場合は使ってみてください。 https://github.com/duck8823/danger-slack