Kuchitama Tech Note

はてな記法がいつまでたっても覚えられないので、はてなダイアリーからマークダウンが使えるこっちに引っ越してきました。

Packerにやられた話(´・ω・`)

お久しぶりです。 以前、Chef-Soloを使って、AMIの状態を収束させるフレームワークを作りました。

このフレームワークはGitHubで公開してます。 https://github.com/Kuchitama/ec2_automation

ただですね…

先日こんな記事が…

Packer & Chef-SoloでAmazon EC2のAMIを簡単に作る方法

ネタがダダ被りじゃねーか!!

なにはともあれ、とにかく、packerとやら試してみようと試してみました

結果…

ダダ被りじゃねーか!!

いや、もちろんオレオレフレームワークよりよっぽど高機能ですが…

NeoBundle のChef Cookbookを公開しました

お久しぶりです。kuchitamaです。

最近は、会社のブログ書いたり、会社のブログ書いたり、関Javaで発表したり、会社のブログ書いたりしてて、こっちの方は全く更新していませんでした。

久々の更新で緊張してますが、今日は会社のブログネタを転用して、ここでもChefネタです。 今回は、NeoBundleのCookbookを作って公開したので、ちょっと宣伝してみます。

NeoBundle Cookbook

NeoBundleは言わずと知れた、暗黒美夢王こと@ShougoMatsuさんが作られたVimプラグインですね。 こいつがいてくれるだけで、僕のVim環境は無限の広がりを見せるので、新しくサーバを立てた時に利用できる状態になっていて欲しいわけです。 インストール手順は自分でまとめたのですが、やはりVagrantなどでサクッと仮想マシンを立ち上げていろいろ試してみる時にいちいちインストール手順を踏むのは面倒なので、自動構築するためにCookbook化してみました。 そのため、現状のNeoBundle Cookbookはkuchitama的においしい設定になっていて、ちょっと汎用的でない部分がありますが、そのうち調整していくつもりです。

使い方

NeoBundle Cookbookはgithubで公開しています。

基本的にはChefリポジトリgit clone して、設定を書いてやるだけです。

]$ cd <chef-repository>/cookbooks
]$ git clone https://github.com/Kuchitama/neobundle.git
]$ cd ..
]$ vim localhost.json

{
  "run_list" : [
    "recipe[neobundle]"
  ],
  "neobundle" : {
    "user" : "vagrant",
    "group" : "vagrant",
    "vim_home" : "/home/vagrant/.vim"
  }
}

これで、準備が整いました。あとは、Chefを実行するだけです。

Attribute

NeoBundle CookbookのAttributeは下記の3つがあります。 * user * group * vim_home

userとgroup

vimを利用する権限を設定します。 例えば、vagrantで作成した仮想マシンを使用しているのであれば、userもgroupもvagrantを指定します。 userもgroupも初期値が設定されていませんので、設定する必要があります。

vim_home

vim_homeには、vimホームフォルダを作成するパスを設定します。 大体の場合に、ユーザのホームフォルダ直下に.vimを作成する事になると思います。

knife-soloとBerkshelfを使う

knife-soloとBerkshelfを使うとかなりお手軽にリモートサーバにNeoBundleをインストールする事が出来ます。 それぞれのインストールについてはこちらを参照して頂ければいいと思います。

まず、knife solo init <repository> でknife solo リポジトリを作ります。

つぎに、作成したリポジトリ直下にあるBerksfileを以下のように編集します。

cookbook "neobundle", git: "https://github.com/Kuchitama/neobundle.git"

そして、knife solo prepare <user>@<remotehost>で、作成されたjsonファイルを編集して、knife solo cook <user>@<remotehost>を実行すればOKです。

まとめ

以上、簡単にNeoBundle Cookbookの使いかたについて解説してみました。 最初に書いた通り、とりあえず自分が楽をするために書いたので、かなり汎用化されていない部分があります。 ちょこちょこといじって、 - ブログネタ - もっといいCookbookを作って行こうと思います。 課題としては以下の感じでしょうか。

  • テストを書く test-kitchen serverspec
  • Resourceに切り出せそうな処理を切り出す ** フォルダ権限の変更とか
  • vim_homeの設定が冗長な気がする ** ohaiとかでなんとかならんかなー

この辺、ちまちまと直して行きたいと思います。

入門Chef Solo - Infrastructure as Code

入門Chef Solo - Infrastructure as Code

突然のVimコマンドを作った

なぜ作ったのか

基本的には思いつきが全て。 ただ理由を挙げるとするなら、最近vimでいろいろプラグインを使って遊んでたりするので、 自分でも作ってみようかなぁとか思ってたのが大きい。 今回、プラグインではなくコマンドを作成したのは、ここ(C++でゲームプログラミング 【Vim Advent Calendar 2012】Vim プラグインを github で公開するまで【1日目】を読んだから。

なので、そのうちプラグイン化するつもり。

vimスクリプトについて調べるのには、下の3つの情報源が本当に役立った。 ありがとうございました。

解説(というか自分のためのメモ)

コマンドの定義

ユーザコマンドの定義は次の書式 :command[!] [{属性}...] {コマンド名} {実行するコマンド}

関数を呼び出すにはcall {関数}の書式で、callコマンドを通して関数を呼び出さなくては行けない。 それは、面倒なので、自作コマンドとして定義しておく事で、保存するときの:wとか終了の:qみたいなノリで自作関数を呼び出せる。

属性に-nargs={引数の数}を指定し、呼び出す関数に<args>を引数として指定する事で、コマンドの引数を関数に渡せる。

詳細はここを参照↓

Hack #158: ユーザコマンドを定義する

関数定義

関数のスコープにはグローバルスコープとスクリプトスコープがある。 グローバルスコープは関数名がg:から始まる関数のことで、グローバル域で有効になる。 今回だとg:totuzen関数がそれにあたる。

スクリプトスコープはs:から始まる関数で、その名の通りスクリプトファイル内でのみ有効になる。 なので、s:mozisu関数はスクリプトファイル内からしか呼び出せない。

モテる男のVim Script短期集中講座

日本語を含む文字数のカウント

文字数の取得はstrlen({文字列})だが、普通にutf-8のマルチバイト文字をstrlenすると、日本語の文字は1文字あたり3文字分カウントされてしまう。

:echo strlen('a')
#=> 1

:echo strlen('あ')
#=> 3

これを回避するために、substituteを使って、文字を全て1バイト文字に変換してしまってから、strlenで文字数を数える

substitute({ソース文字列},{置換元パターン},{置換文字列},{フラグ})

substitute("ほげfuga", ".", "x", "g")
#=> xxxxxx

strlen(substitute("ほげfuga", ".", "x", "g"))
#=> 6

名無しのvim使い → Vimスクリプトリファレンス → 03. 変数 → 文字列型

備忘録 Vimのスクリプトでの置換用関数

ソースコード全文

使い方

↑上のtotuzen.vimファイルどこかに保存して、:source totuzen.vimとコマンドを実行すればOK

:Totuzen "突然のvim"とかコマンドを入れると、いい感じに突然の死っぽくしてくれる

書いたはいいが…

ここまで書いて、そう言えばVim Advent Calendar 2012がまだ続いてたなー、vimmerの方々はどうやらクリスマス終了のお知らせを受け取ったみたいだなーとか思ったのだけど、このネタすでに増産されてて車輪の再発名もいいところだった… まぁ、当たり前だよね…

ましてや、はじめてVimプラグインを書いた話 - metropolisなんて、ネタかぶりもいいところ… しかも、僕は後発のくせに特に目新しい機能なんてないしね…

でも、あくまで自分の勉強用に始めた事なのでいいのです。 誰かやってるからって何もしないより、とりあえず自分の手を動かしてみる方がいいよね。 趣味コーディングだしね。

というわけで、アドベントカレンダーには参加せずにひっそりとブログ更新です。 (ちなみに、突然の死の先達たちのコードはまだ読んでないです。プラグイン化まで実現してから、ゆっくりと読ませて頂いて、自分の実装と比較するつもり)

Vimテクニックバイブル ?作業効率をカイゼンする150の技

Vimテクニックバイブル ?作業効率をカイゼンする150の技

Clojure+LeiningenでJavaFXプログラミング

JavaFX GUIプログラミング〈Vol.1〉をちょいちょい読み始めたので、 JavaFXアプリをClojure + Leiningenで開発する方法についてまとめる。

JavaFX GUIプログラミング〈Vol.1〉

JavaFX GUIプログラミング〈Vol.1〉

プロジェクトの作成

なにはともあれプロジェクトが無くては始まらないのでプロジェクトを作成

lein new javafx

で、とりあえずJavaFXを実行するにはjfxrt.jarが必要。 jfxrt.jarはMacOSXだと /Library/Java/JavaVirtualMachines/jdk1.7.0_09.jdk/Contents/Home/jre/lib/jfxrt.jar にあるっぽいので、そこからコピって来た。 ここのディレクトリにパスが通っていれば問題ないと思うのだけど、デフォでは通ってないっぽい。 作成したプロジェクトディレクトリ直下にlibディレクトリを作成して、その中にjfxrt.jarをコピー。

ただ、手動でjarを追加すると、基本的にLeiningenさんはjarを認識してくれないので、project.cljにjarのパスを追加してやる必要がある。 パスを通すには、:resource-pathsというプロパティを追加してやる。

これでコンパイル時のクラスパスにlib/jfxrt.jarが追加される。 ついでに、:mainを追加して、lein runしたときにちゃんと実行されるように設定しておく。

JavaFX Applicationの作成

準備が完了したので、早速プログラムの作成に入る。 Leiningenのデフォルトだと、src/javafx/core.cljが作成されているのだけど、今回は、自分でsrc/kuchitama/javafx.cljを作成し、そこに実装した。 わざわざ自分でパッケージを切った経緯については、@t_nodaさんと@bouzuyaさんのやり取りを元にしている。詳しくはこちらを参照

で、肝心のプログラムだけどこんな感じ

これで、JavaFX GUIプログラミングのP.8あたりのサンプル相当の実装になる。 こいつをベースにいじって行けば、Clojureでいろいろ出来るハズ

課題

このサンプルだと、lein uberjarを実行したときに、生成されるスタンドアローンなjarの中にjfxrt.jarが含まれないので、実はスタンドアローンで動作しない。 これを解決するには、そもそも手動でのライブラリ追加をやめてちゃんとLeiningenさんに依存関係を解決してもらえばいいのだけど、oracleはjfxrt.jarをmavenで公開していないため、それは不可能になっている。 その対応として自分でローカルのmavenリポジトリを作成して、そこにjfxrt.jarを追加するという方法がある。(参考) 確かに、これが一番オーソドックスな方法だと思うのだけど、僕はわざわざjfxrt.jarだけを管理するためにローカル環境にmavenリポジトリを作りたくはなかったので、今回手動追加という方法をとった。 でも、スタンドアローンなjarがスタンドアローンに動作しないのは詐欺もいいところなのでなんとかしたい。 どなたか、方法をご存知の方がいらっしゃればご教授ください。

Play sendFileのfileNameパラメータの型

昨日の記事で、PlayのsendFileメソッドのとりあえずの使い方を紹介しましたが、その追加情報です。

昨日のサンプルでは

Ok.sendFile(
  content = java.io.File("hoge.csv"),
  fileName = _ => "fuga.csv"
)

このように書く事で、ダウンロードされるファイル名を指定出来ると説明したのですが、よくよく見るとfileName = _ => "hoge.csv"ってなんじゃこりゃと思いませんか?

少なくとも僕は思いました。 今日、@backpaper0さんにも、「あれなんなの?」って聞かれました。 ただ、昨日は力尽きて調べる気力がわかなかったので、今日追記情報を載せます。

まず、sendFileメソッドの実装なのですが、↓こんな感じになってます。

https://github.com/playframework/Play20/blob/master/framework/src/play/src/main/scala/play/api/mvc/Results.scala Statusクラス(Line618-633)

/**
     * Send a file.
     *
     * @param content The file to send
     * @param inline Use Content-Disposition inline or attachment.
     * @param fileName function to retrieve the file name (only used for Content-Disposition attachment)
     */
    def sendFile(content: java.io.File, inline: Boolean = false, fileName: java.io.File => String = _.getName, onClose: () => Unit = () => ()): SimpleResult[Array[Byte]] = {
      SimpleResult(
        header = ResponseHeader(OK, Map(
          CONTENT_LENGTH -> content.length.toString,
          CONTENT_TYPE -> play.api.libs.MimeTypes.forFileName(content.getName).getOrElse(play.api.http.ContentTypes.BINARY)
        ) ++ (if (inline) Map.empty else Map(CONTENT_DISPOSITION -> ("attachment; filename=" + fileName(content))))),
        Enumerator.fromFile(content) &> Enumeratee.onIterateeDone(onClose)
      )
    }

ポイントは、引数のfileNameが fileName:java.io.File=> String = _.getName となっているところです。

これを括弧とか足して書き直すと、 fileName : {java.io.File => String} = _.getName みたいな感じになります。

つまり、fileNameは、

  • java.io.Fileを引数にとり、Stringを返す関数で、
  • デフォルトでは、def func(file:java.io.File) = {file_.getName}という関数が適用される

引数である、ということになります。

で、sendFile関数内では、 (if (inline) Map.empty else Map(CONTENT_DISPOSITION -> ("attachment; filename=" + fileName(content))))) このように、sendFileの第一引数であるcontentをfileNameの引数に渡しているので、contentのファイル名がContent-Dispositionヘッダに追加されることになります。

そのため、任意のファイル名を使用したいときは、fileNameに単純にStringを指定するのでなく、'java.io.File => String'型の関数として、_ => "hoge.csv"のように、何か引数を受け取るけど無視して"hoge.csv"を返す関数を指定しないといけないようです。 fileNameという引数名の型が関数というのは若干違和感がありますが、実装的にはScalaの特徴的な機能をつかっていて「なるほど」という感じですね。

Play2.0Scalaでファイルダウンロード

目的

play2.0Scalaでplay1の時にあったrenderBinaly的な事をしてcsvファイルをダウンロードしたかったので、調べた結果のまとめ。

参考

http://www.playframework.org/documentation/2.0/ScalaStream

とりあえず、このエントリ読むよりも上記公式情報を読むべき。 Serving files の項を読めば、ここでやる内容は十分。 英語が辛いとか、とりあえず動けばいい人だけ、ここから下を読んでください。

とりあえずダウンロード

play2.0 scalacsvファイルをダウンロードするサンプル

conf/routes

# Home page
GET  /            controllers.Application.index
GET  /download    controllers.Application.download

app/controllers/FileDownloadController.scala

package controllers

import play.api._
import play.api.mvc._
import java.io.File

object Application extends Controller {
  
  def index = Action {
    Ok(views.html.index("Your new application is ready."))
  }
  
  def download = Action {
    val file = new File("hoge.csv")
    
    Ok.sendFile(file)
  }
}

このサンプルだと、ダウンロードできるファイル名は、uriからdownloadとなってしまう。 ファイル名そのままのhoge.csvとなります。 公式サイトだと、サンプルとしてPDFをダウンロードしているが、

This helper will also compute the Content-Type header from the file name, and add the Content-Disposition header to specify how the web browser should handle this response. The default is to ask the web browser to download this file by adding the header Content-Disposition: attachment; filename=fileToServe.pdf to the HTTP response.

とあるように、デフォルトはファイル名が自動で指定されるらしい。 なのに、ファイルがcsvだとうまくいかない。

ファイル名を指定

なぜか自動で解決してくれないので、明示的にファイル名を指定する。

app/controllers/FileDownloadController.scala

…

object Application extends Controller {
  
  …
  
  def download = Action {
    val file = new File("hoge.csv")
    
    Ok.sendFile(
      content = file,
      fileName = _ => "fuga.csv"
    )
  }
}

こうすると、ダウンロード出来るファイル名をfuga.csvのように、元のファイル名に関係なく指定する事が出来る。


一部、勘違いがあったので修正しました。 2013/01/12

引っ越しました

新・Kuchitama Tech note
http://kuchitama.hateblo.jp/

はてな記法がいつまでたっても覚えられず、
はてなブログならMarkdownで書けるから楽そうだな〜っと思って引っ越しました。

もうちょい更新頻度を上げようと思うので、今後ともよろしくお願いします。