2011/11/20

[Perl][CPAN]動的にsubroutine呼び出しをするSub::Applyを書いてみた

動的にclass methodを呼びたい場合は割と簡単で、以下のように書ける

    package Foo;
    use strict;
    use feature 'say';
    sub new { bless {}, shift }

    sub sum {
        my $self = shift;
        my ( $x, $y ) = @_;
        say $x + $y;
    }

    package main;
    use strict;
    use feature 'say';

    my $obj  = Foo->new;
    my $func = 'sum';
    if ( $obj->can($func) ) {
        $obj->$func( 1, 2 );
    }
    else {
        say "No such method => $func";
    }

しかし、動的にsubroutineを呼びたい場合はちょっと泥臭くなる

    use strict;
    use feature 'say';

    sub sum {
        my ( $x, $y ) = @_;
        say $x + $y;
    }

    my $func = 'sum';

    {
        local $@;
        eval "$func(1,2)";
        if ($@) {
            say "Oops! $@";
        }
    }

    {
        my $code = do {
            no strict 'refs';
            *{$func}{CODE};
        };
        if ($code) {
            $code->( 1, 2 );
        }
        else {
            say "Oops! Undefined subroutine $func";
        }
    }

evalでstringを評価するか、no strict 'refs'してからsymbol tableを確認する方法があります。

速度面を考えるとevalしたくないし、かと言ってno strict 'refs'をして直接symbol tableを触りたくない。
こう言った泥臭いことはmoduleに隠蔽しちゃうのがいいんじゃないかと思ったので、Sub::Applyを書きました。

Sub::Applyの使用例

    use strict;
    use feature 'say';
    use Sub::Apply qw(apply apply_if);

    sub sum {
        my ( $x, $y ) = @_;
        say $x + $y;
    }

    my $func = 'sum';

    {
        # $funcがない場合、croak
        apply($func, 1, 2);
    }
    {
        # $funcがない場合、carp
        local $Sub::Apply::WARNING = 1;
        apply_if($func, 1, 2);
    }
    {
        # $funcがなくても何もしない
        apply_if($func, 1, 2);
    }

CPANgithubにupしてあります
現在のversion(0.04)では、printf等のcore functionは呼び出せませんので注意してください。

2011/11/19

[Perl][ORM][Teng] Tengでbulk_insertする場合はTeng::Plugin::BulkInsertを使おう (追記: bulk_insertがcoreに取り込まれました)

0.14_01からbulk_insertはcoreに取り込まれました!!

Plugin::BulkInsertはまだ残っているようですが、DEPRECATEDです。load_pluginを使わずに、そのままbulk_insertを使えます


Tengでbulk insertをしたくなったのですが、bulk_insertのmethodがないのでSQL::Makerで書いてしまいました。
後でTwitterでnekokakさんにPluginにあるよと教えていただいたので、再実装しなくて大丈夫です!

ちなみに現在(0.14)のPlugin以下はこんな感じ。

Teng/Plugin
    ├── BulkInsert.pm
    ├── Count.pm
    ├── FindOrCreate.pm
    ├── Pager
    │   └── MySQLFoundRows.pm
    ├── Pager.pm
    └── Replace.pm

使い方は簡単。Tengを継承したクラスでload_pluginを呼ぶだけです。後は普通にbulk_insertを使えます

    package MyDB;
    use strict;
    use warnings;
    use parent 'Teng';
    __PACKAGE__->load_plugin('BulkInsert');

    # in your script 
    my $teng = MyDB->new(...);
    $teng->bulk_insert(
        'tweet',
        [
            { id => 1, tweet => 'hoge' },
            { id => 2, tweet => 'fuga' }
        ]
    );

2011/11/13

[Perl][CPAN] App::pfswatch 0.07をリリースしました

変更点

--pipeオプションの追加

--pipeオプションを付けると実行コマンドのSTDINに変更されたファイル名が渡されます。
後はxargsなりで引数に変換するとかしてもらえるといいんじゃないでしょうか。

    $ pfswatch t/ --pipe --exec xargs prove -lr 

しかし、本当に同時に更新されないと複数のファイル名が渡ってこないので微妙。
実行までのintervalをオプションで付けた方がいいかも

Regexp::Assembleを削除

qr//でもいい程度しか使ってなかったので、削除しました。

[Perl][CPAN] Tweet::ToDeliciousをリリースしました

以前から作成しているTweet::ToDeliciousをCPANにuploadしました。
metacpan.orgでは確認したので、そのうち見えるようになるかと思います。

詳しい説明は以前の Tweet::ToDeliciousでリンク付きTweetをDeliciousに保存する を見てください。

VERSION 0.06ではfavoriteしたtweetを'favorite','via:tweet2delicious'のタグを付けて保存する機能を追加しました。
http://packrati.us/が好きになれなかったので自分で作ったわけですが、これで欲しい機能は一通り実装した形になります。

2011/11/11

[Twitter][API] ドメイン名自動リンクのUser Streams APIでの扱い

[Twitter][API] ドメイン名自動リンクのUser Streams APIでの扱い

Tweet::ToDeliciousでTwitterが勝手にリンクにする"example.com"のような文字列を排除しようと試みたのですが、
判別できなかったので諦めました。

ツイートに含まれるのURL情報のフォーマット

通常、User Streams APIでツイート時に含まれるリンクは以下のようにroot.entities.urlに格納されています。
関係ないところは省略しています。

    {
        "entities":
            "url":
                [
                    {
                        "display_url": "example.com",
                        "expanded_url": "http://example.com",
                        "url": "http://t.co/XXXXX",
                        "indices": [0,20]
                    },
                ] 
    }

そして、現在の仕様では"http"が頭に付かない"example.com"のような文字列が含まれていると、"http://example.com"として扱われます。
しかし、この場合はentities.urlにデータは入りません。URLのつもりでツイートしてるわけじゃないから、当たり前と言っていい挙動です。

問題になるケース

問題となるのは、上記のような自動変換される文字列を含み、且つツイートにURLが含まれる場合になります。
例として"example.com http://example.com"とツイートした場合、以下のようなデータが渡ってきます。

    {
        "entities":
            "url":
                [
                    {
                        "display_url": "example.com",
                        "expanded_url": "http://example.com",
                        "url": "http://t.co/XXXXX",
                        "indices": [0,20]
                    },
                    {
                        "display_url": "example.com",
                        "expanded_url": "http://example.com",
                        "url": "http://t.co/YYYYY",
                        "indices": [21,41]
                    }
                ] 
    }

URLに自動変換された文字列のexpanded_urlにはURI schemeが追加され、通常のURLのdisplay_urlからはURI scheme部分が削除されてる状態になっています。
この仕様によりentities.urlの中身からは、どちらが自動変換された文字列なのか判別ができません。
また、ツイート全体のtext情報では全URLがt.coの短縮URLに置換されているので、ここから判別することもできないようです。

2011/11/06

[perl] Tweet::ToDeliciousでリンク付きTweetをDeliciousに保存する

Tweet::ToDeliciousでリンク付きTweetをDeliciousに保存する

Tweet::ToDelicious

github: https://github.com/ysasaki/p5-tweet-todelicious
git: git://github.com/ysasaki/p5-tweet-todelicious.git

Abstract

リンクの付いているtweetをdeliciousに保存してくれるツールです。
hashtagと[foo]というパターンをtagとして登録します。
また、後で振り分けように自動でvia:tweet2deliciousタグも付与します。

Motivation

今まではdeliciousに気になったリンクを保存していたけど、
最近はCrowsnestbitly経由でTweetすることが増えたため、残しておきたいリンクが分散する形になった。
既存のサービスでリンク付きTweetをdeliciousに保存してくれるものがあるが、hashtagしかtagに変換してくれないので自分で書いた。

Install

gitでgithubから最新版をもってきます。

    $ git clone git://github.com/ysasaki/p5-tweet-todelicious.git
    $ cd p5-tweet-todelicious
    $ cp config.yaml.sample config.yaml

TwitterのStreaming APIを利用しているので、自分で開発者登録をしてconsumer_keyやtokenを取得してください。
登録が完了したら以下の情報をconfig.yamlに書き込みます

  • Twitter ID
  • Twitter App's consumer_key, consumer_secret, token, toke_secret
  • delicious's username, password

後はREADMEにある手順通りにcartonで必要なmoduleをinstallして、起動スクリプトを叩くだけです。

    $ carton install --deployment
    $ carton exec -- perl ./bin/t2delicious.pl

VPSとか適当なサーバ上でdaemontools等を利用して動かすのがベターかと思います。
perl-5.14.xじゃないと動かないので注意。use v5.14;の部分を書き換えれば、perl-5.10.xでも動くと思います。

TODO

  • bit.lyとcrowsnestの短縮URLを展開したい ← 追記 2011/11/09 3:00 展開に対応しました

2011/11/03

use VERSION and use feature

use VERSION and use feature

そろそろ新しいperlを使えそうなので、確認しておく。

use $VERSION

    use v5.14;

これは以下のことが起きる

  • compile time version check
  • use feature ':VERSION' if VERSION >= 5.9.5
  • use strict if VERSION >= 5.11.0

VERSIONの互換性を気にするときは以下のように書けばいいんでないでしょうか。

    use v5.14.2;
    use 5.014_002; # for backwards compatibility. see `perldoc version`

use feature

perl-5.14.2の時点で以下の4つがある

  • say
  • state
  • switch
  • unicode_strings

それぞれの機能は以下のようにしてimportできる

    use feature qw(say);

面倒なので、まとめてimportできる以下のbundleが存在する。

bundle|features
------------------------------------------
:5.10 |switch, say, state
:5.11 |switch, say, state, unicode_strings
:5.12 |switch, say, state, unicode_strings
:5.13 |switch, say, state, unicode_strings
:5.14 |switch, say, state, unicode_strings

bundleは以下のように指定する。

use feature ':5.10';

bundle指定で':5.10.x'と書いた場合、xはなかったものとして扱われる。
また、もう使うことはないだろうが、特別なbundleとして':5.9.5'が存在し、':5.10'として扱われる。

the ’unicode_strings’ feature

5.13.8から完全にサポートされて、Unicode関わるバグ(文字列のappend時の挙動とか)を治してくれる模様。
詳しくはperldoc featureperldoc perlunicodeのThe "Unicode Bug"を参照。

perldoc featureにも以下のようにあるので、新規に書く場合は使ったほうがいいのではないでしょうか。

if you are potentially using Unicode in your program,
the "use feature 'unicode_strings'" subpragma is strongly recommended.

2011/11/01

perlbrewとcpanmとcarton

perlbrewとcpanmとcarton

cartonをどこにinstallするのが適切なのかわからんので、とりあえず以下のようにやってる。

  1. perlbrewをinstall
  2. perlbrew install-cpanm
  3. 必要に応じて perlbrew install perl-5.x.y
  4. cpanm Carton
  5. cd $PROJECT
  6. carton install