Groovy2.3.0-beta-1と噂のTraitを試してみた

Groovy2.3.0-beta-1がリリースされましたね。

目玉としてはやはりtraitでしょう。

今までのMetaClassによるメソッドの動的探索パスをいじる系の機能で似たようなことはできましたが、何かと限界(複数スレッドにおける動作の保証とかかなり泥臭い感じだったり、一度動的メソッドを追加すると取り除くのが大変だったり)がありましたが、それらを超えるために静的ソリューションとしてtraitが実装されました*1

ドキュメントがすごくわかりやすくて、サンプルコードを上から順に読むだけで少なくともどのように使えるかは難なく理解できるでしょう。

最初の方を読んでるとJava8のインタフェース+デフォルトメソッドと同じかな?と誤解しますが、Groovyのtraitはステートフルなのです。つまりプロパティを定義して状態が持てる。もう完全に普通の多重継承です。とうぜんダイアモンド継承などのコンフリクト問題はありますが、ドキュメントにあるとおり、「自前実装最強」「それ以外は登場順の後勝ち」的なルールで明確に決定されますし、特定の実装を優先したい場合も簡単です。まあ、衝突が気になるような名前は避ければいいわけですし。詳しくはドキュメントのこの辺を読みましょう。

GroovyConsoleで軽く試してみましたが、非常にいい感じです。

gist10127510

いわゆるアカデミックな本家の"trait"や、"Scalaのtrait"とどの辺が違うのかあたりは気になるところです。

2014/4/9追記

ASTはこんな感じ。細かい点はおいといてなるほどねーな感じです。

@groovy.transform.Trait
abstract interface public class Flyable extends java.lang.Object { 

    @org.codehaus.groovy.transform.trait.Traits$Implemented
    abstract public java.lang.String fly() {
    }

    @org.codehaus.groovy.transform.trait.Traits$Implemented
    abstract public java.lang.String getName() {
    }

    @org.codehaus.groovy.transform.trait.Traits$Implemented
    abstract public void setName(java.lang.String value) {
    }

}
public class Bird implements Flyable, Flyable$Trait$FieldHelper, groovy.lang.GroovyObject extends java.lang.Object { 

    private java.lang.String Flyable__name 
    private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo 
    public static transient boolean __$stMC 
    private transient groovy.lang.MetaClass metaClass 
    public static long __timeStamp 
    public static long __timeStamp__239_neverHappen1397005006100 

    public Bird() {
        metaClass = /*BytecodeExpression*/
        Flyable$Trait$Helper.$init$(this)
    }

    public void setName(java.lang.String arg1) {
        Flyable$Trait$Helper.setName(this, arg1)
    }

    public java.lang.String fly() {
        return Flyable$Trait$Helper.fly(this)
    }

    public java.lang.String getName() {
        return Flyable$Trait$Helper.getName(this)
    }

    static { 
        __timeStamp__239_neverHappen1397005006100 = 0
        __timeStamp = 1397005006100
        Flyable$Trait$Helper.$static$init$(this)
    }

    @groovy.transform.CompileStatic
    public java.lang.String Flyable__name$get() {
        return Flyable__name 
    }

    @groovy.transform.CompileStatic
    public void Flyable__name$set(java.lang.String val) {
        Flyable__name = val 
    }

    public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) {
        return this."$name"(* args )
    }

    public void this$dist$set$1(java.lang.String name, java.lang.Object value) {
        this ."$name" = value 
    }

    public java.lang.Object this$dist$get$1(java.lang.String name) {
        return this ."$name"
    }
    //....
}
public class UFO implements groovy.lang.GroovyObject extends java.lang.Object { 

    private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo 
    public static transient boolean __$stMC 
    private transient groovy.lang.MetaClass metaClass 
    public static long __timeStamp 
    public static long __timeStamp__239_neverHappen1397005006102 

    public UFO() {
        metaClass = /*BytecodeExpression*/
    }

    public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) {
        return this."$name"(* args )
    }

    public void this$dist$set$1(java.lang.String name, java.lang.Object value) {
        this ."$name" = value 
    }

    public java.lang.Object this$dist$get$1(java.lang.String name) {
        return this ."$name"
    }

    //...
}
abstract public static class Flyable$Trait$Helper implements groovy.lang.GroovyObject extends java.lang.Object { 

    private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo 
    public static transient boolean __$stMC 
    private transient groovy.lang.MetaClass metaClass 

    public Flyable$Trait$Helper() {
        metaClass = /*BytecodeExpression*/
    }

    public static void $init$(Flyable $self) {
        (( $self ) as Flyable$Trait$FieldHelper).Flyable__name$set('trait')
    }

    public static void $static$init$(java.lang.Class<Flyable> $static$self) {
    }

    public static java.lang.String fly(Flyable $self) {
        return "I'm $name and flying!"
    }

    public static java.lang.String getName(Flyable $self) {
        return (( $self ) as Flyable$Trait$FieldHelper).Flyable__name$get()
    }

    public static void setName(Flyable $self, java.lang.String value) {
        (( $self ) as Flyable$Trait$FieldHelper).Flyable__name$set(value)
    }

    public java.lang.Object this$dist$invoke$1(java.lang.String name, java.lang.Object args) {
        return this."$name"(* args )
    }

    public void this$dist$set$1(java.lang.String name, java.lang.Object value) {
        this ."$name" = value 
    }

    public java.lang.Object this$dist$get$1(java.lang.String name) {
        return this ."$name"
    }

    public java.lang.Object methodMissing(java.lang.String name, java.lang.Object args) {
        return Flyable."$name"(* args )
    }

    public void propertyMissing(java.lang.String name, java.lang.Object val) {
        Flyable."$name" = val 
    }

    public java.lang.Object propertyMissing(java.lang.String name) {
        return Flyable."$name"
    }

    //...
}
abstract interface public static class Flyable$Trait$FieldHelper extends java.lang.Object { 

    final public static java.lang.String Flyable__name 

    abstract public void Flyable__name$set(java.lang.String val) {
    }

    abstract public java.lang.String Flyable__name$get() {
    }

}

*1:実際の経緯がこのとおりかは自信なし

Grails/Gradleの「さっきのテストレポート」をAlfredに表示してもらう

はじめに

この記事は、G*(Groovy, Grails ..) Advent Calendar 2013の15日目として書かれたものです。 14日目は @grimroseさんでした。

最近某自作GrailsアプリにてGrails上でVert.xを利用してWebSocketのプッシュを実装したりしてたので、今回はその話を書こうかなと思っていたのですがやっぱりやめて、以前からそのうち書こうと思って寝かせていた開発Tipsネタの方がちょうど良い感じに熟してきたのでそちらを紹介することにします。

Grailsによる通知からのテストレポート表示(前回までのあらすじ)

以前、Grailsのアプリ起動完了やテストの結果をGrowlで通知するEvents.groovyが便利な件という記事を書きました。

Grailsのビルドイベントを、Grailsのイベント機構を利用して拾って、Growlで通知するというものです。 通知するだけではなくて、その通知ペインをクリックすると、そのテストレポートがブラウザで表示されるので、ユニットテストが結構捗ります。

f:id:nobeans:20131215011240p:plain

また、Mac OS X LionでOS標準の通知機構ができたので、それを利用した方法もこちらで紹介されています。自分も今はこれをベースに使っています。

terminal-notifierというMac OS標準のNotificationに通知するためのコマンドがあるので、growlnotifyコマンドの代わりにそれを使う感じです。 openオプションを使うことで通知ペインのクリック時にURLを表示させることができます。 openオプションにテストレポートHTMLファイルのURLfile://.../index.htmlを指定することで、クリック時のブラウザ連携を実現することができるわけです。 他にもexecuteオプションで任意のコマンドを実行させたりもできます。 詳しくは↓あたりをどうぞ。

Gradleによる通知からのテストレポート表示

さて、GradleもGrailsとは違う方式ながら、イベントリスナ機構を持っています。

Gradle標準(?)の実験的プラグインで通知アプリへの連携方法が提供されていて、それを使ってビルドイベントの前後で通知する方法がMr.Hakiのブログでまとめられています。

自分の場合は、取り回し重視で以下のシンプルなコードを改造して、直接terminal-notifierコマンドを実行するようにして使っています。

f:id:nobeans:20131215011244p:plain

サンプルコードは最後にまとめて紹介するとして、先を急ぎます。

Alfredとは?

ここで突然ですが、AlfredというMac用のコマンドライン型ランチャがあります。ホットキーで表示される入力ペインにアプリケーション名を入力すると、インクリメンタルサーチで候補が絞り込まれていき、対象が見つかったところで選択してENTERで実行します。QuickSilverも有名ですが、自分はAlfredを愛用しています。

単なるアプリの起動だけではなく、Spotlightのようにファイル内容の検索もできますし、「カスタム検索(Custom Searches)」を独自定義すればシームレスに色々な情報にたどり着く基盤を築くことができます。

さっきのテストレポートをみたいんだけど...

さて、さきほど紹介したGrails/Gradleの通知→レポート表示の場合、通知が表示されている間にクリックしないとレポートにたどり着けません。 履歴を表示してクリックすることもできますが一手間増えますし、だからといって、Stickyに通知が残るようにすると逆にウザいことが多いのでいやな感じです。 また、通知からしばらくたって、コードを修正している最中にテストレポートを再確認したくなることもあります。 「さっきのテストレポートがみたいなぁ」と思うわけです。

みなさんは、IDEでコードを書いていて「あのソースファイルを修正しよう」と思ったときどうやってそのファイルを開いていますか? まさかエクスプローラ風のビューアからディレクトリ階層をたどってファイルを見つけてダブルクリックしてないですよね? EclipseでもIntelliJでも、リソース名/型名によるインクリメンタルサーチでファイルを開く機能があります。 「あのソースファイル」と思った時点でその名前が念頭にあるんですから、それを使って対象を開くのがもっとも頭にも手にもやさしい自然な方法な訳です。 VimmerならUniteプラグインの出番ですね。

さて、テストレポートの表示も同じです。 頭では「さっきのテストレポートがみたいなぁ」と思っています。 その欲求をできるだけストレートにアクションに結びつけるにはどうしたらよいか。 ここでAlfred(または類似のランチャ)が役立つわけです。

  • 「あのアプリが起動したいなぁ」→ Alfredでアプリ名を指定して起動する
  • 「さっきのテストレポートがみたいなぁ」→ Alfredで開けないかな?

というごく自然な流れですね。

Alfredで「さっきのテストレポート」を表示するには

Alfredのカスタム検索を使って、これを実現しましょう。

しかし、彼はGrails/Gradleの文脈なんて知らないので、「さっきのテストレポート」をAlfredに見つけさせるのは難しいです。 そこで、通知と同じようにビルドイベントのフックで仕込みをしてしまうことにしましょう。

  1. ビルド実行後のフックで「テストレポートのファイルURI」を固定パスの隠しファイルに書き込んでおく
  2. 1.で書き込んだ固定パスのファイルを開くようなカスタム検索をAlfredに定義する

これだけです。単純な戦略ですがこれで十分です。

あとは、Grailsの場合なら「さっき起動したアプリのホーム画面」もさくっと表示できると捗りますね。

というわけで、早速やってみましょう。

実際のコードサンプル

Grails

以下のコードを~/.grails/scripts/_Events.groovyとして保存します。

Gradle

以下のコードを~/.gradle/init.gradleとして保存します。

Alfred

以下の文字列をコピーして、Alfredの入力欄に貼り付けるだけでカスタム検索が登録できます。 ユーザのホームディレクトリを使うので、以下の__YOUR_ACCOUNT__の部分を自分のアカウント名に書き換えてから、Alfredの入力欄に貼り付けてください。

  • AlfredからのGradleテストレポート表示
alfredapp://customsearch/Gradle%20Open%20Latest%20Test-Report/gradletestreport/ascii/url=file:///Users/__YOUR_ACCOUNT__/.gradle/latestTestReport.html
  • AlfredからのGrailsテストレポート表示
alfredapp://customsearch/Grails%20Open%20Latest%20Test-Report/grailstestreport/ascii/url=file:///Users/__YOUR_ACCOUNT__/.grails/latestTestReport.html
  • AlfredからのGrailsローカル起動アプリのホーム画面表示
alfredapp://customsearch/Grails%20Open%20Latest%20Run-App/grailsrunapp/ascii/url=file:///Users/__YOUR_ACCOUNT__/.grails/latestRunApp.html

これでOKです。 あとは、↓の辺りからGrailsやGradleのアイコンを探して、"Drop icon"ゾーンにドラッグ&ドロップすれば、更に見た目がクールになります。

試してみる

まずはGradleプロジェクトをビルドしてテストも実行しましょう。 無事に以下のような通知が表示されたでしょうか?

f:id:nobeans:20131215011244p:plain

表示されていれば裏できっと「さっきのテストレポート」情報も記録されているはず。 Alfredの入力ペインを表示させて「gradle」と入力してみると...

f:id:nobeans:20131215011249p:plain

「Gradle Open Latest Test-Report」を選択すると、期待通りブラウザ上にテストレポートが表示されましたね。

f:id:nobeans:20131215011255p:plain

Grailsプロジェクトがある人はそっちも是非ためしてみてください。

  • grails test-appを実行→「Grails Open Latest Test-Report」
  • grails run-appを実行→「Grails Open Latest Run-App」

おわりに

他のOSやコマンドラインランチャでもこの方法が使えます。 Mac/Alfred使い以外の人も是非お試しを。

さて、明日の16日目は @nobusueさんです!

GroovyServとGradle Daemonとの違い

10/23 9:50 訂正&追記: GradleがデフォルトでDaemon起動すると書いてた件 (thanks to きょんくん)

元記事: http://dev.classmethod.jp/server-side/gradle/gradle-first-contact/

経緯的な

id:daisuke-mが昼過ぎに技術を語るいつものシリーズにGradleを追加した旨のツイートをしてました。 生粋のGradlerとしては喜び勇んでチェックしてみたんですが、「Gradleのインストール」であるべきところを「Groovyのインストール」と書いてたので、親切心でコメントしたわけです。

そこまでは良かったのですが、ちょっと調子に乗ってしまいまして、

と書いたところ、記事に晒し上げされてしまいました。 なんということでしょう。

f:id:nobeans:20131022224044p:plain

そのままフェードアウトしてくれれば終わればまだ良かったのですが、自称怖くないモンスターの人に見付かって問い詰められているid:daisuke-mが忍びなさMAXでつい反応してしまったら、気がついたら私がGroovyServとGradle Daemonの違いについてなんか書かなければならない流れになっておりました。

ああ、恐ろしきかな因果の流れ。

GroovyServとは

Groovyコマンドを実行可能なJVMプロセスをサーバとして常駐させておいて、ネイティブコマンドを使ってTCP経由でコマンド引数をサーバに送り込み、評価した結果を再びTCP経由でもらって出力する、という拙作のツールです。

Groovyに限らずJVM系言語でスクリプトを書いた場合、JVMプロセスを起動して色々なクラスローディングする準備処理が遅くて、1秒ぐらいの待ち時間が発生します。

$ time groovy -e "println 'Hello'"
Hello

real    0m1.347s
user    0m1.481s
sys     0m0.168s

GroovyServをインストールして、クライアントコマンド経由で実行すると

$ time groovyclient -e "println 'Hello'"
Hello

real    0m0.057s
user    0m0.002s
sys     0m0.005s

すごく...はやいです...。

といっても、初回はサーバプロセスを起動するための待ち時間が数秒かかるんですけども。 一度起動したらそれ以降は上のような感じです。 はいはい、爆速爆速。

なお、mainクラスのエントリポイントを指定できればいいので、JythonとかClojureも同じように高速化できますScalaのコンパイルも頑張れば早くなるようですVim+QuickRunでgroovyConsole風にさくっと実行できるのにフル機能エディタ!みたいなこともできますし、SublimeText2からも似たようなことができます

一応このネタで2013/5にGr8conf EU 2013というデンマーク コペンハーゲンのGroovy系カンファレンスで発表してきたりしました。

詳しくは、ちょっと雑多な感じですがユーザガイド(日本語)をどうぞ。

Gradle Daemonとは

Gradleも同様にJVM上で動作するツールなので、起動の待ち時間が大変遅いわけです。 毎回JVMプロセスを一から起動してると、さくさくビルドやテストを試せません。 GroovyServみたいな仕組みが欲しいですよね。

ということで、Gradle本体が同じような仕組みを用意したのがGradle Daemonです。 歴史的に言うと、GroovyServの方が若干早いはず。 パクられた!とは言いませんが、時期的なものを考えると多少は参考にされたんじゃないかと自意識過剰気味にアピールしておきます。どうかな。違うかも。

最近のGradleはデフォルトでDaemonを利用します。 GradleがデフォルトでDaemon起動すると書いたな、アレは噓だ。 --daemonオプションが必要です。 以下はしれっと書き換えてあります。 すいません。

--daemonオプションはまだデーモンがいなければ起動して使い、すでに居ればそれを使うというオプションです。 というわけで、適当なGradleプロジェクトで、2回gradle --daemon tasksを実行してみると、2回目が劇的に早くなってるんじゃないかと思います。

たとえば、自宅のかなり古いMacBook mid 2008で試してみると、

$ gradle --daemon tasks
...
Total time: 12.02 secs

$ gradle --daemon tasks
...
Total time: 2.52 secs

とこんな感じです。すごいですね。 jpsしてみると

$ jps
9376 Jps
4136 GradleDaemon

とデーモンプロセスが常駐してるのが分かるはず。

あと--daemonオプションが付けられていようが、--no-daemonというオプションを追加すれば、デーモンは無視して自前プロセスで実行できたりします。 デーモンプロセスありきでalias設定してあっても、一時的にはずせるわけですね。

また、デーモンプロセスが要らなくなったら、普通にkillコマンドでもいいですが--stopオプションを使えばgradleコマンド経由でコントロールできます。

個人的にはGradle Wrapperを優先しつつDaemonは必須で使いたいので、以下のようなaliasをシェルで設定して使っています。

gradle='if [ -x ./gradlew ]; then GRADLE_BIN=./gradlew; else GRADLE_BIN=\gradle; fi; $GRADLE_BIN --daemon'

こちらも詳しくはユーザガイド(日本語)をどうぞ。

ちなみにGroovyServもビルドにGradleを使っていて、Daemon様におかれましては大変お世話になっております。

GroovyServとGradle Daemonの違い

Gradle Daemonは、Gradleタスクの起動処理をショートカットする
GroovyServは、Groovyスクリプトの起動処理をショートカットする
Gradle Daemonは、gradleコマンドのオプションですべて操作する
GroovyServは、groovyclientコマンドのオプションで操作するほかに、サーバプロセス起動用のgroovyserverコマンドがある
Gradle Daemonは、ユーザの気付かないうちにデーモンプロセスが常駐してる
GroovyServは、サーバプロセスの起動時にコンソール出力してアピールする

気の利いた大喜利風にオチがつけばいいのですが、実力的に無理でした。ごめんなさい。

結局、コンセプト的なものは一緒ですけど、用途が違うので細かいところで色々と違いますね。 あんまりまじめに比べるモノじゃないと思われます。 むしろGroovyServと比べるべきはNailgunとかDripあたり。

おまけ: NailgunとGroovyServの違い

Nailgunはよく知られたJVMプロセス常駐系アーキテクチャのツールです。 歴史的にはGroovyServよりも古いです。 古すぎて2005年以降はまったく更新されていません。 と思ったら、2012/11月にv0.9.1がリリースされててちょっと驚きました。

NailgunはピュアJava実装なのでビルトイン目的によく使われます。 というかAPI的にもそれっぽい感じ。 JRubyもNailgunを使ってますね。

GroovyServはGroovyで実装してるのでビルトイン目的には向いてません。 GroovyServは今更ピュアJava路線に変更するのは不可能ではないにしろ面倒つらい感じです。 ビルトインの代わりに、単にクライアントの引数でmainクラスのエントリポイントを指定するだけで色々なJVM言語の起動時間短縮を計れます。物は言いようです。

機能的な違いでいえば、下のような感じです。

  • GroovyServのみサポート
    • アクセス元アドレスと認証トークンを使ったアクセスコントロール
      • 許可されたアドレス以外のマシンからのコマンド実行はエラー
      • 同一マシンでも認証トークンが一致しないとコマンド実行はエラー
    • クライアント側のCLASSPATH環境変数をサーバ側に一時的に反映する
    • クライアント側の任意の環境変数をサーバ側に永続的に反映する(一時的反映がベストであるが現状は永続的)
    • クライアント側のカレントディレクトリをサーバ側に一時的に反映する
    • クライアント側のCtrl-Cでサーバ側の処理を中断する
  • 両方ともサポート
    • System#exit()が実行されてもJVMは落ちない

ほらこうやってみるとGroovyServ結構がんばってるでしょ? Nailgun側のメリットはビルトイン以外に思いつきませんが、他に何かあるかもしれません。 フェアじゃないのは単に知識不足です。すいません。

おまけ: DripとGroovyServの違い

Dripはシンプルで割り切ったアーキテクチャで中々興味深いところはありますが、アーキテクチャやサポート機能が違いすぎてGroovyServとはあまり競合しないふいんき。

簡単に言うと、

http://b.hatena.ne.jp/nobeans/20130904#bookmark-109556574

GroovyServのようなJVMプロセス常駐系/興味深いアプローチ/単一プロセスを使い回すとdirtyになっていくのでJVM引数,クラスパス,起動クラスのハッシュごとに異なるJVMデーモンを使う/コードベースが超小さい/bash限定は楽そう

という感じです。

Dripは、bashを前提にしているので色々実装がシンプルです。うらやましいです。

GroovyServは、Mac/Linux/Windowsで動作します。
現状ではWindowsサポートが若干手薄な気もしますが、次期バージョンではできるだけ環境ごとの機能差がないように頑張っています。
Dripは、カレントディレクトリ毎に別プロセスを起動します。
よって、複数プロセスが常駐します(一定時間でタイムアウトして消える)。
既存JVMプロセスに対するネイティブレベルでのCWD制御とかを不要にしたりしてます。割り切り方が素敵です。

GroovyServは、ポート毎に1つのサーバプロセスを用意しています。
普通はデフォルトポートで十分なので、サーバプロセスは1つだと思えばOKです。ある意味シンプル。
CWDなどは毎回力業で反映しています。

こんな感じでしょうか。

測定マシンについて一言

なお、上記記事中の性能的なものはすべて自宅の超古いMacbook 2008 Midで測定してます。 そうとう遅いです。 苦痛です。 今晩のAppleのイベントで新MBPでたら速攻で買い換えたい所存です。

まとめ

というわけで、GVMを使ってGradleをインストールしたら、ついでに

$ gvm install groovy
$ gvm install groovyserv

すると良いと思います。 更に手が滑って

$ gvm install gaiden

Gaidenのインストールまで進んだあなたはファッションリーダとしての自覚と風格が感じられて頼もしい限りです。

標準バンドルのVisualVMのウィンドウが空っぽになってしまったらキャッシュを削除すべし

経緯的な

9月にTwitterにぼやいた件のその後。

事象

事象としては、jvisualvmを起動するとこんな画面になって何もできないというもの。

f:id:nobeans:20131017135555p:plain

Netbeansプラットフォームアプリケーションの素のウィンドウだけ表示してみました的な感じで、メニューバーも本来のVisualVMとは違ってまっさらな感じ。 かろうじてVisualVM臭がするのはAboutダイアログぐらいです。 ホントになにもできません。

http://visualvm.java.net/download.html からダウンロードしたApp版のVisualVMは普通に動きます。

ちなみに、Macです。他のOSで発生するかは知りません。

解決策

VisualVMのキャッシュが壊れていたようです。 /Users/me/Library/Caches/VisualVM/7u14を削除してから起動したら、正常に動作しました。

ちなみに、7u40や7u45にバンドルされてるjvisualvmを使っても、ディレクトリ名は7u14でした。 なんででしょうね。

本家Grailsユーザガイドを開くと自動的に日本語翻訳にリダイレクトするChrome拡張

Grailsユーザガイド日本語翻訳版とブックマークレットの紹介 - 豆無日記で、紹介したブックマークレットを毎回実行するのがダルすぎるので、Chrome拡張にしてみました。

以下のURLからChrome拡張をインストールしてください。

セキュリティの都合から野良サイトからの直接インストールはできないようなので*1、いったんダウンロードしてから拡張の管理画面にドラッグ&ドロップするとよいでしょう。

これで、http://grails.org/doc/latest/*なサイトを開くと自動的にhttp://grails.jp/doc/latest/*にリダイレクトされるようになります。 これでGoogle検索結果からのリンクでもさくっと日本語版が表示できますね。

コードという程のモノではない何かはGitHubにサラしてあります。

*1:https://support.google.com/chrome_webstore/answer/2664769?p=crx_warning&rd=1

Grailsユーザガイド日本語翻訳版とブックマークレットの紹介

有志によるGrailsユーザガイドの日本語翻訳作業がずっと行われていたのですが、実は8月の頭にちょうど一区切り良い感じになりました。 そろそろリリースされそうなGrails 2.3向けに追記された部分などまだちょっと英語部分も残っていますが、基本機能についてはほぼすべて翻訳完了な感じです。

残念なことに検索しても出てくるのは本家の英語版だけだったりすることが多いです。

URLのドメイン部分がgrails.orggrails.jpと違うだけなので、ブラウザのアドレスバーを手作業で書き換えて日本語版を表示させたりして過ごしていますが、さすがに面倒になってきたのでブックマークレットを書いてみました。

  • タイトル例
    • Grailsユーザガイドを日本語化する
  • URL
    • javascript:location.href=location.href.replace(/grails.org/,'grails.jp')

そのまんまですが、わりと便利な感じなのでよかったらどうぞ。

iPhotoライブラリをDropboxで管理するときに気をつけるべきたったひとつのこと

iPhoto Libraryの中にあるOriginal、Data、Modifiedというエイリアスを削除してから共有しましょう。

なんで?

Dropboxエイリアス経由のパスであっても気にせずに物理的にサーバ上にファイルを格納するので、画像が多重にアップロードされてしまい、サーバ利用量がヤバいことになります。というか、なった。

具体的にはどうする?

iPhoto Libraryを右クリックのアレで開いて、Original、Data、Modifiedというエイリアスを削除しましょう。Data.noindexというのがあったらそれも*1iPhotoはこれらがなくても普通に動作しますが、万が一何かあっても自己責任でお願いします。

経緯

元々20GBも使ってないときに、40GBくらいのiPhoto Libraryを共有したら、アップロードに2〜3日かかったあと、合計で105GB使ってますよという状態になってしまったので、有料会員の特権(?)でサポートに問い合わせてみたら、FAQらしくて手順書ページを紹介されました。 対処後に一晩放っておいたら、無事に60GBぐらいに落ち着いて、サイズ保存則が無事に成立しました。

*1:自分はなかった