2013年12月25日水曜日

[FreeMarker] ftl名前空間のすゝめ

つい最近知ったのですが、ftlにも名前空間があります。
例えば、ある画面にマクロを定義しているFTLを複数読み込む時、そのFTL毎に名前空間を分ける、ということができます。

名前空間の使い方

これの何がいいかというと、名前空間を分けると各FTLで定義した変数名やマクロ名が衝突しなくなります。
library-a.ftl で foo というマクロを定義し、 library-b.ftl でも同名のマクロを定義し、この2つのFTLを同じ画面で読み込み、それぞれのマクロを同時に、別のマクロとして使うことができるようになります。

<#-- library-a.ftl -->
<#macro copyright date>  
    <p>Copyright © $(date) John Smith. All rights reserved.</p>
</#macro>  

<#assign mail = "jsmith@acme.com">

上記のライブラリFTLを lib/library-a.ftl とします。
そして、このライブラリFTLを top.ftl で利用します。

<#inclde "/lib/library-a.ftl">

このように書くと、 copyright マクロと mail 変数がグローバルな名前空間に定義されてしまいます。
これだと、仮に top.ftl ですでに copyright という名前のマクロや mail という変数を定義していると、名前が衝突してエラーになってしまいます。(変数は同じものとして扱われます)

これだと、再利用可能なコードとは言えません。
そこで、 import という、別の構文を使ってライブラリFTLを読み込みます。

<#-- top.ftl -->
<#import "/lib/library-a.ftl" as libA>
<@libA.copyright date="2012-2013"/>
${libA.mail}

<#-- 出力内容
<p>Copyright © 2012-2013 John Smith. All rights reserved.</p>
jsmith@acme.com
-->

import を使ってライブラリFTLを読み込むと、新しい名前空間が作成されます。
この新しい名前空間からは、ライブラリFTL内で定義された変数か、グローバルで定義された変数か、データモデルの変数(Javaのアクションクラスから渡された変数)しか見ることができません。

この新しい名前空間にある2つの変数(マクロ)に top.ftl からアクセスするには、 import 構文で as の後ろに指定された名前を使います。この場合、上のコードで示したように、 libA.copyright, libA.mail という形で library-a.ftl で定義された変数やマクロにアクセスできます。

このように import 構文を使ってFTLを読み込むと、たとえ top.ftl で同名の変数やマクロが定義されていたとしても衝突してエラーにはなりません。

別の名前空間内の変数を編集する

<#-- top.ftl -->
<#import "/lib/library-a.ftl" as libA>
${libA.mail}
<#assign mail="jsmith@other.com" in libA>
${libA.mail}

<#-- 出力内容
jsmith@acme.com
jsmith@other.com
-->

データモデルと名前空間

データモデルの変数はどこからでも参照できます。
例えば user という名前の変数がデータモデルにあった場合、lib/library-a.ftl はこの user 変数にアクセスできます。

<#-- library-a.ftl -->
<#macro copyright date>  
    <p>Copyright © $(date) ${user}. All rights reserved.</p>
</#macro>  

<#assign mail = "${user}@acme.com">

user が"Fred"だった場合、下記のようになります。

<#import "/lib/library-a.ftl" as libA>
<@libA.copyright date="2012-2013"/>
${libA.mail}

<#-- 出力内容
<p>Copyright © 2012-2013 Fred. All rights reserved.</p>
Fred@acme.com
-->

ただし、名前空間内でデータモデルと同名の変数が宣言されていた場合、後者が勝ちます。

名前空間のライフサイクル

同じFTLを別名で複数回 import で読み込んだ場合、名前空間は最初の読み込みに対してのみ作られ、以後の読み込みは同一の名前空間に対する別名となります。

<#import "/lib/library-a.ftl" as libA>
<#import "/lib/library-a.ftl" as foo>
<#import "/lib/library-a.ftl" as bar>
${libA.mail}, ${foo.mail}, ${bar.mail}
<#assign mail="jsmith@other.com" in my>
${libA.mail}, ${foo.mail}, ${bar.mail}

<#-- 出力内容
jsmith@acme.com, jsmith@acme.com, jsmith@acme.com,
jsmith@other.com, jsmith@other.com, jsmith@other.com,
-->

グローバル変数とローカル変数

FTLにもグローバル変数とローカル変数があります。

assign(ローカル変数)

<#assign foo="bar">

変数の定義や置き換えができます。
特定の名前空間内に変数を作成したり、置き換えることもできます。
(<#assign someValue = "foo" in someNamespace>)

assign を利用して宣言された変数は、名前空間が変わると参照できません。

global(グローバル変数)

<#global foo="bar">

global を使って定義された変数は、すべての名前空間で参照できます。 しかし、個々の名前空間内で同名の変数が定義されていると、その名前空間からはグローバル変数の方にはアクセスできません。
ただし、その場合でも globals という特別な変数を介してアクセスすることはできます。

${.globals.foo}

globals を介してグローバル変数にアクセスできるだけでなく、データモデルの変数にもアクセスすることができます。

local(ローカル変数)

<#local foo="bar">

local 構文はマクロや関数の中でのみ使用することができます マクロ/関数内で local を利用して作られた変数は、そのマクロ/関数外からは参照できません。

つまりどうしたらいい?

  • ライブラリFTLを読み込むときは import で読み込む
  • マクロから参照したい変数が親FTLにある場合、 global を使って宣言する
  • マクロや関数内では、特に意図した場合でない限り local を使って変数宣言する

参考

FreeMarker Manual - Namespaces
FreeMarker Manual - local
FreeMarker Manual - assign
FreeMarker Manual - global

2013年12月5日木曜日

HomebrewからSubversion 1.7をインストールする

普通に brew install subversion してしまうと1.8がインストールされてしまうので、下記のようにする。

brew tap hombrew/versions
brew info subversion17
brew install subversion17

これでSubversion 1.7がインストールされる。

2013年11月19日火曜日

[Android]動的にリソースを取得する

ステータスバーにバッテリー残量を表示させようとして詰まったのでメモ。
動的にリソースを変更したい時はこうすると捗る。

getResources().getIdentifier(resourceName, resourceType, getPackageName());

上記コードでリソースのIDが取得できる。
resourceNameにはリソースの名前を、
resourceTypeにはリソースのタイプ(string, id, drawable…)を
指定する。

ちなみに、

getResources().getIdentifier("@drawable/ic_launcher", null, getPackageName());

みたいにリソース名にタイプを含めちゃっても取得できる。

[Android]特定のPreferenceに依存するPreferenceをつくる

特定のスイッチがオンの時に有効になって、オフの時には使えないスイッチを作る方法です。
ガリガリコード書かなきゃいけないのかと思ったら、xmlの設定だけでいけました。

<SwitchPreference
    android:key="pref1"
    android:title="pref1_title"
    android:switchTextOff="OFF"
    android:switchTextOn="ON"
    android:defaultValue="true"/>

<SwitchPreference
    android:key="pref2"
    android:title="pref2_title"
    android:switchTextOff="OFF"
    android:switchTextOn="ON"
    android:defaultValue="true"
    android:dependency="pref2"/>

android:dependency="foo" と指定すると、指定されたkeyを持つPreferenceの状態に依存するPreferenceが作れます。

2013年10月3日木曜日

Chrome 30で「新しいタブ」を従来のものに戻す方法

最近のChromeのアップデートで「新しいタブ」のデザインが刷新された。 タブの真ん中に検索ボックスを配置するのは明らかに初心者を意識した変更だけど、オムニボックスから検索が可能なことがわかっていれば改悪である。

なによりも、右下の「最近閉じたタブ」がなくなってしまったのが非常に不便でならない。

というわけで、もとの「新しいタブ」に戻してみた。 やり方は簡単。

  1. オムニボックスから chrome:flags にアクセス

  2. 「Instant Extended API を有効にする」オプションを「無効」にする

以上。 これで見慣れた「新しいタブ」に戻った。

2013年9月4日水曜日

tmuxinatorで思うように画面分割できないときにやるべきたった一つのこと

例によってタイトルは釣り。

最近tmuxを使い始めて、ついでにtmuxinatorも入れてみました。
iTermを起動したらすぐに作業が開始できていい感じです。
ただ、標準のレイアウトではうまく画面分割できなかったのでこんな感じで設定してみました。

1. tmuxを起動し、手動で画面を分割する

C-b % や C-b " を活用して画面を任意のレイアウトに分割します。

2. 分割の設定を取得

tmux list-windows を実行すると下記のように表示され、画面分割の細かい設定が取得できます

    tmux list-windows
    0: zsh* (3 panes) [364x84] [layout 4e2c,364x84,0,0{182x84,0,0,5,181x84,183,0[181x42,183,0,6,181x41,183,43,7]}] @2 (active)

3. 分割の設定をtmuxinatorの設定ファイルに転記する

上記コマンドで取得したレイアウト情報を転記します。
必要なのは layout より後ろの部分です。

    # .tmuxinator/foo.yml

    windows:
        - editor: 
            layout: 4e2c,364x84,0,0{182x84,0,0,5,181x84,183,0[181x42,183,0,6,181x41,183,43,7]}
            panes:
                - ls
                - cd workspace
                - # empty pane

以上、こんな感じで幸せになれました。

2013年8月26日月曜日

zshでgitの補完機能を使う

zshでgitのブランチ名等を補完できるようにします。
homebrewからzshとgitをインストールしてる前提です。

homebrewからgitをインストールしてると、一緒に git-completion.zsh がインストールされるので、これを使います。

1. .zshrcに設定追加

以下の内容を追加します。既存の内容と重複する場合は適宜調整してください。

fpath=($(brew --prefix)/share/zsh/site-functions $fpath)

autoload -U compinit
compinit -u

2. zcompdumpの再構築

以下のコマンドを実行します。

rm -f ~/.zcompdump; compinit

実行後に source ~/.zshrc するかzsh再起動すればブランチ名が補完されるようになってます。
以上。