あと味

たくさん情報を食べて、たくさん発信すると、あとになって味わい深い。

S式から(X|HT)MLに変換する簡易的なコマンドラインツール作った

Lispの仏様と言われた竹内郁雄先生は、XML分厚いカッコのあるLispとおっしゃっているそうです。

実際に、S式で(HT|X)MLを書くと、自然でシンプルに書けるので、S式を(HT|X)MLに変換する単純なコンバータを作りました。

作ったもの

Buakko - Simple s−expression to (X|HT)ML converter.

ネーミングのBuakkoは「分厚いカッコ」をもじりました。

シンプルなコンバータです。むしろ、現時点ではいろいろ機能が足りない感じです。

S式といっても、データの表記方法の一つとして採用しているだけで、Lispとの関連性は強くはありません。演算機能もありませんし。

ツールはPerlで書かれています。

動かすのに必要なもの

  • Perl 5.8以上
  • CPANモジュール
    • Parse::RecDescent
    • Path::Class

また、インストールにはcpanmが必要です。

使い方

インストールするとbuakkoというコマンドラインツールがインストールされるので、それを使います。

$ buakko sample > sample.html

これで、sampleというS式で記述したHTMLファイルを、sample.htmlというHTMLに変換します。

リダイレクトしなければ、標準出力にプリントされます。

以下の様なS式形式のファイルを

(html (@lang "ja")
  (head # ヘッダ
    (meta (@charset "utf-8"))
    (title "EXAMPLE"))
  (body (@class "example home") # ボディ
    (h1 "example!")
    (p 
      (a (@href "http://example.com/" @target "_blank") "example web site."))))

以下のようなHTMLファイルに変換します。

<!doctype html><html lang="ja"><head><!--ヘッダ--><meta charset="utf-8" /><title>EXAMPLE</title></head><body class="example home"><!--ボディ--><h1>example!</h1><p><a href="http://example.com/" target="_blank">example web site.</a></p></body></html>

その他、細かい仕様とかインストール方法は、下記のREADMEをご覧ください。

README.md

作った動機

hiccupをとても気に入っていて、Clojureを利用する時には、これを利用すればよいのですが、Clojureを使わなくても気軽にS式をHTMLに変換できるツールがあるといいなぁと思っていました。

何かのモジュールで出来ればよかったんですけど、なかなかズバリなものがなかったので、パーサ部分から書くことにしました。

Perlで作った理由は、Movable Typeで利用したいと思っているからです。これでテンプレートを書けるようにしたい。

このコンバータには、演算機能がありませんが、Movable Typeには、MTVarやら、MTIFやら、MTForやらいろいろあるので、これくらいの機能でも充分かなと思います。

汎用性はすごく低いので、必要に応じてバージョンアップできたらいいですね。

気づいたこと

パーサを書かないといけないというのはわかっていたものの、自分のレベルでは、正規表現でパーサ書くのは厳しいと思ったので、いろいろ調べてみると、Parse::RecDescentという超便利なツールを見つけました。

このツールを作る大半が、Parse::RecDescentの使い方を調べる時間になった気がしますが、これを使えば、Perlで使うちょっとしたミニ言語が作れそうです。

それでも正しく書けているのかどうかよくわからないし、それ以前にコンパイラ周りのこと、全然知らないので、このツールに頼らなくても、ミニ言語が作れるようになりたいですね。

他に、野良モジュールをインストールしてもらう方法とか全然知らなかったので、いろいろ苦戦しました。

まとめ

S式とは言いつつも、ただ、記法を利用しただけなので、別にLispができるできないは関係なく使ってくれる人がいるといいなぁと思います。S式もDOMも、同じく木ですし、S式はそんなに難しいものではないです。

とりあえず、タイプ数は減るし、zen-coding感覚で使ってもらえれば良いかと。

そのままLispに目覚めても良いと思いますよ; )

時間のある時に、Movable Typeのプラグインとして利用できるようと思います。

流行りっぽいツールを組み合わせたミニマルpjaxサンプル書いた

先日、Meteorっていうツールが話題になっていて、いろいろ見てみたんですけど、pjaxいいなぁと思って、今まで実装したことなかったので、Mojolicious::Liteで実装してみました。

ついでに流行りっぽいツールを組み合わせてみました。

利用したツール

  • Mojolicious::Lite
  • Backbone.js
  • jQuery
  • CoffeeScript
  • LESS

実装するにあたり、以下の記事を参考にしました。

サンプル

1ファイルなので、ミニマルですってことです。コードもなるべく短くなるようにしました。

ここで使ってるツールは流行ってるし、オシャレですね。

どうもMojolicious::Liteが一番流行ってなさそうですね。流行らせたいですね。

公式サイトのオシャレさはピカイチなんですけどね。

もとい、自分の周りからでもPerl流行らせたいですね。

実行した時の画面

f:id:jdg:20120414164302p:image

ナビをクリックすると、fadeInとともにオシャレにあいさつ文が変わるだけのサンプルです。

試す方法

Perl5.10.1以上が入ってる環境で、以下の手順でミニマルpjaxサンプルを試せます。

  1. http://mojolicio.us/のトップページからtar.gzで圧縮されたMojoliciousをダウンロード(インストールしてもよいです)
  2. ダウンロードしたMojoliciousの最終バージョンを解凍し、mojoとでも付けておく
  3. 上記のgistのファイルをmojoディレクトリに設置する
  4. 設置したapp.plの2行目に、use lib "lib";を追加する
  5. ターミナルでmojoディレクトリに移動する
  6. そのディレクトリ内で以下のコマンドを叩く
$ ./script/morbo app.pl

最後にlocalhost:3000にアクセスすれば試せます。

まとめ

流行ってる感じでオシャレ。

まじめなまとめ

pjax、初めて試しましたが、とても良いですね。特にユーザー側に不便をかけるわけでもなく、反応速度が上がるってのが良い。

しかも、後からpjaxに対応しても、そんなに手間じゃないのがいいですね。

ただ、pushStateに対応していないブラウザは、Backbone.jsのRouterがハッシュのついたURLに変更してしまうので、その辺りが採用できるかできないかの基準になるかなと思います。

Mojoliciousのせいなのかわかりませんが、IEでどうしても$c->req->headers->is_xhrが取れなかったので、jquery-pjaxと同じように、リクエスト時のパラメータに_pjax=trueを付けるという対応にしました。

LESSとCoffeeScriptのファイルを読み込む時は、これらのリクエストがis_xhrではないので、レイアウトを読み込んでしまうのを避けるために、_pjaxを指定しています。

今回のサンプルはCoffeeScriptとかLESSを直接パースするようにしていますし、CDNからファイルを持ってきているので、実運用では、コンパイルするものはコンパイルして、サーバーに置くものはダウンロードして、publicディレクトリに入れておくべきだと思います。

以上、まじめなまとめでした。

Perlの環境構築

こなれてきたので、Perlの環境構築についてまとめる。出尽くされてる感は半端ないけど。

導入するツール

  • perlbrew
  • cpanm(App::cpanminus)
  • Carton

perlbrew

perlbrewはユーザー領域に複数のPerlのバージョンをインストールして使えるツール。Rubyのrvm相当。

インストール

公式ドキュメントに書いてある手順で簡単に導入できる。

$ curl -kL http://install.perlbrew.pl | bash

これでインストール終わり。

設定

次はperlbrewの設定。でも最初から付いてくる。

$ echo source ~/perl5/perlbrew/etc/bashrc >> ~/.bash_profile

インストールした際のログに書いてあるが、perlbrew用のbashrcが添付されてるので、それを読み込む設定を.bash_profileなどに追記するようにするだけ。

$ which perlbrew
/home/taiju/perl5/perlbrew/bin/perlbrew

whichコマンドで、ちゃんとパスなど通してくれてることが確認できる。
(ターミナルを一度も閉じていないと、.bash_profileの設定が反映されていないので、source ~/.bash_profileをあらかじめ実行しておくこと)

使い方

perldoc形式でヘルプが用意されている。

$ perlbrew -h
perlbrewでperlをインストールする

perlbrewでユーザー領域にperlをインストールする。

$ perlbrew install perl-5.14.2

しばらくすると、インストールが完了する。

perlbrewでインストールしたperlを利用するようにする

perlbrewでインストールしたperlを、そのユーザーで利用するようにする。

$ perlbrew switch perl-5.14.2

また、perlbrewでインストールしたperlはlistコマンドで確認できる。

$ perlbrew list

いつでも好きなタイミングで、好きなバージョンに切り替えできる。

cpanm(App::cpanminus)

より良いCPANシェル。CPANシェルはこれで完全に置き換えが可能。

インストール

次は、cpanmを導入する。perlbrewにインストールコマンドがある。

$ perlbrew install-cpanm

これでインストール終わり。

追記(2012/08/29)

Cartonとcpanmを組み合わせる際、この方法でcpanmをインストールするとあんまりよろしくないようです。

carton と古い cpanm をくみあわせたら悲しい話 - tokuhirom's blog.

cpanmはperlbrewに同梱されたインストーラを使うのではなく、通常通りインストールした方が良さそうです。以下のリンク先の「Installing to local perl (perlbrew)」という項を参考にしてください。

App::cpanminus - search.cpan.org

使い方
$ cpanm -h

Carton

CPANモジュールの依存関係管理ツール。RubyのBundler相当。今後もよく使いそうなコマンドがインストールされるCPANモジュールなどはcpanmを利用して、インストールして、それ以外は、プロジェクトごとにCartonでインストールするのが良さそう。

インストール

cpanmでインストールする。

$ cpanm Carton


これでインストール終わり。

cartonの使い方

プロジェクトのディレクトリを作ったら、まず、Makefile.PLにプロジェクトに必要なCPANモジュールを列挙する。cartonのhelpはModule::Installで書かれているし、最近はExtUtils::MakeMakerより採用例をよく見かけるので、Module::Installも導入しておいた方が良さそう。

$ cpanm Module::Install

では、本題に戻って、Makefile.PL。cartonのドキュメントの例を掲載する。

use inc::Module::Install;
name 'MyAwesomeApp';
requires 'Plack', 0.9980;
requires 'Starman', 0.2000;
WriteAll;

そのプロジェクトのMakefile.PLを書いたら、依存モジュールをプロジェクトで使えるようにcartonでモジュールをインストールする。

$ carton install


プロジェクト内のフォルダに、localというディレクトリが作られ、cpanmでモジュールがインストールされている。

まとめ

一昔前は、自分にとっては環境構築が本当に難しかったんだけど、今はツールも揃っていて、導入も簡単だし、Perl楽しい。

Kansai.pmのかんそうぶん

先週の土曜日にKansai.pmに参加したので、その感想。

今年のYAPC::Asiaに参加して、Kansai.pmのことをいろいろ教えてもらった*1のですが、その後、割とすぐ、Kansai.pmの告知がTwitterに流れてきたので、参加することにしました。

自分は福井県にいるので、なかなか関東圏のイベントに参加するのは金銭的に厳しいのですが、 京都開催であれば、往復1万円かかりません。日帰り余裕だし、特急乗らなければ、往復5,000円で行けちゃうので、大変ありがたいです。(特急乗りましたが)

ちょうど、天気がよく行楽日和で、京都行きの電車は満員でしたし、京都駅もたくさん人がいました。

通常トラックの感想など

Web Operations and Perl

livedoorのインフラの話や、kazebroさん作のcloudforecastの紹介を聞けました。

グラフは未来を予測するためには大変有益なものだなと再確認しました。cloudforecast便利そうです。

Perlで伝統芸能

hitode909さんは、いつもアイディアがすごい。実演もされてて面白かったです。

PerlでAPNS

APNSは全く触ったことがなかったので、どういう仕組みで、iPhoneに通知が行くか、なんとなくわかりました。実際使ってみないとですが。

YAPC::Asiaのすすめ

YAPCに参加した日々を思い出しつつ、聞いていました。来年も行きたいですね。

Perlオワコンと言う人には、YAPC::Asiaの写真を見せつけるとおっしゃっていた点、とても共感しました。

PerlでKyTea

単なる形態素解析エンジンではなくて、自分モデルが作れるというのが面白そうだなと思いました。

テストデータどうしてますか?

CPAN Authorたちはどのような経緯でCPANモジュールを作るのかという、一つのモデルがわかったようで勉強になりました。

Perl & Email

Email::*の名前空間に属するモジュールがどういう過程で作られたのかが知れてよかったです。モジュールの選定に役立てそうです。

Perlの仕事がなかった僕がいかにしてPerlで仕事をするようになったか

lapisさんのPerlで仕事をできるようになるまでのストーリーを聞けました。単にPerlが好きというだけでなく、コミュニティに積極関与していく姿勢は自分にないので、見習いたいです。

LT

ネタあり、実用的な話ありで、笑ったり勉強になったり。

その他

  • 10分て短い感じがしましたが、聞いていると結構、必要十分。
  • mixiやペパボが関西進出を目論んでるという話で、関西キテる。
  • はてなのオフィスはひとりひとりのスペースが広々してる感じでかっこよかった。
  • 感想文苦手。どうしても小学生の作文になる。

*1:[http://d.hatena.ne.jp/jdg/20111016/1318752772:title]参照

YAPC::Asia TOKYO2011で感じたこと・考えたこと

今年は、会社の理解もあり、念願のYAPC::Asia TOKYO 2011に参加することができました。YAPCは初参加です。

結論として、ものすごく楽しく、刺激を受け、良い思い出となりました。

中学生の作文みたいな感想で稚拙ですが、3日間の感想をまとめてみます。

  1. 参加した動機
  2. 前夜祭
  3. 1日目
  4. 2日目
  5. 感じたこと・考えたこと

この順番で書いてあります。

参加した動機

YAPCは以前から羨ましいなと思っていました。初めて触ったプログラミング言語Perlでしたし、趣味プログラミングのために利用していたこともありました。Perl界隈の有名人のブログや書籍なども読んでいて、あまりPerlに触れていない時期もありましたが、結果的には最も長く触れている言語だったと思います。その言語の大イベントということで、いつか参加したいと思いが強くありました。

去年・一昨年は職種的に参加しづらいということもありましたが、エンジニアではなかったという劣等感があったのが、参加を躊躇していた最も大きな理由だと思います。*1

今は、ようやくエンジニアとしてのキャリアをスタートさせることができたので、この部分がクリアできたのは、参加の大きな動機になっています。

ただ、それでも、やはり技術や知識面での劣等感はありましたので、以下のやり取りがなければ、参加していなかったかもしれません。懇親会にて、キッカケを作ってくださった@lestrrat(id:lestrrat)さんに感謝の言葉を伝えることができてよかったです。

第三者から見たら、大したことないやり取りかもしれませんが、自分にとっては、参加する一歩を踏み出す大きなキッカケです。

前夜祭

福井からの参加だったので、木曜日の昼頃、新幹線に乗って会場に向かいました。

荷物を下ろしたり、Suicaを手に入れたりなどして、会場に向かいましたが、東工大の入り口がわからず迷ってしまい、*2@s_ohira(id:s_ohira)さんのトークから参加しました。Twitterでしか絡んだことがなかったので、声をかけていただきお会いできてよかったです。

事前チェックインをしてしまえたのは楽でした。後、東工大の敷地内を多少把握できたことと、ロケタッチでPerlおまつりシール貰えてよかったです。

1日目

東工大の入り口は把握できたので、スムーズに講堂に入れました。

どのトークを聞くかはいろいろ迷ったのですが、メインホールのトークが特に多くの人に聞いて欲しいトークなんだろうなと考え、1日中メインホールに居座ることにしました。

「 Perl 5.16 and beyond」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

Perl5.16とその先というお話でした。英語ではありましたが、Perlがどのように開発されているのか、これからどのような方針で開発していくのかを聞けて勉強になりました。字幕もあり、非常にわかりやすかったです。

小さい言語を目指していくって話や、CPAN Author達みんながPerlを作っているんだという話が印象深かったです。

「 Carton: CPAN dependencies manager」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@miyagawa(id:miyagawa)さんを初めて生で見ました。RubyのBundler相当をPerlで実現するというCartonというツールのお話でした。

まだ、自分がPerlで作ったプロダクトが少ないのでバージョン依存等の問題に出会ったことはない気がしますが、今後長く続けていくうちに、Cartonにお世話になることがきっと出てくると思いました。

「 SmartPhone development guide with Node/CoffeeScript and HTML5 technologies, for Perl programmers」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@naoya_ito(id:naoya)さんも初めて生で見ました。NodeとCoffeScriptでスマートフォンアプリケーションを作る事例についてのお話でした。

特にアクションの多いスマートフォン向けアプリだと、イベントドリブンな言語を使う利点は高そうだなと思いました。

フロントエンドに書いたけど、やっぱりバックエンドに置いといた方がいいなと思った処理をコピペできるっていうのもいいですね。

「 perlbrew」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

Any::Eventのお話でした。同じ非同期処理の話だし、前のトークと比較しながら聞けたらいいなと思いましたが、内部の技術的な話が多く、スライドよりもしゃべり中心だったので、正直あまりわからなかったです。。。英語のヒアリングできるといいな。

モジュール開発者の話は、そのモジュールをよく使っていないと厳しいなと思いました。

「 Perl 5.14 For Pragmatists」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

Perl5.14でどのようなことができるかというお話でした。Perl5.14はすでに使えるのようになっていますが、以前のバージョンから使える機能しか触っていなかったので、勉強になりました。

英語ではありましたが、perldelta - perl v5.14.0 での変更点 【perldoc.jp】を開きながら聞いていたので、話に付いていけました。

リファレンスを取り扱う時の文法が簡潔になったり、破壊的なサブルーチンと知らずに使ってハマってしまうようなことがなくなりそうで、Perl初学者でも使いやすい言語になったんだなと感じました。

「 Mobageソーシャルゲームにおける大規模サーバ運用 with Perl (Large scale server operations in Mobage&#39;s Social Games with Perl)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@riywo(id:riywo)さんの、モバゲーのインフラのお話でした。

インフラの知識が乏しいので、ちょっと難しい部分が多かったですが、日々、様々な問題が発生していて、様々な問題解決手段を試しているということがわかって、インフラエンジニアもクリエイターだなと思いました。

もっとインフラのこと勉強しようと思います。

「 LT Day 1」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

LTは誰もが面白いネタを持ってきていて、とても楽しめました。

ずっとメインホールにいたので、くだけたトークを初めて聞けました。緊張がほぐれた感じがしましたし、2日目はくだけたトークも聞きたいなと思いました。

懇親会

ぼっち時間は長かったですが、会ったことがある方、ネットで絡んだことがある方を探して、多少なりお話できました。自分的には頑張れた方だと思っています。

@takesako(id:TAKESAKO)さんにごあいさつした際、地方PMで一番近いであろう、Kansai.pmのことをお聞きしたら、案内してくれました。その後、id:hakobe932さんに、Kansai.pmのいろんな方を紹介していただきました。ありがとうございました。

関西圏のイベントは福井から参加しやすいので、開催する際には、ぜひ参加したいです。

2日目

1日目を経て、2日目は自分の聞きたいトークに参加することにしました。

参加したトーク
  1. 「 続 Unix Programming with Perl (Unix Programming with Perl 2)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  2. 「 大規模環境における、マニアックなキャッシュ利用術 (Cache Maniacs)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  3. 「 ぼくがかんがえたさいきょうのうぇぶあぷりけーしょんふれーむわーく」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  4. 「 DISられないCPANizeを目指して (PrePAN - Introduction to a website to request for reviews of Perl modules)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  5. 「 Hacking with metacpan」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  6. 「 Monads in Perl」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  7. 「 Evolution of API With Blogging」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  8. 「 少人数でのWebアプリ開発 - CGIからPSGIまでの変遷」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  9. 「 OtoPerl - perl でライブ・サウンド生成しよう。 (OtoPerl - live sound generation with Perl.)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  10. 「 Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームを作ろう (Perl Hobby Programming - Games::BeLike::EightBIT Let&#39;s make the terminal game which looks like a retro 8 bit one.)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  11. 「 LT Day 2」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  12. 「 Managing A Band Of Hackers」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
  13. 「 Closing」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]
「 続 Unix Programming with Perl (Unix Programming with Perl 2)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@kazuho(id:kazuhooku)さんのUnixプログラミングのお話でした。

続編とのことだったので、後日前編も読まなきゃなと思います。

かなり具体的な話で、本当にしっかりしたUnixプログラミングをするには、モジュールのこともOSのこともよくしっていないといけないんだなと痛感しました。

フォーマルな感じで、大学の情報サイエンス系の授業って、こんな感じなのかなと、大学生が羨ましく思えました。

「 大規模環境における、マニアックなキャッシュ利用術 (Cache Maniacs)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@xaicron(id:xaicron)さんの、memcachedの利用術のお話でした。

これも個人的には馴染みが薄い話だったのですが、前の流れもあって、Perlハッカーはインフラハッカーでもあるんだなと思いました。

輝け!全日本最強 CPAN Author 決定選手権でも優勝されていましたし、キャッシュというテーマで、時間切れになるほど資料を用意していらっしゃったし、アウトプット力がすごい人なんだなぁと感じました。

「 ぼくがかんがえたさいきょうのうぇぶあぷりけーしょんふれーむわーく」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@cho45(id:cho45)さんのWAFのお話でした。

来るはてなダイアリーのアップグレードの舞台裏を聞けました。既存のフレームワークを使わず、WAFを自作したという話で、YAPCに参加してからはじめてのWebプログラミングの話だった気がします。

WAFの設計の話ですが、「安全 (信頼性設計)」「読むコード最小 (メンテコスト)」という設計指針を持って取り組んだということで、簡潔でわかりやすい話でした。こういうのって、人によっては、ダラダラな話になってしまいがちだと思うんですけど、シンプルに伝えられる人ってすごいなと思います。

その機能を使うべきか考えて欲しいことは、あえて面倒な手順(コストをかけさせるよう)にしておくという話も印象に残りました。

あと、その後、別の会場に移動した際に、発表を聞いていたnaoyaさんとcho45さんの絡みが見れて、面白かったです。

「 DISられないCPANizeを目指して (PrePAN - Introduction to a website to request for reviews of Perl modules)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@kentaro(id:antipop)さんの最近オープンしたPrePANの紹介や作った背景のお話でした。

懇親会でご挨拶して、ちょうど、2日目の大岡山駅に向かう道中でもお会いしたんですが、プレゼンの最終調整をされていました。

ある便利なモジュールを作った時に、CPANizeする人としない人がいる中、どうやったらCPANizeする人が増えるか考察した結果、その人にとってのincentiveを伸ばし、disincentiveを除くためのツールとしてPrePANを作ったそうです。

どちらかと言うと、disincentiveの方がCPANizeしない理由として大きいということでしたが、自分自身も、劣等感で今までYAPCに参加できなかったこともあり、共感しました。

「 Hacking with metacpan」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@tokuhirom(id:tokuhirom)さんのmetacpanについてのお話でした。

metacpanはcpanモジュールをググった時に、検索結果にミラーサイトが出るくらいの認識でしかなかったので、今後積極的に使っていきたいと思いました。

APIを提供しているようで、今後、そのAPIを活用した便利サイトがいろいろ出てくる期待を持ちました。

時間余った分も別の話をしてくれましたし、LTでもそうでしたが、発表に余裕のある人の話は聞いていて安心だしわかりやすいです。テンポもよくて、プレゼンの参考にもなりました。

「 Monads in Perl」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@hiratara(id:hiratara)さんの、Perlモナドを作ってみるというお話でした。

モナドという言葉に触れることは何度もあったんですが、そのたびに理解していない言葉です。ただ、今回のトークはすごくわかりやすかったので、多少のもやもやが消えたような気がしました。なるべく近日中に自分で動くコードを書けばスッキリするかもしれません。

後で調べてみたら、jQueryは本当にモナドだった - 北海道苫小牧市出身のPGが書くブログを書かれた方なんですね。JavaScriptの方が、言語仕様をまだ把握している方なので、また読み返したいと思います。

gihyo.jpのレポーターも平行してされていたそうですが、噛み砕いて説明できる人って素敵ですね。

「 Evolution of API With Blogging」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@comewalkさんの、ブログのAPIの歴史とこれからについてのお話でした。

話に出てくるもので、知らないメタ情報やAPIがたくさんありました。

今後のAPIとして、情報の取得をする時に、全入りは重いので、部分取得したり更新したりできるAPIがあるといいねとか、記事情報以外の全体的なインポート、エクスポートの共通仕様があるといいねというお話もありました。

個人的に、今後Movable Typeを利用するシーンも多くなりそう*3なので、ブログの新しい仕組みをどんどん開発して欲しいなと思います。

「 少人数でのWebアプリ開発 - CGIからPSGIまでの変遷」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@aloelightさんの、少人数でのWebアプリ開発についてのお話でした。

個人的には、このトークが一番身近だったため、とても参考になりました。aloeightさんの、少人数でWebアプリを開発していく上での開発手法の変化や苦労話を自分に置き換えながら聞いていました。自分もエンジニアが一人だけという現状なので。また、スキルアップしていく過程も聞けてよかったです。

YAPCに行くたびに、開発手法を見直しているとのことで、これも実践していきたいなと思いました。

「 OtoPerl - perl でライブ・サウンド生成しよう。 (OtoPerl - live sound generation with Perl.)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@hrkさんのPerlを使って、ミキサーを作るというお話でした。

サンプルをいろいろ聞かせてもらったのですが、後ろに薄く流れるBGMとしても効果があった気がします。なんとなく心地よく話も聞けた気がしました。

技術的に自分では到底思いつかないものでしたが、Perlの可能性を垣間見ました。

プロジェクタに映しながら、動的に音を変えていくコンセプトで作っているとのことで、そういうライブパフォーマンスがあったら、注目してしまいそうです。

「 Perl Hobby Programming - Games::BeLike::EightBIT ターミナルで8ビット風ゲームを作ろう (Perl Hobby Programming - Games::BeLike::EightBIT Let&#39;s make the terminal game which looks like a retro 8 bit one.)」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@keroyonn_(id:keroyon0630)さんの、Perlで8ビットゲームエンジンを作ったというお話でした。

初日のLTでも見せていただいて、すごく笑ったのですが、トークが面白い方ですね。初日のLTでは拝めなかったボス戦を見ることもできました。

ゲームプログラミングの書籍にあるサンプルコードをPerlで書きたいというのも、作った動機の一つだそうです。

異なるプログラミング言語のバックグラウンドがあってこそできる芸当なんだろうなと思いました。

「 LT Day 2」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

2日目のLTでした。Perl MongerのLTは本当にクオリティが高いですね。

「 Managing A Band Of Hackers」の詳細 - YAPC::Asia Tokyo 2011 [Oct 13, 14, 15]

@hidekさんのハッカーをマネジメントするというお話でした。

DeNAにいるCPAN Author達を統べるマネージャーで、なかなか同じような体験をしている人はいないんだろうなと思いました。

非常に真面目な話でしたが、ところどころTwitterのTLに絡んだり、Perlの愛されキャラと言われる所以を垣間見ました。

コードを書かないと決意する葛藤の話など、内面的な心情を聞けたのも良かったです。

最後にPerlコミュニティに対しての感謝の言葉で締めていましたが、とても穏やかで幸せそうな笑顔が素敵でした。

感じたこと・考えたこと

今回、YAPC::Asia TOKYO 2011に参加して感じたこと・考えたことを書き出してみました。

発表者はその発表に自分の持つ数ヶ月すべてをかけてる(ように見える)

発表される方は皆さん1年間で得た知識財産や制作物を、この場で発表するという意気込みを持って参加しているように見えた。しかも半端なものではなく、自分で納得できるものを発表しているように見えた。

Perl Hackerの知識は広い

特に、インフラ、JavaScriptなどのフロントエンドの話もされている方、そもそも普段はPerlerじゃないって人が結構いて、広い知識を持っている方が多い印象。

エンジニアじゃないと参加できないというのは幻想だった

技術に興味があれば、どのトークも刺激的だし、むしろエンジニアでない方が発表することさえあることがわかった。

毎年参加するくらいの意気込みがあった方がいい

どうしてもトークが被ってしまうし、来年は今年見れなかった人のトークを見るというような計画もできる。あと、発表者のリアルな成長を目にすることができそうだし、頑張るモチベーションになりそう。

せめてネット上だけでもPerl Monger絡むべき

地方にいるとなかなかPerl Mongerの方とお会いすることができないが、ネット上で絡んだというだけでも会話の糸口はあった。懇親会を有意義に過ごすためにも。

手を動かさないといけない

来年、LTに出すぞという意気込みで、モノづくりしたいと思った。実際に手を動かしている人の話には魅力がある。

Perlerっぽく見える努力をする

Perlやってる感じがまだ出てない気がしているので、ブログも含め、Perlやってる人っぽく見えるようにしたい。

発表すればぼっちにならない

これは、YAPCで感じたことという訳ではないが、経験的に、発表者側に回れば、それなりに興味を持ってくれた人が話しかけてくれるし、YAPCでもまた同様だと感じた。

CPANAuthorになりたい

できれば来年には。多少なり、技術的な劣等感は拭えるはず。

仕事外にプログラミングできる時間を作る

大きな会社の方の発表は別にしても、そうじゃない方の発表は、仕事外でのプログラミング経験に基づいた話をしていることが多かったように思う。その時間が取れないというのはまずい気がした。

ブログの記事書くなら1日ごとに分けるか、ちゃんと帰ってからまとめといた方がいい

2日目に比べて、1日目の方が印象が薄い、というより、記憶が保ててない気がするので、頭の中の鮮度があるうちにエントリーに起こすなり、下書きするなりした方が良さそう。

最後に

運営の皆様、発表者の皆様、その他、参加された皆様。非常に良い体験ができ、刺激になりました。ありがとうございました。

*1:実は、こっそりPerl Casual #1には参加してたのですが、その時はエンジニアでもないし、そんなにPerlも書いてないし、空気感パネェ状態でした。

*2:というか、駅の目の前なのに横道に入ってしまった俺が悪い

*3:Wordpressを使う案件が多かったのですが、ソース読んだり、プラグイン作ったりする時に、馴染みのあるPerlの方が良いしストレスがないので、なるべくMTにさせて欲しいとお願いしているところ

Mojoliciousで出力時だけ別の文字コードにする

Mojoliciousを使っていて、タイトルの件でハマったので、解決方法をメモがてら記事に起こします。

前提・課題

  1. 制作はUTF-8を利用し、出力時はEUC-JPを使いたい
  2. テンプレートは別ファイルに分けたい

MojoliciousはUTF-8を使用する前提で作られているようで、異なる文字コードを使うには少し手間がかかります。

Mojolicious::Plugin::Charsetというプラグインがあって、これを使えば解決しそうな感じでしたが、ちょっと問題がありました。

app.pl
#!/usr/bin/env perl
use utf8;
use Mojolicious::Lite;

plugin Charset => { charset => 'EUC-JP' };

get '/welcome' => sub {
  my $self = shift;
  $self->render('welcome');
};

app->start;
__DATA__

@@ welcome.html.ep
% layout 'default';
% title 'Welcome';
うぇるかむとぅもじょりしゃす!

@@ layouts/default.html.ep
<!doctype html><html>
  <head><title><%= title %></title></head>
  <body><%= content %></body>
</html>

上記のようにひとつのファイルで完結する場合は問題ありませんでしたが、前提にあるように、テンプレートは下記のように別ファイルで管理したい。

app.pl
#!/usr/bin/env perl
use utf8;
use Mojolicious::Lite;

plugin Charset => { charset => 'EUC-JP' };

get '/welcome' => sub {
  my $self = shift;
  $self->render('welcome');
};

app->start;
default.html.ep
% layout 'default';
% title 'Welcome';
うぇるかむとぅもじょりしゃす!
welcome.html.ep
<!doctype html><html>
  <head><title><%= title %></title></head>
  <body><%= content %></body>
</html>

テンプレートを別ファイルに分けると、文字コードの変換に問題が生じます。文字化けというやつです。

テンプレートをEUC-JPで制作すれば問題ありませんが、ファイルごとに文字コードが違うというのも面倒なので、できればテンプレートはutf-8のまま制作したいと思うと、何らかの方法で解決するしかありません。

Mojolicious::Plugin::Charsetを読んでみる

Mojolicious::Plugin::Charsetはシンプルなプラグインでした。

Mojolicious::Plugin::Charset
package Mojolicious::Plugin::Charset;
use Mojo::Base 'Mojolicious::Plugin';

# "Shut up friends. My internet browser heard us saying the word Fry and it
#  found a movie about Philip J. Fry for us.
#  It also opened my calendar to Friday and ordered me some french fries."
sub register {
  my ($self, $app, $conf) = @_;

  # Got a charset
  $conf ||= {};
  if (my $charset = $conf->{charset}) {

    # Add charset to text/html content type
    $app->types->type(html => "text/html;charset=$charset");

    # Allow defined but blank encoding to suppress unwanted
    # conversion
    my $encoding =
      defined $conf->{encoding}
      ? $conf->{encoding}
      : $conf->{charset};
    $app->renderer->encoding($encoding) if $encoding;

    # This has to be done before params are cloned
    $app->hook(after_build_tx => sub { shift->req->default_charset($charset) }
    );
  }
}

1;

HTMLのMIMEタイプの文字コードと、レンダラのエンコード、リクエストの文字コードを、ユーザーが指定した文字コードに一括で設定するようです。

文字化けが起こる原因を調べる

テンプレートを処理している箇所は、Mojolicious::Controllerのrenderメソッドなので、それを調べます。

Mojolicious::Controller
sub render {
  my $self = shift;

  # Recursion
  my $stash = $self->stash;
  if ($stash->{'mojo.rendering'}) {
    $self->app->log->debug(qq/Can't render in "before_render" hook./);
    return '';
  }

  # Template may be first argument
  my $template;
  $template = shift if @_ % 2 && !ref $_[0];
  my $args = ref $_[0] ? $_[0] : {@_};

  # Template
  $args->{template} = $template if $template;
  unless ($stash->{template} || $args->{template}) {

    # Default template
    my $controller = $args->{controller} || $stash->{controller};
    my $action     = $args->{action}     || $stash->{action};

    # Normal default template
    if ($controller && $action) {
      $self->stash->{template} = join('/', split(/-/, $controller), $action);
    }

    # Try the route name if we don't have controller and action
    elsif ($self->match && $self->match->endpoint) {
      $self->stash->{template} = $self->match->endpoint->name;
    }
  }

  # Render
  my $app = $self->app;
  {
    local $stash->{'mojo.rendering'} = 1;
    $app->plugins->run_hook_reverse(before_render => $self, $args);
  }
  my ($output, $type) = $app->renderer->render($self, $args);
  return unless defined $output;
  return $output if $args->{partial};

  # Prepare response
  my $res = $self->res;
  $res->body($output) unless $res->body;
  my $headers = $res->headers;
  $headers->content_type($type) unless $headers->content_type;
  $self->rendered($stash->{status});

  return 1;
}

my ($output, $type) = $app->renderer->render($self, $args);の箇所で、別のrenderメソッドを呼んでいます。Mojolicious::ControllerのPODによると、このrenderメソッドはMojolicious::Rendererのrenderメソッドのラッパーということなので、Mojolicious::Rendererのrenderメソッドをさらに調べます。

Mojolicious::Renderer
sub render {
  my ($self, $c, $args) = @_;
  $args ||= {};

  # Localize extends and layout
  my $partial = $args->{partial};
  my $stash   = $c->stash;
  local $stash->{layout}  = $partial ? undef : $stash->{layout};
  local $stash->{extends} = $partial ? undef : $stash->{extends};

  # Merge stash and arguments
  while (my ($key, $value) = each %$args) { $stash->{$key} = $value }

  # Extract important stash values
  my $template = delete $stash->{template};
  my $class    = $stash->{template_class};
  my $format   = $stash->{format} || $self->default_format;
  my $handler  = $stash->{handler};
  my $data     = delete $stash->{data};
  my $json     = delete $stash->{json};
  my $text     = delete $stash->{text};
  my $inline   = delete $stash->{inline};

  # Pick handler
  $handler = $self->default_handler if defined $inline && !defined $handler;
  my $options = {
    template       => $template,
    format         => $format,
    handler        => $handler,
    encoding       => $self->encoding,
    inline         => $inline,
    template_class => $class
  };

  # Text
  my $output;
  my $content = $stash->{'mojo.content'} ||= {};
  if (defined $text) {
    $self->handlers->{text}->($self, $c, \$output, {text => $text});
    $content->{content} = b("$output")
      if ($c->stash->{extends} || $c->stash->{layout});
  }

  # Data
  elsif (defined $data) {
    $self->handlers->{data}->($self, $c, \$output, {data => $data});
    $content->{content} = b("$output")
      if ($c->stash->{extends} || $c->stash->{layout});
  }

  # JSON
  elsif (defined $json) {
    $self->handlers->{json}->($self, $c, \$output, {json => $json});
    $format = 'json';
    $content->{content} = b("$output")
      if ($c->stash->{extends} || $c->stash->{layout});
  }

  # Template or templateless handler
  else {
    return unless $self->_render_template($c, \$output, $options);
    $content->{content} = b($output)
      if ($c->stash->{extends} || $c->stash->{layout});
  }

  # Extends
  while ((my $extends = $self->_extends($c)) && !$json && !$data) {
    my $stash = $c->stash;
    $class                     = $stash->{template_class};
    $options->{template_class} = $class;
    $handler                   = $stash->{handler};
    $options->{handler}        = $handler;
    $format                    = $stash->{format} || $self->default_format;
    $options->{format}         = $format;
    $options->{template}       = $extends;

    $self->_render_template($c, \$output, $options);
  }

  # Encoding (JSON is already encoded)
  unless ($partial) {
    my $encoding = $options->{encoding};
    encode $encoding, $output if $encoding && $output && !$json && !$data;
  }

  return $output, $c->app->types->type($format) || 'text/plain';
}

コメントもヒントに、Mojolicious::Controllerにあった、$outputの値を作っているところを探すと、Templateを利用している場合は、return unless $self->_render_template($c, \$output, $options);のところっぽいです。

Mojolicious::Rendererの_render_templateメソッドも調べてみます。

Mojolicious::Renderer

sub _render_template {
  my ($self, $c, $output, $options) = @_;

  # Renderer
  my $handler =
       $options->{handler}
    || $self->_detect_handler($options)
    || $self->default_handler;
  $options->{handler} = $handler;
  my $renderer = $self->handlers->{$handler};

  # No handler
  unless ($renderer) {
    $c->app->log->error(qq/No handler for "$handler" available./);
    return;
  }

  # Render
  return unless $renderer->($self, $c, $output, $options);

  # Success!
  return 1;
}

return unless $renderer->($self, $c, $output, $options);の箇所でハンドラを呼び出しているみたいです。ハンドラはepを使っているので、Mojolicious::Plugin::EpRendererを読んでみます。

Mojolicious::Plugin::EpRenderer
sub register {
  my ($self, $app, $conf) = @_;

  # Config
  $conf ||= {};
  my $name     = $conf->{name}     || 'ep';
  my $template = $conf->{template} || {};

  # Custom sandbox
  $template->{namespace} =
    'Mojo::Template::SandBox::' . md5_sum(($ENV{MOJO_EXE} || ref $app) . $$)
    unless defined $template->{namespace};

  # Auto escape by default to prevent XSS attacks
  $template->{auto_escape} = 1 unless defined $template->{auto_escape};

  # Add "ep" handler
  $app->renderer->add_handler(
    $name => sub {
      my ($r, $c, $output, $options) = @_;
################### 中略 ################### 
      # Render with epl
      return $r->handlers->{epl}->($r, $c, $output, $options);
    }
  );

  # Set default handler
  $app->renderer->default_handler('ep');
}

1;

return $r->handlers->{epl}->($r, $c, $output, $options);の箇所でeplを使ってレンダリングしてますね。

ということで、Mojolicious::Plugin::EplRendererも読んでみます。

Mojolicious::Plugin::EplRenderer
sub register {
  my ($self, $app) = @_;

  # Add "epl" handler
  $app->renderer->add_handler(
    epl => sub {
      my ($r, $c, $output, $options) = @_;

      # Template
      my $inline = $options->{inline};
      my $path   = $r->template_path($options);
      if (defined $inline) {
        utf8::encode $inline;
        $path = md5_sum $inline;
      }
      return unless defined $path;

      # Cache
      my $cache = $r->cache;
      my $key   = delete $options->{cache} || $path;
      my $mt    = $cache->get($key);

      # Cached
      $mt ||= Mojo::Template->new;
      if ($mt->compiled) { $$output = $mt->interpret($c) }

      # Not cached
      else {

        # Inline
        if (defined $inline) {
          $c->app->log->debug('Rendering inline template.');
          $mt->name('inline template');
          $$output = $mt->render($inline, $c);
        }

        # File
        else {
          $mt->encoding($r->encoding) if $r->encoding;
          return unless my $t = $r->template_name($options);

          # Try template
          if (-r $path) {
            $c->app->log->debug(qq/Rendering template "$t"./);
            $mt->name(qq/template "$t"/);
            $$output = $mt->render_file($path, $c);
          }
################### 以下略 ################### 

$$output = $mt->render_file($path, $c);の箇所でテンプレートファイルを読み込んでるっぽいです。やっとたどり着いた...

$mtはMojo::Templateなので、Mojo::Templateのrender_fileメソッドを読んでみます。

Mojo::Template
sub render_file {
  my $self = shift;
  my $path = shift;

  # Slurp file
  $self->name($path) unless defined $self->{name};
  croak "Can't open template '$path': $!"
    unless my $file = IO::File->new("< $path");
  my $tmpl = '';
  while ($file->sysread(my $buffer, CHUNK_SIZE, 0)) {
    $tmpl .= $buffer;
  }

  # Decode and render
  $tmpl = decode($self->encoding, $tmpl) if $self->encoding;
  return $self->render($tmpl, @_);
}

ここでようやくdecodeがでてきました。ふぅ。

$self->encoding文字コードでデコードしているようです。

$self->encodingの値はMojolicious::Plugin::EplRendererの$mt->encoding($r->encoding)の箇所で設定されています。

Mojo::Templateのencodingの値を設定する時の引数、$r->encodingの$rは、ソースを読むとMojolicious::Rendererのインスタンスとわかります。Mojolicious::Rendererのencoding属性は、Mojolicious::Plugin::Charsetで設定しています。

Mojolicious::Plugin::Charset(再掲)
package Mojolicious::Plugin::Charset;
use Mojo::Base 'Mojolicious::Plugin';

# "Shut up friends. My internet browser heard us saying the word Fry and it
#  found a movie about Philip J. Fry for us.
#  It also opened my calendar to Friday and ordered me some french fries."
sub register {
  my ($self, $app, $conf) = @_;

  # Got a charset
  $conf ||= {};
  if (my $charset = $conf->{charset}) {

    # Add charset to text/html content type
    $app->types->type(html => "text/html;charset=$charset");

    # Allow defined but blank encoding to suppress unwanted
    # conversion
    my $encoding =
      defined $conf->{encoding}
      ? $conf->{encoding}
      : $conf->{charset};
    $app->renderer->encoding($encoding) if $encoding;

    # This has to be done before params are cloned
    $app->hook(after_build_tx => sub { shift->req->default_charset($charset) }
    );
  }
}

1;

pluginでencodingオプションが設定されていれば、その値が使われて、設定されていなければ、charsetオプションで設定した値が使われるようです。*1

本題に戻る

  1. 制作はUTF-8を利用し、出力時はEUC-JPを使いたい
  2. テンプレートは別ファイルに分けたい

ここで本題に戻ります。

上記の前提・課題をクリアしようと思った時に、文字化けが発生したのでした。ソースを読み解いたので、これで原因がわかるはずです。

charsetにEUC-JPを設定し、テンプレートはutf8を使った場合
plugin Charset => { charset => 'EUC-JP' }

アプリケーションの実行ファイルの上記述によって、以下のような設定になるはずです。

  1. HTMLのMIMEタイプの文字コードEUC-JP
  2. Mojolicious::Rendererのencoding属性の文字コードEUC-JP
  3. テンプレートはUTF-8

Mojo::Templateのrender_fileメソッドで読んだように、Mojolicious::Rendererのencoding属性の文字コードで、テンプレートファイルがデコードされていました。そうすると、UTF-8のファイルをEUC-JPでデコードしちゃいますね。これは文字化けになるはずです。

Mojolicious::Plugin::Charset(再掲)

    my $encoding =
      defined $conf->{encoding}
      ? $conf->{encoding}
      : $conf->{charset};
    $app->renderer->encoding($encoding) if $encoding;

隠しオプション的ですが、以下のようにすれば、EUC-JPでデコードしてしまう事態は防げそうです。

plugin Charset => { charset => 'EUC-JP', encoding => 'UTF-8' };

Mojolicious::Plugin::Charsetのencodingオプションの値が未設定の時、Mojolicious::Rendererのencodingの値がcharsetと同じになってしまうことが問題だからです。

ただ、これにも落とし穴がありました。

Mojolicious::Renderer(再掲)
  # Encoding (JSON is already encoded)
  unless ($partial) {
    my $encoding = $options->{encoding};
    encode $encoding, $output if $encoding && $output && !$json && !$data;
  }

  return $output, $c->app->types->type($format) || 'text/plain';

Mojolicious::Rendererのソースで、Mojolicious::Rendererのencoding属性の値を使ってencodeしています。

Mojolicious::Plugin::CharsetでencodingオプションにUTF-8を設定することで、UTF-8のテンプレートをEUC-JPでデコードしてしまうことは防げますが、このままではUTF-8でデコードされたテンプレートをそのままUTF-8でencodeしてしまって、htmlのMIMEタイプの文字コードEUC-JP)との不一致が起こってしまいます。

ここまでの結論

  1. 制作はUTF-8を利用し、出力時はEUC-JPを使いたい
  2. テンプレートは別ファイルに分けたい

以上を満たすためには、既存のソースを修正するか、既存のソースを動的に書き換えるしか方法がなさそうです。

一番簡単な解決方法

Mojolicious::Controllerのrenderメソッドの内部の動きが問題になっているので、Mojolicious::Controllerのrenderメソッドをそのままコピペして、一部修正することで問題解決できます。

app.pl
#!/usr/bin/env perl
use utf8;
use Mojolicious::Lite;
use Encode;

plugin Charset => { charset => 'EUC-JP', encoding => 'UTF-8' };

get '/welcome' => sub {
  my $self = shift;
  ## ここからMojolicious::Conrollerのrenderメソッドからのコピペ&修正
  # Recursion
  my $stash = $self->stash;
  if ($stash->{'mojo.rendering'}) {
    $self->app->log->debug(qq/Can't render in "before_render" hook./);
    return '';
  }

  # Template may be first argument
  my $template;
  $template = shift if @_ % 2 && !ref $_[0];
  my $args = ref $_[0] ? $_[0] : {@_};

  # Template
  $args->{template} = $template if $template;
  unless ($stash->{template} || $args->{template}) {

    # Default template
    my $controller = $args->{controller} || $stash->{controller};
    my $action     = $args->{action}     || $stash->{action};

    # Normal default template
    if ($controller && $action) {
      $self->stash->{template} = join('/', split(/-/, $controller), $action);
    }

    # Try the route name if we don't have controller and action
    elsif ($self->match && $self->match->endpoint) {
      $self->stash->{template} = $self->match->endpoint->name;
    }
  }

  # Render
  my $app = $self->app;
  {
    local $stash->{'mojo.rendering'} = 1;
    $app->plugins->run_hook_reverse(before_render => $self, $args);
  }
  my ($output, $type) = $app->renderer->render($self, $args);
  return unless defined $output;
  return $output if $args->{partial};

  ################### 以下の行を追加 ###################
  $output = encode('EUC-JP', decode('UTF-8', $output));
  ################### 以上の行を追加 ###################

  # Prepare response
  my $res = $self->res;
  $res->body($output) unless $res->body;
  my $headers = $res->headers;
  $headers->content_type($type) unless $headers->content_type;
  $self->rendered($stash->{status});

  return 1;
  ## ここまでMojolicious::Conrollerのrenderメソッドからのコピペ&修正
};

app->start;

UTF-8エンコードされてしまった$outputを再び、UTF-8でデコードし直して、改めてEUC-JPでエンコードしています。
ただ、これを各ルートパターンにこのコードを埋め込むのはどう考えても実用的ではありません。

パッチ的に動作を変更する

Sub::Installを使ってパッチ的に動作を変更してみます。

app.pl
#!/usr/bin/env perl
use utf8;
use Mojolicious::Lite;
use Encode;
use Sub::Install;

plugin Charset => { charset => 'EUC-JP', encoding => 'UTF-8' };

Sub::Install::reinstall_sub({
  code => sub {
    my $self = shift;
  
    ################### 中略 ###################
  
    my ($output, $type) = $app->renderer->render($self, $args);
    return unless defined $output;
    return $output if $args->{partial};

    ################### 以下の行を追加 ###################
    $output = encode($self->req->default_charset, decode($app->renderer->encoding, $output));
    ################### 以上の行を追加 ###################
  
    # Prepare response
    my $res = $self->res;
    $res->body($output) unless $res->body;
    my $headers = $res->headers;
    $headers->content_type($type) unless $headers->content_type;
    $self->rendered($stash->{status});
  
    return 1;
  },
  into => 'Mojolicious::Controller',
  as => 'render',
});

get '/welcome' => sub {
  my $self = shift;
  $self->render('welcome');
};

app->start;

これでもOKですが、Mojo::Templateでデコードして、Mojolicious::Rendererでエンコードして、Mojolicious::Controllerでデコードしてエンコードすることになるので、エンコードとデコードを無駄に2回呼び出すことになってしまいます。ということで、Mojolicious::Rendererのrenderメソッドを上書きすることにしました。

app.pl

#!/usr/bin/env perl
use utf8;
use Mojolicious::Lite;
use Mojo::ByteStream 'b'; # Mojolicious::Rendererのrenderメソッドで使うので読み込む
use Mojo::Util 'encode';  # Mojolicious::Rendererのrenderメソッドで使うので読み込む
use Sub::Install;

plugin Charset => { charset => 'EUC-JP', encoding => 'UTF-8' };

Sub::Install::reinstall_sub({
  code => sub {
    my ($self, $c, $args) = @_;

    ################### 中略 ###################

    # Encoding (JSON is already encoded)
    unless ($partial) {
      my $encoding = $c->req->default_charset; # この行を変更
      encode $encoding, $output if $encoding && $output && !$json && !$data;
    }
  
    return $output, $c->app->types->type($format) || 'text/plain';
  },
  into => 'Mojolicious::Renderer',
  as => 'render',
});

get '/welcome' => sub {
  my $self = shift;
  $self->render('welcome');
};

app->start;

実行ファイルに置いたままだと見通しが悪いので、このパッチをプラグインにまとめちゃいます。基本はMojolicious::Plugin::Charsetのを継承してSub::Installの箇所以外は親に丸投げする感じでOKかと。デフォで入っていますし。

app.pl
#!/usr/bin/env perl
use utf8;
use Mojolicious::Lite;
use lib 'lib';

plugin MyCharset => { charset => 'EUC-JP', encoding => 'UTF-8' };

get '/welcome' => sub {
  my $self = shift;
  $self->render('welcome');
};

app->start;
Mojolicious::Plugin::MyCharset
package Mojolicious::Plugin::MyCharset;
use base qw(Mojolicious::Plugin::Charset);
use strict;
use warnings;
use Mojo::Base 'Mojolicious::Plugin';
use Mojo::ByteStream 'b';
use Mojo::Util 'encode';
use Sub::Install;

Sub::Install::reinstall_sub({
  code => sub {
    my ($self, $c, $args) = @_;

    ################### 中略 ###################

    # Encoding (JSON is already encoded)
    unless ($partial) {
      my $encoding = $c->req->default_charset; # この行を変更
      encode $encoding, $output if $encoding && $output && !$json && !$data;
    }
  
    return $output, $c->app->types->type($format) || 'text/plain';
  },
  into => 'Mojolicious::Renderer',
  as => 'render',
});

1;
################### 以下略 ###################

これで解決。もっといい方法があるのかもしれませんが。。。

この作業を通して学んだこと

  1. 簡単な仕組みを利用する裏側には複雑な仕組みが動いている。
  2. ソースコードは目的意識を持って読もうとすればなんとか読める。
  3. コメントとドキュメント重要。
  4. Sub::Install便利。既存モジュールの動作の仕組みを調べるのにも使える。
  5. 問題を解決するよりも、問題の原因を突き止めるほうが数倍時間がかかる場合がある。
  6. perldoc -m MODULEのお陰でソースコードリーディングが捗る。*2
  7. MojoliciousのRenderer周りの動きがなんとなく分かった。
  8. オレは『納得』したいだけだ!『納得』は全てに優先するぜッ!! でないとオレは『前』へ進めねぇッ!『どこへ』も!『未来』への道も!探す事は出来ねえッ!!は、やっぱり名言

次の記事はYAPC参加報告かなー。

おすすめ書籍

Perl CPANモジュールガイド

Perl CPANモジュールガイド

追記

一応、githubに置いた。削除した。

*1:PODにencoding属性のこと一言も書いてないのに...

*2:[https://github.com/thinca/vim-ref:title=vim-ref:bookmark]使えば特に便利

Perlを使っています

今の会社は技術を担当する人がいないので、自分がサーバーサイドの面倒を見ているのですが、技術の上司もいなければ、同僚もいないし、後輩もいないということで、どんな技術を使うかは自分次第という環境です。

そんな中、自分はPerlを選びました。

その理由は以下のとおりです。

  1. 最初に触れたプログラミング言語なので書籍をたくさん持っている。
  2. ドキュメントのインターフェイスが統一されていて(perldoc)、かつ、そのことを知っていて、すでに使えていた。
  3. はてなブックマークなどでPerlの話題が拾われやすい気がする。(はてながPerl中心の会社だし)
  4. 自分でアルゴリズムを作るとバグが混入するリスクが高まることを認識したので(経験不足に起因して、起こりうる事象を把握できていないし、見えてないことが多すぎる)、極力、よくテストされたモジュールを使う方針になった。その時に、CPANがものすごく役立つ。
  5. Perl界隈で有名な人、有名な会社を他の言語に比べて知っているので、その人たちの作っているモジュールやオススメしているモジュールから何を使うべきかを検討しやすい。
  6. ある事柄に対して、同じ説明がウェブ上にいくつ転がっていても、誰の書いた説明を優先して読むべきかの判断がしやすい。
  7. Perl界隈の人達はインフラの知識も豊富な方が多く(そんなイメージ)、インフラに関しても頼りにできる。
  8. なんだかんだで、浅いけど、一番長く触れている言語であると思う。

Wordpressを使う案件などではPHPを使ったりするので例外はありますが、基本的にサーバーサイドは上記の理由*1もあってPerlを使う流れにしています。

Rubyにも興味があるけど、ほとんどそれでモノを作ったことがないので、すべて自分で解決しなきゃいけない今の状況だとたぶん、時間がかかりすぎます。

ソーシャルサービスと同じで、プログラミング言語も、そこに誰がいるかってのが重要だなと思います。PerlCPAN Authorが尊敬の対象だし、人が見える。

現状、自分自身が「ある技術に詳しい人」ではないのですが、「ある技術に詳しい人をなんとなく知っている」ので、それが自分に安心感を与えていることは確か。いずれは「ある技術に詳しい人」というものになりたいとも思います。今は実務を通して技術・知識共に不足していることを痛感しているので、マジ頑張らないといけない。

*1:内容がフワフワしてますが