Gitbucketのプラグインを作成する


[GitBucket2.2](https://github.com/takezoe/gitbucket)がリリースされたようです。

最近地道に社内に広めている身としてはありがたい事に、プラグイン機能が実装された事を[@takezoeさんのblog](http://d.hatena.ne.jp/takezoe/20140804)で知りました。

> とはいえ、プラグインシステムはまだまだ試行錯誤を繰り返している状態なので、APIを含め今後大幅な変更が入る可能性が高いです。現状はあくまでお試し版といったところです。

と書かれていますが、試しにちょっと遊んでみました。


## 注意事項
**勝手にソースを弄ってごにょごにょしているので自己責任でお願いします。**

GitBucketの開発向けの起動の仕方や、Scalaの開発環境構築に関してはここでは触れません。
[for Developers](https://github.com/takezoe/gitbucket/wiki#for-developers)辺りを参考に構築してください。

## 前準備
デフォルトでは、https://github.com/takezoe/gitbucket_plugins.git がセントラルリポジトリとして設定されているようです。
ただ遊ぶだけなのに、`gitbucket_plugins`にPullRequestを取り込んでもらうのも面倒なので、
ソースを書き換えて自分リポジトリをプラグインとして認識させます。

```scala
// src/main/scala/plugin/PluginSystem.scala#L51

// デフォルトはこれだけが定義されている
repositoriesList += PluginRepository("central", "https://github.com/takezoe/gitbucket_plugins.git")

// ローカルリポジトリを追加
// git://は駄目な模様
repositoriesList += PluginRepository("local", "https://github.com/hikaruworld/test")
```

あわせて、コメントアウトされていた`admin/plugins/console`のURLもアクセス可能にしておきます。
GitBucketのプラグインはjavascriptで書けるのですが、実体が[Rhino](https://developer.mozilla.org/ja/docs/Rhino)なので、通常のjsのつもりで書いてもちょこちょこ引っかかるのでアプリ上から実行可能にします。

```scala
// src/main/scala/app/SystemSettingsController.scala#L114-L122

//コメントアウトを戻す
get("/admin/plugins/console")(adminOnly {
   admin.plugins.html.console()
})
post("/admin/plugins/console")(adminOnly {
   val script = request.getParameter("script")
   val result = plugin.JavaScriptPlugin.evaluateJavaScript(script)
   Ok(result)
})

```

これで、http://localhost:8080/admin/plugins/console にアクセスすることでjavascriptの検証が可能になります。

## プラグインの作成

基本的には[gitbucket_plugins.git](https://github.com/takezoe/gitbucket_plugins.git)のサンプルと、`src/main/scala/plugin/JavaScriptPlugin.scala`辺りを見ながら拡張していきます。

今回は試しに、githubやbitbucketにあるこれを実装してみます。
理由は『Githubにある簡単にSourceTreeを起動するアレがGitBucketにはない』と言われたので....

![スクリーンショット 2014-08-07 1.08.40.png](https://qiita-image-store.s3.amazonaws.com/0/11214/2078d298-2fc0-f6d8-11d8-bf5bb30c206c.png "スクリーンショット 2014-08-07 1.08.40.png")


### 提供される拡張ポイント

現時点では、以下の4つが提供されているようです。
今回はサンプルに従って、`addRepositoryAction`の拡張を行ないます。

| 拡張メソッド       | 概要  | 必要なプロパティ | functionの引数 |
|------------------|------|--------------------|-------------|
|addRepositoryMenu |リポジトリのメニューを拡張(試してない)   | label,name,url,icon,condition |context |
|addGlobalMenu     |全体のメニューを拡張(試してない)      | label,url,icon,condition |context|
|addGlobalAction |全体のアクションを拡張 |path,function|request, response|
|addRepositoryAction|リポジトリのアクションを拡張|path,function|request, response, repository|

また、function内部では、`plugin.db()`を通してselect句がサポートされていました。

### 完成図

http://localhost:8080/root/test/desktop-clone にアクセスすると以下が表示されるようにします。
![kobito.1407370688.511491.png](https://qiita-image-store.s3.amazonaws.com/0/11214/e5484d7a-d5b7-e780-2cb6-048e2175d5dd.png "kobito.1407370688.511491.png")


### 必要なファイルの作成と設定

`plugin.js`と`plugin.properties`の2つが必要になるようです。

| ファイル名 | 概要 |
|-----------|-----|
| plugin.js | 実装されるプラグインのソース  |
| plugin.properties| 設定ファイル|



### 設定を記述
`plugin.properties`に設定を書きます。サンプルを参考にこんな感じで書きました。

```properties
id=desktop-clone
version=1.0.0
author=hikaruworld
url=https://github.com/hikaruworld/test
description=destop clone support(source tree).
```

### 実装を記述

`plugin.properties`に定義された情報にアクセスできるようです。
最初に`JavaScriptPlugin#define`でインスタンスを生成し、`JavaScriptPlugin#addRepositoryAction`を利用して、拡張ポイントを定義します。
最後に`PluginSystem#install`を利用して実際にプラグインを有効化します(と言った流れでしょうか)。

```javascript
var plugin = JavaScriptPlugin.define(id, version, author, url, description);

// REPOSITORY/desktop-cloneにアクセスされた場合に動く実装を定義
plugin.addRepositoryAction('/desktop-clone', function(request, response){

  // UserAgent情報からOSを切り分け
  var userAgent = request.getHeader("User-Agent") + "";
  var platform = "";
  if (userAgent.indexOf("Mac") >= 0) {
    platform = "mac";
  } else if (userAgent.indexOf("Linux") >= 0) {
    platform = "linux";
  } else if (userAgent.indexOf("Win") >= 0) {
    platform = "windows";
  } else {
    platform = "";
  }

  // requestはJavaのHttpServletRequestなので、getRequestURLで現在のURLを取得
  // 色々加工して、Clone用のURLを構築
  var endpoint = request.getRequestURL() + "";
  var ary = endpoint.split("/");
  ary.pop();
  ary.splice(-2, 0, "git");
  endpoint = ary.join("/") + ".git";
  // 画面に表示するためHTMLを返す。JSON形式でformat指定を返す事も可能な模様。
  return '<div style="margin-top: 10px;">' +
         '   <a href="github-' + platform + '://openRepo/' + endpoint + '" class="btn btn-small" style="width: 147px;"><i class="icon-download-alt"></i>Clone in Desktop</a>' +
         '</div>';
});

PluginSystem.install(plugin);
```

### 公開とインストール
1. 先ほど登録したローカルリポジトリにpushします。
1. gitbucketを再起動します。
1. 管理者権限でログインし、`Administration`から`Plugins`を選択し、`Available plugins`タグから今回作成したプラグインを選択してインストールする。

![kobito.1407378203.827239.png](https://qiita-image-store.s3.amazonaws.com/0/11214/f52d7f5d-2d04-bb4c-96da-4d40919bc949.png "kobito.1407378203.827239.png")




### 毎回インストールが面倒な時
インストールしたプラグインは、GITBUCKET_HOME/plugins以下に配備されます。
これを直接弄れば、いちいちプラグインのアップデートをしなくても挙動の確認が出来ます

### プラグインの更新を認識してくれない
画面表示時に最新を取ってくれる訳ではないようなので、`sbt`を起動して`container:restart`で認識されます。

## 出来たらいいな

* 設定画面でプラグイン用の設定を個別にユーザが行なえるようにしたい
* 設定情報やユーザー情報にアクセスできるようにしたい(db経由はちょっと面倒なので)

## 調べたい事

* `ScalaPlugin.scala`が気になる。Scalaでも書ける?
* `jQuery`なども活用して書きたい

## オチ

別にプラグインとして実装する必要はない機能だったりする訳です。
本体側に取り込んでもらうようにPullRequestしたいかも....