« 2005年12月 | メイン | 2006年02月 »

2006年01月31日

CGI::Application::HTTP

以前作ったCGIAppベースのApplicationをStandAloneで動かすCGI::Application::HTTPを
http://nomadscafe.jp/archives/CGI-Application-HTTP-0.01.tar.gz
に置いておいてみた。

Catalystのコンポーネントの読み込み

Catalystのコンポーネントの読み込みのコード(setup_components)ってけっこう凄くないっすか?

    eval "package $class;\n" . q!Module::Pluggable::Fast->import(
            name   => '_catalyst_components',
            search => [
                "$class\::Controller", "$class\::C",
                "$class\::Model",      "$class\::M",
                "$class\::View",       "$class\::V"
            ],
            callback => $callback
        );
    !;


eval 式;をつかって、現在のpackage宣言しつつ、Module::Pluggable::Fastを動かす。
tricky杉。

2006年01月30日

Catalystのclassとinstance

CatalystをデバッグOnで起動したときに最初の方にでてくる

.-------------------------------------------------------------------+----------.
| Class                                                             | Type     |
+-------------------------------------------------------------------+----------+
| Traba::Model::DBISweet                                            | class    |
| Traba::Model::Trackbacks                                          | class    |
| Traba::Model::URLBL                                               | instance |
| Traba::View::RSS                                                  | instance |
| Traba::View::TT                                                   | instance |
'-------------------------------------------------------------------+----------'

これの右側のTypeがあるんだけど、この欄のclassかinstanceになるかは、Catalystの起動時のsetup_componentsで判定される。
判定に使われるのは、COMPONENTというクラスメソッド。ちなみに、上は暦トラバのもの。

Catalyst::Componentにはそれが実装されてる。中身的には、configをまとめてコンストラクタのnewを呼び出すというコード。

sub COMPONENT {
    my ( $self, $c ) = @_;
    # Temporary fix, some components does not pass context to constructor
    my $arguments = ( ref( $_[-1] ) eq 'HASH' ) ? $_[-1] : {};
    if ( my $new = $self->NEXT::COMPONENT( $c, $arguments ) ) {
        return $new;
    }
    else {
        if ( my $new = $self->new( $c, $arguments ) ) {
            return $new;
        }
        else {
            my $class = ref $self || $self;
            my $new = { %{ $self->config }, %{$arguments} };
            return bless $new, $class;
        }
    }
}

ということで、Catalyst::ComponentをベースとするModel/Viewは(おかしい事をしないかぎり)必ずinstanceになる。

上のリストにある、Traba::Model::URLBLはTrackbackスパムのチェックをするクラスで、id:miyagawaのKwiki::URLBLを参考にして、以下のような感じで実装されている。

package Traba::Model::URLBL;

use strict;
use warnings;
use base 'Catalyst::Model';
use NEXT;
use Net::DNS::Resolver;
use URI;

sub new {
    my($class, $c, $config) = @_;
    my $self = $class->NEXT::new($c,$config);
    $config->{urlbl_dns} ||= [qw/sc.surbl.org bsb.spamlookup.net rbl.bulkfeeds.jp/];
    $self->config($config);
    return $self;
}

sub is_blocked {
    my($self, $url) = @_;
    my $uri = URI->new($url);
    my $domain = $uri->host;
    $domain =~ s/^www\.//;
    my $res   = Net::DNS::Resolver->new;
    for my $dns (@{$self->config->{urlbl_dns}}){
	    my $q = $res->search("$domain.$dns");
        return 1 if $q && $q->answer;
    }
    return;
}
1;

Catalyst's ACCEPT_CONTEXT

Catalyst 5.61からmodelなどのcompornentを呼び出すところに、ACCEPT_CONTEXTなるものがある。
compornentの呼び出しはいつからか、

$c->model('MyModel')->foo;
$c->view('MyView')->bar;

って方法が使えるようになってる。

現在開発中のコードでは、

my $comp = $c->components->{$try};
if ( eval { $comp->can('ACCEPT_CONTEXT'); } ) {
     return $comp->ACCEPT_CONTEXT($c);
}
else { return $comp }

というようになっていて、インスタンスでもクラスでも目的のmethodを呼び出す前にACCEPT_CONTEXTがあれば、ACCEPT_CONTEXTを引数$cで呼び出してくれる。このcontextオブジェクトをクラス側で保持しておけば、Catalyst::Compornentをベースとしていないクラスでも$cが使えるいうわけっす。

ってことで、Class::DBIのプラグインを簡単につくってみた。ただし現在のCatalystのバージョンでは動かない。

package Class::DBI::Plugin::AcceptCatalyst;
use strict;
use warnings;
our $VERSION = '0.01';
sub import {
    my $caller = caller();
    no strict 'refs';
    $caller->mk_classdata('catalyst_context');  	
    *{"$caller\::ACCEPT_CONTEXT"} = sub {
        my ($class, $c) = @_;
        $class->catalyst_context($c);
        return $class;
	};
}


これをロードしておけば、Class::DBIベースのクラスの中でCatalystのContextが使える、ハズ。before_hoge、after_hogeとかのTriggerでこれを利用してLogを取るなんてこともできるんじゃないかな。
ただ、このコードだとメモリリークする可能性があるので、もう一工夫いるかな。

YAML::SyckにConfigが変わるらしい5.64はいつになるだろう。

2006年01月27日

Ittec項目にApplicationsのエイリアスを追加して右クリックランチャー

GoodPic.comでMac OSX でファイルブラウジングを快適に。DocにHDDを追加と紹介されています。関連したTipsのようなもの。

Tigerに対応してくれないIttecを会社Macでは使っているのですが、Ittec項目(Library下)というところにアプリケーションへのエイリアス等をいれてランチャー代わりにつかえます。僕は、そこにApplicationsのエイリアスもいれてます。

ittecapplications.jpg

ショートカットを整頓してなくても、全部のアプリケーションへアクセスできて便利です。
FinderPopでできるかどうかは試してないです。

2006年01月26日

カレンダーにトラックバックを送る

ブログ人マップとかlivedoor MAPなど、地図にTrackbackを送ることができるサービスはいくつかあります。

WhereがあるならWhenもいけるんじゃないかと思って、カレンダーにTrackbackを送信することができるサービスを作ってみました。

暦トラバ(beta)
http://traba.nomadscafe.jp/

カレンダー・トラックバックセンターとでも言えばいいでしょうか。カレンダーの日付に対してTrackbackできます。受信したTrackbackは一覧表示されてRSSフィードなども配信してます。
イベント情報とか集まったらモノになると思います。

koyomitraba.jpg

さらにMAPと組み合わせられたら面白いかなぁというのも考え中。

mac-key-mode

CarbonEmacsでmac-key-mode使ったら駄目ですかね。

Command-c Command-v、Command-↓での文末移動とかの癖が強い。
mac-key-modeをいれると、Command-xがM-xにならないのがあれだ。

textmateはフォルダを俯瞰できるのがいい。
emacsは日本語が問題ないのがいいですね。

Text::Hatena 0.07

Text::Hatenaがバージョンアップで0.07。
お願いしていたHTML::Parserのprereqのバージョンが下がったのと、Text::Hatena::AutoLinkが増えて、自動リンクに対応してる。

[http://www.hatena.ne.jp/:title]
mailto:someone@example.com
asin:4798110523
[tex:x^2+y^2=z^2]
d:id:kazeburo

みたいなリンクも動く(らしい)

モジュール内部で、Net::AmazonやURI::Titleを使って情報を取りに行ったり、Texの場合ははてなの画像生成URIにリンクしていたりと、サーバのリソース(はてなのリソースも)を結構食います。Femoだと表示都度で変換しているんだけど、そのままでは導入が難しいなぁ。
だけど、このあたりをキャッシュする等をすればそのままはてなができそうな具合になってきてます。

そうそう、AWSのtokenとかアフィリエイトIDがモジュールにそのまま書かれているんだけど、使う時はもちろんOverrideしていいんですよね(w

2006年01月24日

TextMateがすばらしく良い件

MacのエディターはJeditをずっと使ってきているんですが、emacsに触れつつ、TextMateがかなりいい感じです。

textmate.jpg

背景がWinXPみたいですが、Macです。
Rails関係でよく使われているようで、Rubyとかprototype.jsとかのモードが充実してます。

問題としては日本語が通らない事。日本語を打つとそれっきり入力を一切受け付けなくなります。
templateの編集は今まで通りやりつつ、プログラミングはTextMateに移動、、、したいところ。

2006年01月21日

Perlにおける真偽

おいぬま日報(不定期)さんの記事で、

でもこれは下記のように書くことが出来ます。
sub func {
my %args = @_;
my $hoge = $args{hoge} || 'fuga';
# なんか処理
}
なんとこれを知っているだけで3行もコードが節約出来るんです。

とあって、短く書けてエレガントです。

これだけでも97%ぐらいはかまわないのですが、使い続けるとはまりどころがあります。

&func(hoge=>0);
&func(hoge=>"");

として、$hogeに0や""を入れたい場合がそれ。
なぜなら、0や""がブール値コンテキストにおいて、偽となるからです。

Perlにおける真偽の規則は、らくだ本から引用ですが、

1. ""と"0"を除き、すべての文字列は真である
2. 0を除き、すべての数値は真である
3. すべてのリファレンスは真である
4. すべての未定義値は偽である

となってます。なので、$hogeに""や0を入れたくて、&func(hoge=>0);としたとしても

my $hoge = $args{hoge} || 'fuga';

において、$args{hoge}が偽と判断され、'fuga'になってしまいます。
これに対応するならば、definedを使って

my $hoge = (defined $args{hoge}) ? $args{hoge} : 'fuga';

などとすると、希望通りに動くようになります。
$args{hoge}が2回あるので、ちょっとかっこわるいですがね。


↓最近ようやく買ったのですが、やはり勉強になります。

プログラミングPerl〈VOLUME1〉
ラリー ウォール ジョン オーワント トム クリスチャンセン Larry Wall Jon Orwant Tom Christiansen 近藤 嘉雪
オライリー・ジャパン (2002/09)
売り上げランキング: 28,553


それとかっこいいコードを書くなら、id:naoya氏監修の

Perlプログラミング救命病棟
ピーター・J・スコット トップスタジオ 伊藤 直也
翔泳社 (2005/09/06)
売り上げランキング: 7,023


がおすすめ。

XML::FeedとAtom 1.0

いまさら気付いたんだけど、XML::FeedってAtom 1.0に対応していないのね。。
FeedとEntryのissuedとmodifiedが、Atom 1.0ではpublishedとupdatedになっているので、Atomのバージョンを見ながら参照先を変えるとかが必要になると思われ。

feed.nomadscafe.jpの方では、無理矢理対応した。カテゴリーへの対応だけがまだできてない。
Modelクラスの部分を↓だんだんBKの固まりになってきそう。

use XML::Atom::Util qw( iso2dt );
sub process{
    my ( $self, $c ) = @_;
    my %ret;    
    local $XML::Feed::RSS::PREFERRED_PARSER = "XML::RSS::LibXML";
    my $feed = XML::Feed->parse(URI->new($c->stash->{uri}));
    if(!$feed){
        $c->stash->{errstr} = XML::Feed->errstr;
        return
    }
    my $pf = DateTime::Format::Mail->new();    
    my $is_atom10 = ($feed->{atom} && $feed->{atom}->version > 0.3) ? 1 : 0;
    
    my %feed;
    $feed{title} = $feed->title;
    $feed{link} = $feed->link;
    $feed{description} = ($is_atom10) ? $feed->{atom}->subtitle :  $feed->description;    
    $feed{author} = $feed->author;
    $feed{language} = $feed->language;
    $feed{copyright} = ($is_atom10) ? $feed->{atom}->{rights} : $feed->copyright;
    if($is_atom10){
        $feed{modified} = $feed->{atom}->updated ? $pf->format_datetime(iso2dt($feed->{atom}->updated)) : undef;
    }else{
        $feed{modified} = $feed->modified ? $pf->format_datetime($feed->modified) : undef;
    }
    $feed{generator} = (ref $feed->generator) ? $feed->generator->{resource} : $feed->generator;
    foreach (qw(title link description author language copyright modified generator)){
        utf8::decode($feed{$_}) unless utf8::is_utf8($feed{$_});
    }    
    $feed{entries}=[];
    for my $entry ($feed->entries) {
        my %entry;
        $entry{title} = $entry->title;
        $entry{link} = $entry->link;
        $entry{summary} = ($entry->summary) ? $entry->summary->body : undef;
        $entry{category} = $entry->category;
        $entry{author} = $entry->author;
        #issued,modified
        if($is_atom10){
            $entry{issued} = $entry->{entry}->published ? $pf->format_datetime(iso2dt($entry->{entry}->published)) : undef;
            $entry{modified} = $entry->{entry}->updated ? $pf->format_datetime(iso2dt($entry->{entry}->updated)) : undef;
        }else{
            $entry{issued} = $entry->issued ? $pf->format_datetime($entry->issued) : undef;
            $entry{modified} = $entry->modified ? $pf->format_datetime($entry->modified) : undef;            
        }
        foreach (qw(title link summary category author issued modified)){
            utf8::decode($entry{$_}) unless utf8::is_utf8($entry{$_});
        }
        push(@{$feed{entries}},\%entry);
    }    
    $ret{$c->stash->{uri}} = \%feed;
    $c->stash->{feed} = \%ret;
}


feed.nomadscafe.jpもバージョンあがって、0.04。ソースは
http://nomadscafe.jp/archives/
においてあります。

2006年01月18日

Feed2JSON(feed.nomadscafe.jp)のソースコード

昨日エントリしたFeed2JSONのfeed.nomadscafe.jpのソースコードを

http://nomadscafe.jp/archives/

に置いておきました。

DL&展開して、できたディレクトリに入って、

./script/feeder_server.pl -r

と打って内蔵サーバがport 3000で起動できます。
以下なモジュールが入っていることが必要です。

  • Catalyst => 5.60以上
  • JSON::Syck => 0.05以上
  • Catalyst::View::JSON => 0.08以上
  • YAML::Syck => 0.28以上
  • Catalyst::View::TT => 指定なし
  • XML::Feed => 0.07以上
  • DateTime::Format::Mail => 指定なし
  • MIME::Base64 => 指定なし


ライセンスはPerlと同じで。

2006年01月17日

オオタ ユキ

ちょっと久しぶりにCD購入

マングローブ
マングローブ
posted with amazlet on 06.01.17
オオタ ユキ
インディペンデントレーベル (2005/12/21)


最近Happinessが多いなぁ。
SaigenjiとかQypthoneとかいい感じでおすすめ。

Feed2JSON

Feed2JSONをつくってみた。
Feed2JSONは、RSS/AtomをJSONに変換するようなサービスです。rss2jsonの方が一般的。

http://feed.nomadscafe.jp/

Catalystで動いていて、指定されたURIをXML::Feedで取得、Catalyst::View::JSONにjson_driver=>'JSON::Syck'でforwardしているだけ。

このBlog AtomのJSONを取得するには
http://feed.nomadscafe.jp/json/http://blog.nomadscafe.jp/atom.xml.js
となる。最後の.jsはIE対策

ついでにYAMLでも出力できる。こちらはYAML::Syckを利用
http://feed.nomadscafe.jp/yaml/http://blog.nomadscafe.jp/atom.xml

なにかJavaScriptのDemoめいたものを作るときに役に立つかも。

2006年01月16日

JSON::Syckでundef⇒nullへの変換がほしい

さきほど、JSON::Syck 0.04がでてましたが、
JSON::Syckで一部気になるところが。

use JSON::Syck;
my $ref ={foo=>undef};
print JSON::Syck::Dump($ref);

こんな感じにしたときに、出力されるのは、

{"foo":}

になります。これだと、JavaScriptのエラーになることがありますね。

できれば、JSON.pmと同じく

{"foo":null}

と、出てほしいところ。


JSON::Syckのコードとか全然読めない。勉強不足だなぁ。

2006年01月12日

Safari 2.0.3 supports setSelectionRange

Mac OS X 10.4.4に含まれるSafari 2.0.3でFirefoxと同じく、setSelectionRange、selectionStart、selectionEndがサポートされたようです。

<input type="text" id="textfield" name="textfield" value="foo"/>
<script type="text/javascript">
var ele = document.getElementById('textfield');
for(i in ele){
    document.write(i + " = " + ele[i] + "<br />");
}
</script>


とした、結果の中に

setSelectionRange = [function]
selectionStart = 3
selectionEnd = 3

と入ってます。

見ていると、contentEditableなんていうのもあって、

<div contenteditable="true">
この文章はブラウザ上で編集できます。
</div>

というのも動く。編集ができちゃう。

↓お試し用

IEや最新のSafariの場合、この文章はクリックすると編集できます。


前から動いたっけ??

Safari 2.0.3でtextbox中のキャレットの位置の処理が変わった?

昨日のMacのアップデートでSafariが2.0.3(もしくは1.3.2)になったわけだが、ちょっと気になった点が。

<input type="text" name="textfield" onfocus="this.value=this.value+'foo'"/>

というtextboxを作って、textboxをクリックすると、textboxに「foo」と入ります。
一度フォーカスを外して再度クリックすると「foofoo」になると思います。繰り返すと「foo」が増えていきます。

このときのキャレット(文字入力ポインタ)の位置が、firefoxやいままでのSafariなら、必ず最後に来ていたのですが、Safari 2.0.3(1.3.2)だと、Textbox上のクリックしたあたりにキャレットがつきます。

これ、何が問題なのかというと、「Tag入力Suggestテスト」のエントリで書いた調整中の
FemoのTag Suggest Test (http://autocomptest.nomadscafe.jp/)
でTagを候補の中から決定したときにキャレット位置が最後にいかないところにあります。
これでは、次のTagを入力しようとするときに、キャレットを矢印キーやマウスで動かさないとだめです。
う〜ん、不便。

OperaもこのSafariと同じ仕様ぽい。


追記:
del.icio.usのsuggestがうまく動くのでしらべると、どうもSafariでもsetSelectionRangeがうまく動くようだ。

if (this.tagText.setSelectionRange){
    //this.tagTextはtextboxエレメント
    this.tagText.setSelectionRange(this.tagText.value.length,this.tagText.value.length);
}

でキャレットを最後に持っていける。

2006年01月10日

JSONとContent-Type

サーバサイドからJSONを吐き出すときのContent-Typeなのですが、各ブラウザによって対応がちょっと違います。
下の表にまとめてみました。

×のところはeval中にエラーがでます。

Content-type WinIE Firefox Safari Opera(8.5)
text/javascript ×
text/javascript; charset=utf-8 ×
text/javascript; charset=utf8(utf-8の間違い) × ×
text/javascript+json ×
text/javascript+json; charset=utf-8 ×
text/html; charset=utf-8      


Safariでマルチバイトな文字を含む場合は、「charset=utf-8」があったほうがいいです。

Opera(8.5)はtext/javascriptだと根本的にだめみたいです。
Catalyst::View::JSONは「text/javascript+json」とかなっているんだけどどうするのが良いのでしょう。

さっきまで、間違えて「utf8」って書いてしまっていたんだけど、IE以外はうまく認識してくれますね。


確認は下のようなコードで行いました。prototype.jsのAjax.Requestをつかってます。

new Ajax.Request(
    "/foo",{
    method: "get",
    parameters: params,
    onComplete: function(originalRequest){
        try{
            eval("var ret="+originalRequest.responseText);
        }catch(e){alert(e);}
    }
});

クロージャの使用例 in Text::Hatena

id:naoya氏がclosureのpracticeをいくつか揚げていて非常に勉強になります。

ちょっと思いだしたのは、Text::Hatenaに含まれるText::Hatena::HTMLFilterで、HTML::Parser

$self->{parser} = HTML::Parser->new(
	api_version => 3,
	handlers => {
	    start => [$self->starthandler, 'tagname, attr, text'],
	    end => [$self->endhandler, 'tagname, text'],
	    text => [$self->texthandler, 'text'],
	    comment => [$self->commenthandler, 'text'],
	},
    );


こんな感じで呼び出しています。
各handlerがどうなっているのかと言えば、

sub texthandler {
    my $self = shift;
    return sub {
	my $text = shift;
	$text = &{$self->{context}->texthandler}($text, $self->{context});
	$self->{html} .= $text;
    }
}

のようになっていて、「$self」を保持しつつ無名サブルーチンを返しています。

実はこの方法を最近まで知らなくて、クロージャも理解してなく、HTML::Parserを継承してパーサクラスを書いてますた。ようやくきちんと理解できた気がする。

ところで、この連休はJavaScript(prototype.js有り)をずっと書いていたおかげで、

sub parser{
    $self->{parser} = HTML::Parser->new(
	api_version => 3,
	handlers => {
	    start => [$self->starthandler->bind($self), 'tagname, attr, text'],
	    end => [$self->endhandler->bind($self), 'tagname, text'],
	    text => [$self->texthandler->bind($self), 'text'],
	    comment => [$self->commenthandler->bind($self), 'text'],
	},
    );
}

sub texthandler {
    my $self = shift;
    my $text = shift;
    $text = &{$self->{context}->texthandler}($text, $self->{context});
    $self->{html} .= $text;
}

と書けたらいいかもとか少し思った。

2006年01月09日

Form.Element.Observerの自動停止

Form.Element.Observerはprototype.jsが提供する機能で、指定した間隔でフィールドをチェックして内容に変化があればcallbackを実行してくれるものです。
一つ前のエントリーで書いた「Tag入力Suggestテスト」でTagの入力の捕捉に利用してます。

Form.Element.Observerでちょっと気になるのは、指定した間隔でフィールドをチェックするTimerの停止ができないところです。Femoだとフォームが表示されたり消えたりとするので、timerを停止できたほうがCPUやメモリーにやさしいと思われ。

Timerのセットは、Form.Element.Observerの上位クラスであるAbstract.TimedObserverで

registerCallback: function() {
    setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
},

こんなように書かれています。intervalを呼び出す機能しかありません。
なので、この辺りをすこし書き加えて、確認対象のフィールドがなくなったらclearintervalされるようにしました。

Form.Element.Observer.prototype.registerCallback=function(){
    this.interval = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
};
Form.Element.Observer.prototype.clearTimerEvent=function(){
    clearInterval(this.interval);
};
Form.Element.Observer.prototype.onTimerEvent=function(){
    try{
        var node = this.element.parentNode.tagName;
    }catch(e){
        this.clearTimerEvent();
    }    
    var value = this.getValue();
    if (this.lastValue != value) {
        this.callback(this.element, value);
        this.lastValue = value;
    }
};

と、してみました。

確認ですが、

<div id="text1div">
Text:<input type="text" name="text1" id="text1" size="50">
</div>
<p><a href="JavaScript:void(0);" onClick="$('text1div').innerHTML='deleted'">消す</a></p>
<script type="text/javascript">
new Form.Element.Observer($('text1'),1,function(){alert('foo')});
</script>

というHTMLを用意して、ブラウザで表示。「消す」をクリック。
この状態で、

var node = this.element.parentNode.tagName;

がエラーとなってclearintervalが行われました。

動いたんだけど、

    try{
        var node = this.element.parentNode.tagName;
    }catch(e){
        this.clearTimerEvent();
    }    

これが微妙ですよね。

何かいい方法ないかなぁ。

Tag入力Suggestテスト

Femoに入れようかと思っているTag入力の補助機能のテストをしてます。

FemoのTag Suggest Test
http://autocomptest.nomadscafe.jp/

femoac.jpg

入力補助自体はよくあるものと同じで、Tagの途中まで入力すると(サーバとの通信間隔があるので1秒ほどかかりますが)候補がでてきて上下キーで選択できます。
Safariでも動きます。ただしWindowsのIEでは確認をしていません。

Tagの入力は、半角(全角でもOK)スペースで区切る方法で、Tagの中にスペースが入る場合は、「"」などで囲えば入力可能になってます。
現在の仕組みでは入力したTagのパースをサーバ側で行ってます。FastCGIで動いているのでそれなりにさくさくと使えると思います。JavaScriptの正規表現だと厳しい。

何か気付いた点などご意見いただけたらうれしいっす。


どうやらWinIEで動かないらしい。現在原因究明中です。
WinIEでも動いた。Opera(8.5)は微妙です。

2006年01月06日

CGI::Application::Dispatch v2.00_01

CGI::ApplicationのMLでCGI::Application::Dispatchのv2.00_01がでてます

svnで取得できます。
svn://svn.cromedome.net/CGI-Application-Dispatch

新しいdispatch tableがいい感じ。
今までは、PATH_INFOが

/Module/Runmode

で固定でしたが、これをいじる事ができます。

dispatchのカスタマイズをするにはCGI::Application::Dispatchを継承してdispatch_argsをoverrideします。

package MyApp::Dispatch;
use base 'CGI::Application::Dispatch';

sub dispatch_args {
    return {
        prefix  => 'MyApp',
        table   => [
            '' => { app => 'Welcome', rm => 'start' },
            :app/:rm' => { },
            'admin/:app/:rm' => { prefix => 'MyApp::Admin' },
        ],
    };
}

package main;
MyApp::Dispatch->dispatch;


tableの中身は

PATH_INFO => Local設定のhashref

の配列です。PATH_INFO中で
:app = Module
:rm = Runmode
:foo = $self->param('foo')で取得可能
になります。Local設定では、prefixやModule、Runmodeを指定可能っす。

これは結構便利なんじゃないでしょうか。

2006年01月05日

Catalyst::View::JSON

miyagawaさんのCatalyst::View::JSONを試し中。

sub foo : Local {
    my ($self,$c) = @_;
    $c->stash->{tags}=[qw/foo bar baz/];
    $c->forward('View::JSON');
}

として、prototype.jsのAjax.Requestなどで、

new Ajax.Request(
    "/foo",{
    onComplete: function(originalRequest){
        var ret = eval(originalRequest.responseText);
        alert(Object.inspect(ret['tags']));
    }
});

こうすると、きちんと

['foo','bar','baz']

と得られるはず。Object.inspectもData::Dumperみたいで便利。

日本語対策というかSafari対策としては、

$c->res->content_type('text/javascript+json; charset=utf-8');

とするのがいいと思う。

2006年01月04日

Femoで全角スペースでのタグの切り分けをサポートしました。

Femoで全角スペースでのタグの切り分けをサポートしました。

全角 スペース

というように全角で区切っても 全角 スペース の2つのタグになります。
もちろん今まで通り半角スペースでも動きますです。

FemoはTagがつけられるメモ帳Webアプリケーションです。ぜひお試しください。ご意見もくださいませ。


ちなみに、タグを切り分ける部分のPerlのコードは以下のようになってます。

my @tags;
my $tagtext = $self->tagtext;

utf8::decode($tagtext) unless utf8::is_utf8($tagtext);
my %seen;
while ($tagtext =~ /\G [\p{Zs}\t\r\n\f,]* (?:
        (") ([^"]*) (?: " | $) |
        (') ([^']*) (?: ' | $) |
        ([^\p{Zs}\t\r\n\f,]+)
    )/gx){
    my $tag = $+;
    my $is_quoted = $1 || $3;
    next unless length $tag;
        
    $tag =~ s/^[\ \t\r\n\f]+//;
    $tag =~ s/[\ \t\r\n\f]+$//;
    $tag =~ s/[\ \t\r\n\f]+/ /g;
    
    utf8::encode($tag) if utf8::is_utf8($tag);
    if(my $ds = Date::Simple->new($tag)){
        $tag = $ds->as_iso;
    }
    push @tags,$tag unless $seen{$tag}++;
}
return \@tags;

いままでText::Tagsというモジュールを使ってきましたが、今回のアップデートから独自の実装(かなりそのままコピー&ペーストですが)になりました。全角でもタグがsplitされるようになった以外はText::Tagsと互換性があります。

2006年01月03日

use utf8なら\sは全角スペースもmatchする

Femoで全角スペースでもTagのsplitができるように、と調べているのですが、

use utf8;

をしている場合、\sは全角スペースにもmatchするようです。初めて気がついた。

#!/usr/bin/perl
use strict;
use warnings;
use utf8;
binmode STDOUT => ":utf8";
my $str ="全角 ス ペ ー ス が入った テキ\tス\nト";
print join ",",split /\s/,$str;

の出力は

全角,ス,ペ,ー,ス,が入った,テキ,ス,ト

となります。


perlretut - Perl の正規表現のチュートリアルによると、これも知らなかったのですが、

\sは空白キャラクタで [\ \t\r\n\f]を表します

なのですね。

utf8の場合は、ここに書かれているutf8のGeneral Category Valuesで表現すると

\s = [\p{Zs}\t\r\n\f]

なのかなぁ、、、難しいなぁ。

Femoでタイトル順ソートをサポート

Femoにメモをタイトル順に並べ替える機能を追加しました。

femo-title-order.jpg

タイトルの並び順は、

  1. 半角数字
  2. 半角アルファベット大文字
  3. 半角アルファベット小文字
  4. 全角文字

のようになってます。