プロキシサーバを指定してgit cloneできるコマンドを作りました

タイトル通りですが、jgitの勉強がてら、プロキシサーバを指定してgitリポジトリをクローンできる「jgit-proxy-clone」を作りました。名前もそのまんまです。

リポジトリはこちら。 https://github.com/rabitarochan/jgit-proxy-clone

作ったきっかけ

以下のツイートを見たのがきっかけです。

・・・実は同じようなことで悩んでいて、ちょっと前にツイートしてましたw

会社のネットワーク環境にはプロキシサーバが設置されているため、外部のgitリポジトリを見に行く場合はそのプロキシを経由しなければいけません。
しかし、社内のgitリポジトリについてはプロキシを経由するとアクセスできなくなるため、クローンする度に.gitconfigファイルを修正していました。

今回作ったもの

jgitを使って、プロキシサーバを指定して(または、プロキシサーバを経由しないようにして)gitリポジトリをクローンするだけのコマンドを作りました。

コマンド実行時に指定したプロキシ設定については、自動的にリポジトリのconfigファイルに登録するようにしています。

そのため、クローンする時だけこのコマンドを使えばよくて、それ以降は通常のgitリポジトリとして使うことができます。

使い方

最初のリリース版を作成していますので、以下のページから「jgit-proxy-clone-0.1.jar」ダウンロードして、任意のディレクトリに保存します。

https://github.com/rabitarochan/jgit-proxy-clone/releases

jarの実行方法は以下のとおりです。

(プロキシサーバを指定する場合)
> java -jar jgit-proxy-clone-0.1.jar <リモートURL> [<ディレクトリ>] -h <プロキシホスト名 or IPアドレス> -p <プロキシポート番号>

(プロキシサーバを使わない場合)
> java -jar jgit-proxy-clone-0.1.jar <リモートURL> [<ディレクトリ>]

上記のとおり、プロキシサーバのホスト名とポート番号を指定せず、リモートURLのみを指定して実行した場合、 .gitconfigのプロキシ設定やシステムのプロキシ設定を使わず、直接接続してクローンを実行します。

その点だけ注意してもらえれば、最低限使えるものになっています。

今後

最低限使えるものとして、とりあえずリリースした状況です。ですので、ドキュメントだったり、実行時の表示だったりがほぼ出来ていない状態となっています。

とりあえずは使い方の記述と、動作がわかるような画面表示を実装使用と思います。

それと、現在は認証が必要なリポジトリにはアクセス出来ません。これについても早めに実装しようと思っています。

まとめ

こんな状態のままリリースしてしまいましたが、最低限は動くものとなっています。
プロキシ環境が複雑でgitリポジトリのクローンに困っている方、ぜひお使いいただきフィードバック頂ければと思います。

pullreqお待ちしてます!

play2でフィルター処理を実現する

前回の記事 をベースに考えて、なんとなく動くフィルター処理を作ってみました。

https://github.com/rabitarochan/play2-filter-sandbox

フィルターの書き方についてはだいたいイメージ通りに行きました。

object Application extends Controller with FilterController {
  
  def index = LoggingFilter >> WithAction { req =>
    Ok(views.html.index("Your new application is ready."))
  }
  
  case class User(name: String, password: String)
  
  case object UserKey extends AttributeKey[User]

  /**
   * 認証もどきのフィルター。
   * 
   * QueryString に user と password が設定されていて、
   * その値が同じであれば User を作成して次の処理へ進む。
   * それ以外の場合は認証エラー (Unauthorized) を返す。 
   */
  def AuthFilter = Filter { req: AttributedRequest[AnyContent] =>
    val userOpt = req.getQueryString("user")
    val passwordOpt = req.getQueryString("password")
    
    (userOpt, passwordOpt) match {
      case (Some(user), Some(password)) if user == password => {
        continueWith(UserKey -> User(user, password))
      }
      case _ => Unauthorized("user is not equal password.")
    }
  }
  
  /**
   * ログを出力するだけのフィルター
   */
  def LoggingFilter = Filter { req: AttributedRequest[AnyContent] =>
    Logger.debug(s"[${req.method}] ${req.path}")
    continue
  }
  
  
  def test = 
    LoggingFilter >>
    AuthFilter >>
    WithAction { implicit request =>
      val user = UserKey.get
      Ok(s"Action: ${user}")
    }
  
}

次のフィルター、アクションにパラメータを渡すあたりの実装は、t2v/stackable-controllerをかなり参考にさせていただきました。

なぜこれを作ったのか

参考にさせていただいた stackable-controller は、StackableControllerを継承したトレイトをControllerにmixinしていくことで、同じように共通処理を実行できます。

ただ、このフィルターのように、実際どのような処理が行われているのかが見えるほうが分かりやすいのではないかと思い、作ってみることにしました。

ふと思いついた、という理由が大きかったり。

課題

テスト等もまだまだ不十分ですし、これを使って何かを作ったわけではないです。それはこれからとして、今の時点でどうにかならないか考えていることが何個かあります。

1. Filterのリクエストの型を明示しない方法を作りたい

上記のコードの通り、全てのFilter処理のリクエストに型を明示しています。これを書かないとコンパイルが通らないため書いていますが、どうにか明示しない方法がないかを模索しています。

完全に書かないわけにはいかないと思うので、フィルターの型パラメータにするなどで、省略出来ればいいかなーと考えています。

// Filterの型パラメータとして
def HogeFilter = Filter[java.io.File] { req => 
  continue
}

// 省略したら AnyContent とみなす

def HogeFilter = Filter { req =>
  continue
}

2. 内部処理はなるべくPlay2標準のActionを利用するようにしたい

現在はFilter処理を保持するためにFilterActionというクラスを作っていますが、独自のものよりは標準で用意されているものを使ったほうがいいのかなと思っています。

3. 英語・・・

scaladocを英語で書きたかったのですが、読めるけど書けないというなんとも残念な感じなので、どうにかしたいところです。


とりあえず動くところまでは行ったので、あとは使いながら修正していこうかなと思います。もう少ししっかり作れば、最低限のフィルター処理的なものに使えると思うので、実用出来るところまで持って行きたいです。

play2でフィルター処理っぽく書きたい

ソースも全然できてませんが、play2でActionを実行する前に色々とフィルター処理がかけたらいいなーと思い、色々と試行錯誤してます。

イメージ的には、こんな感じ。(フィルター処理は適当)

def index =
  Filter { req =>
    req.getQueryString("key1") match {
      case Some(_) => continue // continueで次のフィルターに処理が移る。
      case None    => BadRequest // continue以外のResultの場合はそれが返る。
    }
  } >> Filter { req =>
    req.getQueryString("key2") match {
      case Some(value) => continueWith(value) // continueWithで渡した値は次のFilter/Actionに渡る(出来ればタプルで)
      case None        => BadRequest
    }
  } >> Action { (value2) => request =>
    Ok("key2 is %s" format value2)
  }

実現できないかなぁ・・・。

Play2のevolutionスクリプトのコメントを表示するプラグインを書いた

この記事は Play or Scala Advent Calendar 2012 12/8 分の記事です。

前は @lyrical_logical さんの View/Context Bounds の制約 です。
次は @gakuzzzz さんです。


Play2には「evolutions」という、DBスクリプトを管理してくれる機能があります。他の仕組みでDDL等を管理していないのであれば、これを利用しているのではないでしょうか。

スクリプトには、スキーマの変更情報をSQL直で記述できるので、RoRのmigrationよりもいいなーと思っているのですが、ファイル名が気になっていました。

migrationでは、ファイル名を201212080149_create_users_schema.rbのように自由に決めることができ、ファイル名からある程度どのようなスクリプトかが判断できます。

しかし、evolutionsでは、ファイル名が1.sql, 2.sqlというように固定となっており、ファイル名からスクリプトの中身を判断できません。

そのため、Playプラグイン(sbtプラグイン)の勉強をかねて、evolutionsスクリプトに記載されているコメントを一覧で表示するプラグインを書いてみました。

rabitarochan/play2-evolist-plugin

どんなもの?

playコンソールでevolistというコマンドを実行すると、スクリプトのコメント部分を列挙するプラグインです。

スクリプトには#で始めるとコメント行として認識されるため、スクリプトからその行を取得し、一覧として表示しています。

例として、以下のようなスクリプトがあったとします。

(1.sql)
# Person schema

# !Ups
create sequence person_id_seq;
create table person (
  id integer not null default nextval('person_id_seq'),
  name varchar(40)
);

# !Downs
drop table person;
drop sequence person_id_seq;
(2.sql)
# Group schema with add column to User schema

# !Ups
create sequence group_id_seq;
create table group (
  id integer not null default nextval('group_id_seq'),
  name varchar(40)
);
alter table user add column group_id integer;

# !Downs
alter table user drop column group_id;
drop table person;
drop sequence person_id_seq;

この場合、スクリプトを実行すると以下のような出力が得られます。

[testapp] $ evolist
database: default
1: Person schema
2: Group schema with add column to User schema

もしコメントが長い場合、次の行に行った場合に適切なインデントを入れてあげるようにしています。( 現在はコンソールが 80 文字と決め打ちをしています。この部分はちゃんと実装します・・ )

[testapp] $ evolist
database: default
1: Person schema
2: Group schema with add column to User schema
3: This script is fix to the user join to some groups. Define relation table for
    user and group.

あ、一文字ズレてる!!

また、以下のようにDB名を指定することもできます。

[testapp] $ evolist -db test
database: test
...

まとめ

なにもまとまりませんが・・・。

ネタを探しつつ、ふと前から不便に思っていた部分を実装してみました。実装を初めてまだ時間が経ってなく、絶賛実装中です。ドキュメントもテストも整備されていません。

evolutionスクリプトを使ってらっしゃる方で同じようなことを思っていた方は、一度試していただき、いろいろとツッコミを入れていただければと思います。

sbtプラグインについても少しずつ分かってきたので、ネタが見つかったらプラグインの作り方を書こうと思います。

evolutionsスクリプト、ファイル名じゃ何がなんだか分からないからプラグインを書いてみたよー。というお話でした。

ScalaでSPI(サービスプロバイダインターフェース)を使う

Scalaでも、JavaのSPI(サービスプロバイダインターフェース)はもちろん使えます。

SPIは、AsakusaFrameworkで知って、Javaではいろいろ遊んでみたのですが、Scalaでは試してなかったので、試してみました。

ソースはこちら。
https://github.com/rabitarochan/scala-spi-test.git

インターフェースは?

Javaでは、インターフェースの実装クラスを [ META-INF/services/${インターフェースのFQCN} ] というファイルに書きます。

Scalaでは、インターフェース名の部分をトレイト名にしてあげることで、同じように使えました。

呼び出し方

ほぼJavaと同様ですが、Service.providers で得られるIteratorジェネリクスでないため、キャストが必要なようです。

package com.rabitarochan.parent

import java.util.Iterator
import scala.collection.JavaConversions._
import sun.misc.Service

object Main extends App {
  val services = Service.providers(classOf[PrintTrait]).asInstanceOf[Iterator[PrintTrait]]

  val message = "Hello world"

  services.foreach(p => p.print(message))

}

Scala版SPI?

github内を[ META-INF/services ]で検索してみたところ、apache/activemq-apolloというプロジェクトで[ ClassFinder ]というソースを見つけました。

https://github.com/apache/activemq-apollo/blob/5b8879cd8a681bcd91c5b661d26ccd51783bd06b/apollo-util/src/main/scala/org/apache/activemq/apollo/util/ClassFinder.scala

SPIの内部もこのような実装になってるのかなと思いつつ、他にも同じようなものがないかをいろいろ探しています。

パーサコンビネータの処理が終わってくれない!!

パーサコンビネータの勉強も兼ねて、ScalaでWikiエンジンを作ろうと思ってます。

パーサコンビネータでいろいろ試していますが、以下のコードは、私の環境では処理が終わってくれません。
なんでだろう・・・。

import scala.util.parsing.combinator.RegexParsers

case class Indent(i: Int)
case class Text(s: String)
case class Line(i: Indent, t: Text)

object Parser extends RegexParsers {

  def indent: Parser[Indent] = """^\s*""".r ^^ {in => Indent(in.length)}
  def text: Parser[Text] = """[^\n\r]*""".r ^^ {s => Text(s)}
  def eol = """\n?\r?""".r

  def line: Parser[Line] = indent ~ (text <~ eol) ^^ {case in ~ te => Line(in, te)}

  def parse(s: String) = parseAll(rep(line), s)
 
}

object Main extends App {
  val s = """  hoge\n  fuga"""
  
  // 自分の環境では、この処理は終わらない。
  // 戻り値は List(Line(Indent(2), Text(hoge)), Line(Indent(2), Text(fuga))) を想定しているが・・・
  Parser.parse(s)
}

とりあえずやりたいことは、

  • indent で行頭のスペースの数を取得したい
  • text で本文を取得したい
  • eof で行を区切る

なのですが、うまく処理が終わってくれません。