2009年05月28日

Hash::Merge 活用術

「全裸は違法だということを言われた。ええええ、そんな法律があるのか?Debugはできるようになるかもしれない。」と思っておセンチなyappoです。

hashを簡単にmergeするCPAN moduleとしてHash::Mergeがあるのは有名ですが、デフォルトだと色々頑張ってマージしちゃうので、例えばHTTP::Engine::Middlewareの使いたいMiddlewareをARRAY refで書いちゃったりして、base.yamlとproduction.yamlでmergeした時に、以下のような混ざりかたでとんでも無い目にあいます。

use strict;
use warnings;
use YAML;
use Hash::Merge;

my $base = {
    Middlewares  => [
        { module => 'HTTPSession', config => { name => '開発やらステージング用の設定だよ' } },
        { module => 'MethodOverride' }, # 全部で共通して使いたいよ
    ],
    name => 'base',
    base => 1,
};

my $production = {
    Middlewares  => [
        { module => 'HTTPSession', config => { name => '本番用の設定だよ' } },
    ],
    name => 'production',
    production => 1,
};

my $config = Hash::Merge::merge($base, $production);
print Dump($config);
---
Middlewares:
  - config:
      name: 開発やらステージング用の設定だよ
    module: HTTPSession
  - module: MethodOverride
  - config:
      name: 本番用の設定だよ
    module: HTTPSession
base: 1
name: base
production: 1

これは、マズいですね、HTTPSessionの設定を開発と本番で別けたいのにARRAYだから全部混ざっちゃいます。

そんな時はspecify_behaviorでmergeのルールを弄れるのです。左のreftype => 右のreftypeみたいな形で、左の要素がSCALARで右の要素がARRAYだったどうこうするみたいな事が出来ます。
ぶっちゃけ左の要素がHASHで右の要素がARRAYとかだいぶあり得ないので、そういった組み合わせになったらdieするとかすると混乱無いと思います。

こんかいは左右でreftypeが違うと混乱するし、ARRAY同士でmergeしないで右要素だけを生かしたいと言う要望でbehaviorを作ったです。あと、behavior作っちゃうとデフォルトの振る舞いが上書きされるのでget_behaviorで今の振る舞いを取っておいて用が済んだらset_behaviorすると良いと思います。

use strict;
use warnings;
use YAML;

use Carp;
use Hash::Merge;

my $base = {
    Middlewares  => [
        { module => 'HTTPSession', config => { name => '開発やらステージング用の設定だよ' } },
        { module => 'MethodOverride' }, # 全部で共通して使いたいよ
    ],
    name => 'base',
    base => 1,
};

my $production = {
    Middlewares  => [
        { module => 'HTTPSession', config => { name => '本番用の設定だよ' } },
        { module => 'MethodOverride' }, # array は上書きされるので、こっちにもかく
    ],
    name => 'production',
    production => 1,
};

my $old_behavior =  Hash::Merge::get_behavior;
Hash::Merge::specify_behavior({
    SCALAR => {
        SCALAR => sub { $_[1] },
        ARRAY  => sub { Carp::croak 'SCALAR and ARRAY cannot merge in config file.' },
        HASH   => sub { Carp::croak 'SCALAR and HASH cannot merge in config file.' },
    },
    ARRAY  => {
        SCALAR => sub { Carp::croak 'ARRAY and SCALAR cannot merge in config file.' },
        ARRAY  => sub { $_[1] },
        HASH   => sub { Carp::croak 'ARRAY and HASH cannot merge in config file.' },
    },
    HASH   => {
        SCALAR => sub { Carp::croak 'HASH and SCALAR cannot merge in config file.' },
        ARRAY  => sub { Carp::croak 'HASH and ARRAY cannot merge in config file.' },
        HASH   => sub { Hash::Merge::_merge_hashes( $_[0], $_[1] ) },
    },
}, 'MY_CONFIG_STRICT_MODE' );

my $config = Hash::Merge::merge($base, $production);
Hash::Merge::set_behavior( $old_behavior );

print Dump($config);
---
Middlewares:
  - config:
      name: 本番用の設定だよ
    module: HTTPSession
  - module: MethodOverride
base: 1
name: production
production: 1
素敵にマージされましたね。

Posted by Yappo at 15:26 | Comments (1) | TrackBack

2009年05月19日

HTTP::Engine is moved to GitHub

こんにちわ!gitがむづかしすぎてgitなんか滅んでしまえば良いのにと思ってる金曜日の天使ことyappoです。

表題の通り HTTP::Engine 関連のプロジェクトを github に引っ越しました。
http://github.com/http-engine
http://twitter.com/httpengine
http-engineアカウントを取ってそっちで管理する感じです。
必要な方にはコラボレータ追加したりとか良い感じで運用しようと思います。

なおHTTP::Engine 0.1.8 をshipitしました。
Any::Moose 0.08 での変更の追随や
http://example.com/?aco=tie でリクエストたときに $req->uri の中身が http://example.com?aco=tie になってしまうバグが解決されています。

次は、$req->uri->baseがInterfaceによって取れる値が違うのでこれを共通化する方向にしたりとか、良い感じでapplicationのroot pathを取れる仕組みを追加したい所ですが何校中です。

ちなみに移行にはperl製のsvn2gitをforkしてhttp://github.com/yappo/svn2git/tree/master使いました。

$ mkdir hoge
$ cd hoge
$ git init
$ svn2git --strip-tag-prefix 'release-' http://svn.example.com/some-project
$ # git remote ついか
$ git push origin master
$ git push --all
$ git push --tag
な感じで、branches/tagsも含めて全部移行できました。authorsとかのコンバートは面倒いのでやらんす。

Posted by Yappo at 17:47 | Comments (0) | TrackBack

yacc と lex で簡易言語の AcotieScript っての作ってみた

そろそろ梅雨入りするので急いでキュウリの苗植えたら安物のせいか根っこが弱っこくて少し心配な金曜日担当Yappoです。

Perlも飽きたし、ふと思う所があって、オレオレ言語を作ってみました。
http://github.com/yappo/AcotieScript/tree/master
たぶんMacでしか動かないんじゃないかなとは思ってますが、普通にmakeすればコンパイルされる筈。

サポートされているシンタックスはコメントとprintとperlにあるような文字列同士のxorです。

"hoge" ^ "ugee"
みたいのですが、これを実行すると死にます。元ネタの人が文字列xorを理解したら正しく動くなる予定です。

printは

print "hello\n"
を実行すると「おうっふー hello」とprefixにおうっふーを追加します。

AcotieScriptの最大の特徴は、コメント構文の豊富さで、次の物は全てコメントとして解釈されます。

//this is comment
#this is comment
/*this is comment*/
"""this is comment
'''this is comment
<!--this is comment-->
REM this is comment
また、実行文の後ろにも次の物はコメントになります。
    print "hello!\n" #    this is comment
    print "hello!\n" //   this is comment
    print "hello!\n" """  this is comment
    print "hello!\n" '''  this is comment
    print "hello!\n" :REM this is comment

その他構文は{}のブロックはサポートしているが意味ありません。
またrubyの用っぽく1行で1つの文になり、一つの行に複数の文を置きたければ;で区切れます。

print "hoge\n" # 1文
print "foo\n";print "bar\n" # 2文

サポートされているリテラルは文字列リテラルのみでシングルクオートとダブルクオートが使えます。
それぞれはだいたいその他の言語のような動きはします。

コマンドラインでもいけます

acotiescript -e 'print "hello\n"'

shebang使えます。

$ cat oufu.acotie 
#!./acotiescript

print "うっ!\n"
$ ./oufu.acotie 
おうっふー うっ!

今後

今のままだと100% CGIとして動作しないので、CGIとして動作するようにするかも。
構文解析した物をスタック詰んだりしないで、逐一実行するだだからifやらwhileやら作るの面倒いのでスタック型にするかなーとか
mod_acotie作ってみるのはどうか?

まとめ

perlとrubyはparserをyaccでやってトークナイザはlex使わないで自前でやってる。
PHPもyacc使ってるが何かスッゲーきたない。
Pythonは全部自前でやっててコンパクトでシンプルでソース量も少なかった。

とりあえずlex使うと楽ですね。yaccは規則がループしたりしないようにするように作らないといけないのだが、気を抜くと循環参照しだしてパースが終わんなくなる。
perl -e 'print "hello"' みたいな -e オプションを実装するのにfunopenを使って、文字列バッファをstdioインターフェィスに組み込めるような機能を関数を使ったがBSDでしか使えなっぽ。
perlとかだとトークナイザは自前なのでどうとでもなるが、lex使うとFILE *なものしかやってくれないので、char *をFILE *にしてくれる汎用的な方法ないかなーと探してる所。

あとコマンドラインオプション解析は getopt を使いました。 argv[1] がファイル名だったらそれスキップします。

そんな感じでfork歓迎push requestも歓迎です!

Posted by Yappo at 11:24 | Comments (0) | TrackBack

2009年05月09日

Iron Man Blogging Challengeに参加するよ、日本のPerlな人も参加しようよ

Perlをもっとブログの表舞台に - Iron ManコンテストとかPM 05:36 Iron Man Blogging Challengeとか見て僕も応募してみたよ!

エスペラント語だろうが地球語だろうが言語は何でも良いそうなので日本のPerl書きの皆も申し込めば良いよ!
ironman@shadowcat.co.ukにblogのurlとそのblogの詳細を送れば参加出来るみたいだよ!
僕はURLと日本の諺を添えて送っただけだけどちゃんと受理されるかな?

上手くすればmstの髪の毛の色を好きに変える権利も持てるみたいだし損する事は無いので日本のPerlの盛り上がりぶりを世界に知らしめるチャンスだから皆応募しようぜ!

Posted by Yappo at 12:41 | Comments (1) | TrackBack

2009年05月08日

虹クッキリしすぎワロタ

ギロッポンにて。

こっちは二重虹。

関連エントリ: 虹クッキリしすぎワロタ

Posted by Yappo at 18:30 | Comments (0) | TrackBack

Log::Dispatch::Screen::Color - ログに色付けるよ

Log::Dispatch::Screen::Color を shipit しました。(りぽじとりはこっち)
昨年末に空前のlog colorブームがあったのですが、最近僕もようやくLog::Dispatchをまともに使うようになったので、Log::Dispatch::Screenに色付けたくなって付けました。
うそです。hirose31さんが呟いてたので作りました。

こんなコードと

use strict;
use warnings;
use Log::Dispatch::Config;

Log::Dispatch::Config->configure('test.cfg');
my $log = Log::Dispatch::Config->instance();

$log->info('いんふぉー');
$log->error('えらーーーーーだよ');
$log->warning('warningwarningwarning');
こんなconfigで
dispatchers = screen

screen.class = Log::Dispatch::Screen::Color
screen.min_level = debug
screen.stderr = 1
screen.format = [%d] [%p] %m at %F line %L%n
こうなります

もちろんLog::Dispatch::Colorfulは知っているのですが、これを使うとLog::Dispatchのメソッドを書き換えちゃうので、やや微妙という所もありすっきり仕上げてみたしだいです。
パッチ送れって話もあるですが、$foo->debug({ foo => 'bar' })みたいな事したらDumpしてくれるようにvalidateとか変えてあって、ちょっと僕の欲しい物の方向性じゃ無さそうだという所で新たに作ったのでした。

Log::Dispatch::Colorfulとの互換性はあるので安心です。

とか書いてるうちにcharsbarさんがWin32対応書いてくれたす charsbar++

Posted by Yappo at 14:44 | Comments (0) | TrackBack

2009年05月01日

良い子のMacなPerlユーザーに送る、あなたのPerlアプリをMacアプリにする方法 (Mac版的PAR)

こんにちは!近頃咳と痰と鼻水と鼻づまりがすごく多い、金曜日の天使ことYappoです。

ちょっとしたツールをPerlで書いて、お友達に使ってもらいたいときってありますよね?普通は常識的にgithubとかのurlを教えれば良いのですが、それも出来ない人とかもいた場合が非常に面倒です。
そんな時の便利ツールとしてPlatypusがあるのは有名ですね。
Platyputsを使えば簡単にXSを含めたアプリが配布出来ますんです。

XSとかはアーキテクチャ等によって違うバイナリが吐かれてる事が知られますが、今回はあなたと同じMacOSのバージョンが入ってる事を前提にしちゃって問題無いです。
Macユーザ同士なんだからCPUのアーキテクチャは、殆どの場合は一緒だろうしOSのバージョンもLeopard使ってる前提にしちゃいましょう。

準備するもの

  • Platyputs
  • local::lib
  • あなたのアプリ

手順

まずは作業用ディレクトリでも、作りましょう。mkdir ~/yourappname-work/とかで良いでしょう。

cd ~/yourappname-work/ してから mkdir extlib します。
あなたのアプリで使ってるCPANモジュールを突っ込むのです。

mkdir -p lib/local して local::lib の lib.pm を lib/local ディレクトリの中に入れます。

以下のextlib install用のスクリプトを~/yourappname-work/の中において実行します。

use strict;
use warnings;
use lib 'lib';
use local::lib '--self-contained', 'extlib'; # miyagawaさんのアドバイスで書き換えました
use CPAN;
CPAN->shell;
もしくはlocal::libを普通にインストールして、miyagawaさんが書いたhttp://gist.github.com/104823使うのがいいかなと。

あまり良くわかってないけど、/System/Library/Perl/5.8.8以下にMacOSが添付してるモジュールが入ってるようなので、@INCをいじって標準モジュール以外はキッチリインストール出来るようにしておきますね。
で、このスクリプトはCPAN Shellになってるので、頑張ってあなたのアプリを実行するのに必要なCPANモジュールをインストールしてください。XSでも大丈夫ですが、外部ライブラリに依存するような物(TokyoCabinetなど)はちょっとやり方解らないので誰か教えてください。

今度はPlatypusがkick startするscriptを用意します。#!/usr/bin/env perl とかshebangしとくと良いでしょう。ちなみにこのスクリプトの中では@ISAを弄る必要は特に無いですが

use FindBin;
use lib ("$FindBin::Bin/remedie-git/lib", "$FindBin::Bin/remedie-git/extlib", "$FindBin::Bin/extlib/lib/perl5");
とかしてextlibへのパスを通す必要はあります。

もし、あなたのアプリが$ENV{HOME}を使うようなら

BEGIN {
    $ENV{HOME} = "$FindBin::Bin/home";
}
use Remedie::CLI::Server;

Remedie::CLI::Server->new_with_options(
    root => "$FindBin::Bin/remedie-git/root",
)->run();
とかして、使用するPATHには気を使う必要があります。 $FindBin::Binは YourApp.app/Contents/Resources が該当するため、あなたのスクリプトの使うディレクトリはアプリの外のディレクトリを使わないように気を使いましょう。

.app作る

Platypusを起動して必要な項目とか埋めたり選択して下さい。ここでは詳しく書きません。
Script Pathは、上で書いた.app用のscriptでを選択して下さい。
重要なのは左下の「Show Advanced Options」をクリックして出てくる

でして、右側の+をクリックして頑張ってCPANインストールしたextlibのパスを指定して下さい。
配布アプリの中にそのままコピーされて含まれるのです。

extlib以外の物を配布アプリに含めたい時は任意に好きなだけ追加してください。僕はremedieをgit cloneしたディレクトリを丸っと入れたりしました。

あとは「Create」して、アプリが出来るのを待ちます。

僕の場合は YourApp.app/Contents/script に実行bitが立ってなかったので、 chmod ugo+x YourApp.app/Contents/script しました。
それをやればあっという間に普通のMacのアプリで実行出来る筈です。

まとめ

MacでPARみたいにする手順をずらずら書きました。

Platypus、 local::lib、scriptで使うファイルは$FindBin::Binの中に入れるとアプリケーションの外を汚さなくてすむ。という簡単な要素で作れます。

僕もこれでRemedia.app作ったら他の人のMacでも動きました(最近のmacはlibxml2はいってるっぽ)

さぁ皆さんもPerl使って素敵なMacアプリ開発ライフを過ごしてください。Enjoy!

Posted by Yappo at 10:06 | Comments (1) | TrackBack