Groovy2.3.0-beta-1と噂のTraitを試してみた
Groovy2.3.0-beta-1がリリースされましたね。
目玉としてはやはりtraitでしょう。
今までのMetaClassによるメソッドの動的探索パスをいじる系の機能で似たようなことはできましたが、何かと限界(複数スレッドにおける動作の保証とかかなり泥臭い感じだったり、一度動的メソッドを追加すると取り除くのが大変だったり)がありましたが、それらを超えるために静的ソリューションとしてtraitが実装されました*1。
ドキュメントがすごくわかりやすくて、サンプルコードを上から順に読むだけで少なくともどのように使えるかは難なく理解できるでしょう。
最初の方を読んでるとJava8のインタフェース+デフォルトメソッドと同じかな?と誤解しますが、Groovyのtraitはステートフルなのです。つまりプロパティを定義して状態が持てる。もう完全に普通の多重継承です。とうぜんダイアモンド継承などのコンフリクト問題はありますが、ドキュメントにあるとおり、「自前実装最強」「それ以外は登場順の後勝ち」的なルールで明確に決定されますし、特定の実装を優先したい場合も簡単です。まあ、衝突が気になるような名前は避ければいいわけですし。詳しくはドキュメントのこの辺を読みましょう。
GroovyConsoleで軽く試してみましたが、非常にいい感じです。
いわゆるアカデミックな本家の"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で通知するというものです。 通知するだけではなくて、その通知ペインをクリックすると、そのテストレポートがブラウザで表示されるので、ユニットテストが結構捗ります。
また、Mac OS X LionでOS標準の通知機構ができたので、それを利用した方法もこちらで紹介されています。自分も今はこれをベースに使っています。
terminal-notifier
というMac OS標準のNotificationに通知するためのコマンドがあるので、growlnotify
コマンドの代わりにそれを使う感じです。
open
オプションを使うことで通知ペインのクリック時にURLを表示させることができます。
open
オプションにテストレポートHTMLファイルのURLfile://.../index.html
を指定することで、クリック時のブラウザ連携を実現することができるわけです。
他にもexecute
オプションで任意のコマンドを実行させたりもできます。
詳しくは↓あたりをどうぞ。
- ターミナルコマンドからMountain Lionの通知センターへ通知を表示させる『terminal-notifier』
Gradleによる通知からのテストレポート表示
さて、GradleもGrailsとは違う方式ながら、イベントリスナ機構を持っています。
- Chapter 42. 通知プラグイン
- Chapter 43. ビルド通知プラグイン
- Gradle Goodness: Getting Announcements from Gradle Build
Gradle標準(?)の実験的プラグインで通知アプリへの連携方法が提供されていて、それを使ってビルドイベントの前後で通知する方法がMr.Hakiのブログでまとめられています。
自分の場合は、取り回し重視で以下のシンプルなコードを改造して、直接terminal-notifier
コマンドを実行するようにして使っています。
- Gradle Growl Notifications
サンプルコードは最後にまとめて紹介するとして、先を急ぎます。
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に見つけさせるのは難しいです。 そこで、通知と同じようにビルドイベントのフックで仕込みをしてしまうことにしましょう。
- ビルド実行後のフックで「テストレポートのファイルURI」を固定パスの隠しファイルに書き込んでおく
- 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のロゴアイコン
- Grailsのロゴアイコン
試してみる
まずはGradleプロジェクトをビルドしてテストも実行しましょう。 無事に以下のような通知が表示されたでしょうか?
表示されていれば裏できっと「さっきのテストレポート」情報も記録されているはず。 Alfredの入力ペインを表示させて「gradle」と入力してみると...
「Gradle Open Latest Test-Report」を選択すると、期待通りブラウザ上にテストレポートが表示されましたね。
Grailsプロジェクトがある人はそっちも是非ためしてみてください。
おわりに
他の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のインストール」と書いてたので、親切心でコメントしたわけです。
@daisuke_m "Groovyのインストール"->"Gradleの〜"
— Yasuharu Nakano (@nobeans) 2013, 10月 22
そこまでは良かったのですが、ちょっと調子に乗ってしまいまして、
@daisuke_m つ "ついでにせっかくなのでGroovyもインストールしておきましょう。`gvm install groovy`"
— Yasuharu Nakano (@nobeans) 2013, 10月 22
@daisuke_m 要るかどうかなんて関係ないのです。そこにgvmがあるから入れるのです。groovyを入れたらgroovyservも一緒に入れるのです。
— Yasuharu Nakano (@nobeans) 2013, 10月 22
と書いたところ、記事に晒し上げされてしまいました。 なんということでしょう。
そのままフェードアウトしてくれれば終わればまだ良かったのですが、自称怖くないモンスターの人に見付かって問い詰められている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のみサポート
- 両方ともサポート
- 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にぼやいた件のその後。
7u40のretina対応のおかげでjvisualvmもくっきりきれいになってるなー
— Yasuharu Nakano (@nobeans) 2013, 9月 12
と思って、7u25と比較したりしてたら、メインウィンドウに何も表示されなくなってしまった...
— Yasuharu Nakano (@nobeans) 2013, 9月 12
7u40のjvisualvmが使い物にならなくなってしまった。再起動したら治るかな...
— Yasuharu Nakano (@nobeans) 2013, 9月 12
@cero_t 最初は普通に起動したんですけど、retinaとそうじゃないのを比較するために7u25に切り替えてjvisalvmを起動して、その後7u40のGUIをいじったら固まってしまい、その後は起動し直してもウィンドウ前面がグレイ状態(ビューがなし)という状況です
— Yasuharu Nakano (@nobeans) 2013, 9月 12
起動中にjvmを切り替えたせいかもしれないけど、アプリだけ再起動してもずっと同じ状態。7u25に切り替えて起動するとそっちは普通に使える。謎。OS再起動はまだしてない。
— Yasuharu Nakano (@nobeans) 2013, 9月 12
事象
事象としては、jvisualvmを起動するとこんな画面になって何もできないというもの。
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にサラしてあります。
Grailsユーザガイド日本語翻訳版とブックマークレットの紹介
有志によるGrailsユーザガイドの日本語翻訳作業がずっと行われていたのですが、実は8月の頭にちょうど一区切り良い感じになりました。 そろそろリリースされそうなGrails 2.3向けに追記された部分などまだちょっと英語部分も残っていますが、基本機能についてはほぼすべて翻訳完了な感じです。
残念なことに検索しても出てくるのは本家の英語版だけだったりすることが多いです。
URLのドメイン部分がgrails.org
→grails.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というのがあったらそれも*1。iPhotoはこれらがなくても普通に動作しますが、万が一何かあっても自己責任でお願いします。
経緯
元々20GBも使ってないときに、40GBくらいのiPhoto Libraryを共有したら、アップロードに2〜3日かかったあと、合計で105GB使ってますよという状態になってしまったので、有料会員の特権(?)でサポートに問い合わせてみたら、FAQらしくて手順書ページを紹介されました。 対処後に一晩放っておいたら、無事に60GBぐらいに落ち着いて、サイズ保存則が無事に成立しました。
*1:自分はなかった