go-promptでGitのブランチをいい感じにチェックアウトするコマンドを作ってみた

先日Qiitaで以下の記事を見てこれでなにか作れないかなぁと思っていたのですが…

qiita.com

ふと思い立ってGitのブランチをチェックアウトするコマンドを作ってみました。

github.com

ブランチの一覧からチェックアウトしたいブランチをインクリメンタルサーチしてチェックアウトできるというもので、ローカルブランチの場合は普通にチェックアウト、リモートブランチの場合はローカルブランチを作成してチェックアウトしてくれます。

f:id:takezoe:20170828190434p:plain

一応目的のモノは作れたのですが、元々は以下のようなシェルスクリプトを使って、pecoを使ってチェックアウトするブランチを選択できるようにしたものを使っていたのですが、これと比べるとなんだか使いにくい気がする。

function peco-git-checkout() {
  local BRANCH=`git branch -a | peco`
  if [[ -z "$BRANCH" ]]; then
    return
  fi
  if [[ "$BRANCH" =~ remotes/([^/]*)/(.*) ]]; then
    git checkout -b ${BASH_REMATCH[2]} ${BASH_REMATCH[1]}/${BASH_REMATCH[2]}
  else
    git checkout $BRANCH
  fi
}

慣れの問題かもしれませんが…。うーん…。

Scala用のタイプセーフなSQLビルダtranquil 0.0.3をリリースしました

github.com

以前から少しずつ進めていたtranquilですが、APIもそれなりに落ち着いてきたので久しぶりにバージョンアップしてみました。ちなみに以前のものはこんな感じでした。

takezoe.hatenablog.com

テーブル定義は以下のような感じで随分シンプルに定義できるようになりました。

import com.github.takezoe.tranquil._

case class User(userId: String, userName: String, companyId: Option[Int])

class Users extends TableDef[User]("USERS") {
  val userId    = new Column[String](this, "USER_ID"),
  val userName  = new Column[String](this, "USER_NAME"),
  val companyId = new OptionalColumn[Int](this, "COMPANY_ID")

  override def toModel(rs: ResultSet): User =
    User(userId.get(rs), userName.get(rs), companyId.get(rs))
}

object Users {
  def apply() = new SingleTableAction(new Users())
}

case class Company(companyId: Int, companyName: String)

class Companies extends TableDef[Company]("COMPANIES") {
  val companyId   = new Column[Int](this, "COMPANY_ID"),
  val companyName = new Column[String](this, "COMPANY_NAME")

  override def toModel(rs: ResultSet): Company =
    Company(companyId.get(rs), companyName.get(rs))
}

object Companies {
  def apply() = new SingleTableAction(new Companies())
}

マクロで自動生成する手法も試してみたのですが、IntelliJで補完が効かなかったりなど厳しい感じだったので現状ではソースコードの自動生成で対応する感じになるかなと思います。ただ、自動生成するとはいってもシンプルにできるのであればそのほうがよいので、そういう意味ではResultSetからケースクラスを生成するメソッドや、コンパニオンオブジェクトでファクトリメソッドを定義しないといけないのがまだちょっと微妙ですね。

クエリはこんな感じです。

import com.github.takezoe.tranquil._
import com.github.takezoe.tranquil.Dialect.generic

val conn: java.sql.Connection = ...

// SELECT
val users: Seq[(User, Option[Company])] =
  Users()
    .leftJoin(Companies()){ case u ~ c => u.companyId eq c.companyId }
    .filter { case u ~ c => (u.userId eq "takezoe") || (u.userId eq "takezoen") }
    .sortBy { case u ~ c => u.userId asc }
    .list(conn)

テーブルのエイリアスを手動で指定する必要がなくなりました。が、既存のモデルを壊さずにエイリアスを自動生成できるようにしたので実装が割と微妙な感じになってしまいました。やはりDSLではASTを組み立てるだけにして、SQLに変換するフェーズとは分けるようにしたほうが良さそうです(なるべくシンプルな作りにしたかったのですが、こうやって複雑になっていくのか…)。

この他にも以前ブログに書いた時点ではまだサポートしていなかった以下のような機能が実装済みです。

  • 特定のカラムだけSELECTする
  • GROUP BY、HAVING
  • ページング(LIMIT、OFFSET的なやつ)
  • DBごとの違いを吸収するためのDialect

ただ、最初にあまり深く考えずに作り始めてしまったこともあり、無理やり辻褄をあわせるためにvarを使っていたり、ランタイムリフレクションを使っていたりと作り的に結構微妙な感じになってしまいました。また、見た目に違和感のないDSLを作ろうとするとどうしてもimplicit conversionを多用することになってしまい、コードがわかりにくくなってしまいます。

一度作ってみて感覚が掴めたので、もう一度ちゃんと設計して作り直したほうがいいかも…。

sbtとpecoでテンプレートを選択してプロジェクトを作成する

sbtではsbt new playframework/play-scala-seed.g8などのようにしてgiter8テンプレートを指定してプロジェクトを作成することができます。

しかし、プロジェクトを作るたびにテンプレートのリポジトリ名を調べるのも面倒です。そこで、giter8のWikiにまとめられているgiter8テンプレートのリストからpecoでインクリメンタルサーチしてプロジェクトを作成できるようにしてみました。

function peco-sbt-new() {
  local TEMPLATE=`curl https://github.com/foundweekends/giter8/wiki/giter8-templates -s | grep "\.g8<" | sed -e "s/</ /g" -e "s/>/ /g" | awk '{print $3}' | peco | head -n 1`
  if [[ -z "$TEMPLATE" ]]; then
    return
  fi
  sbt new $TEMPLATE
}

上記のような感じのスクリプト.bash_profileに入れておき、peco-sbt-newを実行するとテンプレートを選択してsbt newを実行できます。

f:id:takezoe:20170819163550p:plain

これでgiter8のWikiさえメンテしていれば新しいテンプレートも使えるようになります。

ちょっとしたものですが、テストやユーザサポートなどの用途などの目的でgiter8テンプレートからPlayやScalatraのプロジェクトを作成することがちょくちょくあるので割と便利な感じです。

Tower2をコマンドラインから起動する

Tower2Mac*1GUIなGitフロントエンドです。このブログでも以前紹介しましたが、有償*2なだけあり、軽快な動作が売りで使いやすいGitフロントエンドの1つだと思います。

takezoe.hatenablog.com

takezoe.hatenablog.com

で、最近気づいたのですが、Tower2ではCLIツールが用意されており、コマンドラインからディレクトリを指定して起動したりということもできるようです。CLIツールは設定のIntegrationのタブでインストールできます。

f:id:takezoe:20170817025057p:plain

するとgittowerというコマンドが利用できるようになりますので、Gitリポジトリのルートディレクトリで

$ gittower .

などとするとそのリポジトリをTower2で開くことができます。ちょっとしたことなのですが、ターミナル族には嬉しい機能なのではないでしょうか。

*1:現在はWindows版もリリースされています

*2:$79の買い切りモデル

GitBucket 4.15.0をリリースしました

Scalaで実装されたオープンソースのGitサーバ、GitBucket 4.15.0をリリースしました。

https://github.com/takezoe/gitbucket/releases/tag/4.15.0

標準プラグインをバンドル

以下のプラグインがGitBucketのディストリビューションにバンドルされるようになりました。

これらのプラグインは管理画面で有効/無効を切り替えることができます。

f:id:takezoe:20170731123257p:plain

Notificationsプラグイン

イシューやプルリクエストのメール通知が gitbucket-notifications-pluginとしてプラグインに分離されました。機能も強化されており、リポジトリやイシュー/プルリクエスト毎にメールを受け取るかどうかをユーザが選択できるようになっています。

f:id:takezoe:20170731123317p:plain

f:id:takezoe:20170731123327p:plain

プラグインのホイットデプロイに対応

プラグインのインストール/アンインストールを、GitBucketを再起動しなくても行えるようになりました。プラグインをインストールする場合はGITBUCKET_HOME/pluginsディレクトリにプラグインのjarファイルを配置します。アンインストールする場合はjarファイルを削除すればOKです。

さらにGitBucketの起動オプションに--plugin_dirオプションが追加されました。これはプラグイン開発者向けの機能で、GitBucketはこのオプションで指定したディレクトリからもプラグインを読み込むようになります。たとえば以下のようにGitBucketを起動します。

$ java -jar gitbucket.war --plugin_dir=<YOUR_PLUGIN_PROJECT_DIR>/target/scala-2.12

プラグインのプロジェクト側で以下のようにしてsbtを実行します。

$ sbt ~package

するとプラグインのプロジェクト側でソースを変更しただけで自動的にjarファイルが生成され、GitBucketにも変更が反映されます。

Slickを3.2.1にバージョンアップ

Slick(GitBucketが使用しているデータベースアクセスライブラリ)を3.2.0から3.2.1にバージョンアップしました。

このバージョンのSlickは前バージョンであるSlick 3.2.0とバイナリ互換性がないため、現在リリースされている多くのGitBucketプラグインが動作しなくなってしまうものと思われます。GitBucketプラグイン開発者の皆様はGitBucket 4.15.0向けにプラグインの再ビルドをお願いできればと思います。

SSHアクセスでed25519キーをサポート

GitリポジトリへのSSHアクセスでed25519キーもサポートされるようになりました。

編集フォームでのMarkdownプレビュー

これまでイシューやコメントの作成フォームではMarkdownのプレビューが可能でしたが、編集画面ではプレビューを行うことができませんでした。このリリースでは編集フォームでもMarkdownのプレビューが可能になりました。

f:id:takezoe:20170731123345p:plain

今回のバージョンではこの他にも様々な改善やバグフィックスを行っています。詳細についてはIssueの一覧をご覧ください。

Scala福岡 2017でApache PredictionIOの紹介をしました

7月29日(土)に福岡のヌーラボさんオフィスで開催されたScala福岡2017に参加しました。

scala.connpass.com

セッションではApache PredictionIOの紹介と、PredictionIOやelasticsearch-hadoopなどのOSSScala警察として活動してきた中で見かけたScala的にいけてないコードあるある(とその対処法)についてお話させていただきました。

www.slideshare.net

www.slideshare.net

他の皆さんのセッション(2トラックだったのでもう片方のセッションを聴講できなかったのが残念でした!)も興味深いものが多かったですが、特にはてなさんのPlayのバージョンアップの話が大変心に残りました。Slick様…。また、かなり久々にお会いする方々やお世話になっている方々にもご挨拶できたりなど大変有意義な時間を過ごさせていただきました。(Project Mobster懐かしい!!)

次は9月のScala関西サミットですねー。準備しなくてはー。

MarkdownベースのドキュメンテーションツールParadoxを使ってみた

最近AkkaやAlpakkaなどのドキュメントで使われているLightbend製のParadoxというドキュメンテーションツールを試してみました。 *1

github.com

使い方はドキュメントを見ればわかると思いますが、要点だけまとめておきます。

はじめの一歩

Paradoxはsbtプラグインとして実装されているのでまずはproject/plugins.sbtに以下を追記します。

addSbtPlugin("com.lightbend.paradox" % "sbt-paradox" % "0.2.12")

build.sbtに以下の設定を追加します。

enablePlugins(ParadoxPlugin)

paradoxTheme := Some(builtinParadoxTheme("generic"))

ドキュメントはsrc/main/paradoxディレクトリ配下に作成します。以下のような内容でindex.mdというファイルを作っておきます。

Paradoxのテスト
========
これはParadoxのテストページです。

ここまで設定したらsbt paradoxを実行するとtarget/paradox/site/mainディレクトリ配下にHTMLが生成されます。

f:id:takezoe:20170711160752p:plain

ディレクティブを使ってみる

Paradoxでは基本的にMarkdownでドキュメントを記述しますが、@(インライン)、@@(ブロック)、@@@(コンテナ)で特殊なディレクティブを使用することができます。ここでは特に重要そうなディレクティブを2つだけ紹介しておきます。

サイドメニューを表示する

index.mdの末尾に以下のような記述を入れておくとサイドメニューを表示することができます。

@@@ index

* [Getting started](getting-started.md)
* [News](news.md)
* [Documentation](documentation.md)
* [Community](community.md)

@@@

表示はこんな感じになります。

f:id:takezoe:20170711160839p:plain

ソースコードを引用する

Paradoxの非常に便利な機能の1つがこのソースコードの引用機能です。以下のような記述でソースコードの一部を埋め込むことができます。

@@snip [Hello World](../../main/scala/HelloWorld.scala) { #sample }

ソースコードには以下のようにコメントでParadoxから参照するためのタグを埋め込んでおきます。

// #sample
object HelloWorld extends App {
  println("Hello World!")
}
// #sample

ソースコードの引用機能を使うことでソースコードとドキュメント内のサンプルコードの二重管理を防ぐことができます。ドキュメントで使用できるということがテストケースを書くインセンティブになりますし、CIを回しておけばドキュメントに掲載されているコードはビルドが通るコードであることを保証できます。

その他の機能

Paradoxにはこの他にもドキュメント内のクロスリファレンスやScaladocへのリンクを簡単に張ることができたり、Java / Scalaなどソースコードをタブで切り替えられるような形で表示することもできます。シンプルながらフレームワークやライブラリなどのドキュメント用にツボを抑えたツールと言えるでしょう。

利用シーンは主にScalaに限定されますが、READMEだけでは使い方を書ききれなくなってきたらParadoxの利用を検討してみてもよいのではないかと思います。

*1:UnfilteredのWebサイトもParadoxを使っているそうです。sbt-siteとsbt-ghpagesを使ってGitHub Pagesにデプロイしているそう。
http://xuwei-k.hatenablog.com/entry/20161211/1481437560