search.cpan.org で syntax highlighting

最近 search.cpan.org で POD 中のコードに syntax highlight できるようになりました。

使い方

ページ最下部右側にこのようなcolor schemeを選ぶオプションがあるので、リストの中から自分好みのものを選ぶと良いでしょう。

おすすめは "cpan" らしいです。

参考

http://blogs.perl.org/users/mark_a_stratman/2011/03/syntax-highlighting-for-searchcpanorg.html


How to growl IRC messages over ssh

My environment

I'm using weechat on remote server's screen.

The big problem is I can't perceive chatting on IRC without "polling" terminal by myself. Because I have no way to get alert over screen and ssh.

I was thinking to solve this problem and just found the solution.

The flow is:

  • Use weechat's logging function
  • Connect to remote server over ssh, tail -f your logs
  • Receive the multiple lines then process it with perl on your local machine and growl!

Preparation

  • Enable weechat's logging function and find the log files.
  • Confirm that the following command works well (rewrite the path to logs)
tail -f $HOME/.weechat/logs/*/*/*.weechatlog
  • Install `growlnotify`

Setup scripts

Shell script that connecting to the server (irc_growl.sh)

You may have to rewrite ssh options and path to the log. Be careful with `'` (single quotation mark) to guaranteeing the home dir path is interpolated with remote's.

ssh example.com tail -n 1 -f '$HOME/.weechat/logs/*/*/*.weechatlog' | perl irc_growl.pl &
Perl script that processes received lines (irc_growl.pl)

You can rewrite system()'s args as you want. `man growlnotify` will help you.

while (<STDIN>) {
    chomp;
    next unless $_;
    next if /^==>/; # ignore tail's output

    my $attr = parse($_);
    if ($attr->{type} =~ /(:?NOTICE|PRIVMSG)/) {
        system("growlnotify", "-m", $attr->{content}, "-t", $attr->{user}, "--appIcon", "LimeChat");
    }
}

sub parse {
    my ($line) = @_;

    my ($time, $cmd, @contents) = split /\t/, $line;
    my $content = join "\t", @contents;

    my $user;
    if (not $cmd) {           # critical error
        $cmd = 'ERROR';
    } elsif ($cmd eq '-->') { # join
        $cmd = 'JOIN';
    } elsif ($cmd eq '<--') { # part
        $cmd = 'PART';
    } elsif ($cmd eq '--') {  # messages from server
        $cmd = 'SERVER';
    } elsif ($cmd eq '*') {   # notice
        $user = $cmd;
        $cmd = 'NOTICE';
    } else {                  # privmsg
        $user = $cmd;
        $cmd = 'PRIVMSG';
    }

    return +{
        type    => $cmd,
        time    => $time,
        content => $content,
        user    => $user,
    };
}

Run!

./irc_growl.sh

100 fav されたので MacBook Air 11インチ を買ったら spell がなくてxt/podspell.t がコケた

Mac に戻ってくるのはもう6~7年ぶりなのか、当時は Intel Mac なんて無くて PowerPC でした。全体的な UI は昔からさほど変わった感じはしないですね。

さっそく環境を整えていたら github から持ってきたモジュールのテストがコケる。

こんな感じのテストを使っていたんですが、 spell は OSX には無いらしく、今まで Linux でしか作業してなかったのでだいぶ嵌りました。

podspell.t

use Test::More;
eval q{ use Test::Spelling };
plan skip_all => "Test::Spelling is not installed." if $@;
add_stopwords(map { split /[\s\:\-]/ } <DATA>);
$ENV{LANG} = 'C';
all_pod_files_spelling_ok('lib');
__DATA__

spell がないと怒られます。

sh: spell: command not found

はてどうしたものかと CPAN モジュールを漁って見ると、各々思い思いの方法で podspell.t を書いているようでした。

そこで参考にしたのは、いろいろな環境の開発者がいて xt/ が多く走っていそうな Plack のテストを参考にしてみました。aspell が存在する場合には aspell を使い、ない場合には spell を使う方法が良さそうです。

Plack-0.9973/xt/podspell.t

use Test::More;
eval q{ use Test::Spelling };
plan skip_all => "Test::Spelling is not installed." if $@;
add_stopwords(map { split /[\s\:\-]+/ } <DATA>);
$ENV{LANG} = 'C';
set_spell_cmd("aspell -l en list") if `which aspell`;
all_pod_files_spelling_ok('lib');

__DATA__

Google Reader で 'v' キーを押したときにバックグラウンドで InstaReader を使ってページを開く Greasemonkey を書いた

// ==UserScript==
// @name           Google Reader with InstaReader
// @namespace      http://linknode.net
// @description    Open feed item with InstaReader in background by pressing 'v'
// @include        http*://www.google.com/reader/view/*
// ==/UserScript==

document.addEventListener('keypress', function(event) {
    if(event.which == 118) { // 118 is 'v'
        if (document.getElementById('current-entry') == null) return; // no current entry

        var link = document.getElementById('current-entry').getElementsByTagName('a').item(0).getAttribute('href');
        var url  = 'http://www.instapaper.com/text?u=' + encodeURIComponent(link);
        event.stopPropagation();
        event.preventDefault();
        GM_openInTab(url);
    }
}, true);

インストールはこちら

https://gist.github.com/raw/825918/5a9f84f2745a062450ec766d213be4fc095b6804/google_reader_with_instareader.user.js


Gmail で OCN の SMTP を指定すると Remote server does not support TLS code(500) とエラーが出る件

ヘルプフォーラムから2chまで探してようやく解決したので2011年1月14日現在のメモ

2010年9月1日以前にOCN会員登録証が届いたお客さま smtp.vcの後にメールアドレスの@の右側

http://help.ocn.ne.jp/ols/mail/10002_m_infochk.html の表記通りに従っても、smtp.vc*.ocn.ne.jp はTLS対応してないようなので延々と下のエラーメッセージが表示される。

Remote server does not support TLS code(500)

2010年9月1日以降にOCN会員登録証が届いたお客さま(SSL方式)

この項目は一見すると昔からのユーザは使えない様に見えるが、この通り設定することで受信できる。なんだそれ。

送信メールサーバ(SMTP) smtp.ocn.ne.jp
ポート番号 465
SSL 利用する
認証 使用する
アカウント名(ユーザ名) メールアドレスをすべて入力
パスワード メールパスワードを入力

Imager で Twitter アイコンを低品質 jpeg で返す

Twitter のアイコンには主に gif / jpeg / png が使われており、 png を表示できないガラケーがある。

Cache::Memcached::Fast でキャッシュしつつ、 Imager では一時ファイルを作らず。思っていたよりも楽にかけた

use LWP::UserAgent;
use Imager;
use Cache::Memcached::Fast;

use Plack::Request;
use Plack::Builder;

my $memd = Cache::Memcached::Fast->new({
    ...
});


builder {
    mount '/tw_thumbnail' => sub {
        my $env = shift;
        my $req = Plack::Request->new($env);

        if ($req->param("url") =~ m{^(http://a\d\.twimg\.com/.+)}) {
            my $url = $1;

            if (my $result = $memd->get($url)) {
                return [200, ['Content-Type' => 'image/jpeg'], [$result]];

            } else {
                my $img = LWP::UserAgent->new->get($url)->decoded_content;
                my $image = Imager->new;

                $image->read(data => $img)
                    or return [500, [], [$image->errstr]];

                $image->write(data => \my $out, jpegquality => 30, type => 'jpeg')
                    or return [500, [], [$image->errstr]];

                $memd->set($url, $out, 1 * 60 * 60 * 24);

                return [200, ['Content-Type' => 'image/jpeg'], [$out]];
            }

        } else {
            return [404, [], ['Not Found']];
        }
    };
};

Plack::Middleware::Session で Session Fixation 対策

Session Fixation

XSS や事前に取得したセッションを利用者に送り込んで権限を昇格させる類の攻撃で、セッションIDをクエリに含ませたり、シーケンシャルなセッションIDを用いるような愚かなことをしなければ基本的に問題はないものの、 Session Fixation への根本的な対策として、ログイン成功後にこれまでのセッションを破棄し、新しいセッションを発行することが望ましい。

Session Fixation について詳しいことは IPA の出している『安全なウェブサイトの作り方(pdf)』改定第4版 p.15 から p.20 に説明があります。

0.13

今まで P::M::Session ではログイン成功後に新しくセッションを開始するのにちょっとした細工が必要でしたが、 0.13 からは古いセッションの破棄とあたらしいセッションの発行を、

$request->env->{'psgix.session.options'}->{change_id}++;

することで容易に行えるようになりました

追記

$request->session_options->{change_id}++;

こちらの方がいいですね!

検証用

ログイン時にあたらしいセッションが発行されるかをチェックするために簡略化した検証用のコードを書いてみました。

/ -> /login -> / と遷移したときに、セッションが変わっていることがわかりますね!

use strict;
use warnings;

use Plack::Builder;
use Plack::Session 0.13;
use Plack::Session::Store::File;
use Plack::Session::State::Cookie;

my $app = sub {
    my $env = shift;
    my $request = Plack::Request->new($env);

    my $body = q{
        <html><head><title></title>
        <script>document.write(document.cookie);</script>
        </head><body></body></html>
    };

    if ($request->path_info eq '/') {
        my $session = Plack::Session->new($request->env);

        if ($session->get('verified')) {
            my $res = $request->new_response(200);
            $res->content_type('text/html');
            $res->body($body, ' verified session');
            $res->finalize;

        } else {
            my $res = $request->new_response(200);
            $res->content_type('text/html');
            $res->body($body, ' session is not verified');
            $res->finalize;
        }

    } elsif ($request->path_info eq '/login') {
        my $session = Plack::Session->new($request->env);

        if ($request->param("password") eq 'foo') {
            $request->session_options->{change_id}++;
                # is equals to $request->env->{'psgix.session.options'}->{change_id}++;
            $session->set('verified', 1);

            my $res = $request->new_response(200);
            $res->content_type('text/html');
            $res->body($body, ' correct password');
            $res->finalize;

        } else {
            my $res = $request->new_response(200);
            $res->content_type('text/html');
            $res->body($body, ' incorrect password');
            $res->finalize;
        }

    } elsif ($request->path_info eq '/logout') {
        my $session = Plack::Session->new($request->env);
        $session->expire;

        my $res = $request->new_response(200);
        $res->content_type('text/html');
        $res->body($body, ' logout');
        $res->finalize;

    } else {
        my $res = $request->new_response(404);
        $res->body("Not Found");
        $res->finalize;
    }

};

builder {
    enable 'Session',
        store => Plack::Session::Store::File->new(
            dir => './sessions'
        ),
        state => Plack::Session::State::Cookie->new(
            session_key => 'sid'
        );
    $app;
};

MySQL と Perl で UTF-8 を扱う

とある環境で latin1 - latin1 の憎き動作をしていたので今後のために最小限のコードと設定をメモしておく

my.cnf

これ重要

[mysqld]
character_set_server = utf8

mysql クライアントで status と SHOW VARIABLES LIKE 'char%'; を発行し、 mysqld_safe で指定した my.cnf の内容が適用されているか、次のような結果を得られるかを確認する。

status
Server characterset:	utf8
Db     characterset:	utf8
Client characterset:	utf8
Conn.  characterset:	utf8
SHOW VARIABLES LIKE 'char%';
+--------------------------+------------------------------------------------------------+
| Variable_name            | Value                                                      |
+--------------------------+------------------------------------------------------------+
| character_set_client     | utf8                                                       |
| character_set_connection | utf8                                                       |
| character_set_database   | utf8                                                       |
| character_set_filesystem | binary                                                     |
| character_set_results    | utf8                                                       |
| character_set_server     | utf8                                                       |
| character_set_system     | utf8                                                       |
| character_sets_dir       | /usr/local/mysql-5.1.50-linux-i686-glibc23/share/charsets/ |
+--------------------------+------------------------------------------------------------+

テスト用のデータベース・テーブルを作る

データベース作成
CREATE DATABASE enctest;
テーブル作成
CREATE TABLE `utf8table` (
  `title_utf8` VARCHAR(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
テーブルの確認

DEFAULT CHARSET が utf8 になってるかを確認

SHOW CREATE TABLE utf8table;

アプリケーションのサンプル

テスト用なので最低限必要なオプションを DBI->connect() の \%attr にふたつ。

use strict;
use warnings;
use utf8;
use Encode;
use Data::Dumper;
use DBI;

my $dbh = DBI->connect('DBI:mysql:database=enctest;host=localhost', 'foo', 'bar',
    {
        mysql_enable_utf8 => 1,
        on_connect_do => ['SET NAMES utf8'],
    }
);

my $sth = $dbh->prepare(q{
    INSERT INTO utf8table (title_utf8)
         VALUES (?)
});
$sth->execute('いろは' . rand); # string

my $sth2 = $dbh->prepare(q{
    SELECT * FROM utf8table
});
$sth2->execute;

my $rv = $sth2->fetchall_arrayref;
print Dumper $rv;

for (@$rv) {
    print encode_utf8($_->[0]), $/;
}

$dbh->disconnect;

exit;
__END__
実行結果
$VAR1 = [
          [
            "\x{3044}\x{308d}\x{306f}0.052142747285874"
          ],
          [
            "\x{3044}\x{308d}\x{306f}0.87866795396349"
          ],
          [
            "\x{3044}\x{308d}\x{306f}0.85588866665876"
          ],
          [
            "\x{3044}\x{308d}\x{306f}0.473092086686851"
          ]
        ];
いろは0.052142747285874
いろは0.87866795396349
いろは0.85588866665876
いろは0.473092086686851

めでたしめでたし。


AnyEvent::Twitter 0.52

http://search.cpan.org/~punytan/AnyEvent-Twitter-0.52/lib/AnyEvent/Twitter.pm を出しました。

0.51 から 0.52 の変更点

new()

AnyEvent::Twitter::Stream と同等の OAuth 引数を使えるようになりました

つまり、access_token と access_token_secret が token と token_secret として使えます。これに伴って、eg/gen_token.pl でも出力形式を選べるようになりました。

これまでは

    my $ua = AnyEvent::Twitter->new(
        consumer_key        => 'consumer_key',
        consumer_secret     => 'consumer_secret',
        access_token        => 'access_token',
        access_token_secret => 'access_token_secret',
    );

のように書いていたものを

    my $ua = AnyEvent::Twitter->new(
        consumer_key    => 'consumer_key',
        consumer_secret => 'consumer_secret',
        token           => 'access_token',
        token_secret    => 'access_token_secret',
    );

のようにかけるようになりました

get(), post()

これまでは request() メソッドのみ対応していましたが、今回の変更で get() と post() を追加しました。

    $cv->begin;
    $ua->request(
        method => 'GET',
        api    => 'account/verify_credentials',
        sub {
            my ($hdr, $res, $reason) = @_;

            say $res->{screen_name};
            $cv->end;
        }
    );

のように書いていたものは、 0.52 からは

    $cv->begin;
    $ua->get('account/verify_credentials', sub {
        my ($hdr, $res, $reason) = @_;

        say $res->{screen_name};
        $cv->end;
    });

のようにかけるようになりました。

get() の引数

get() ではパラメータは省略可能です。

  • $ua->get($api, sub {})
  • $ua->get($api, \%params, sub {})
  • $ua->get($url, sub {})
  • $ua->get($url, \%params, sub {})
post() の引数

post() ではパラメータは省略不可です。パラメータ不要の場合は空の hashref を渡してください。

  • $ua->post($api, \%params, sub {})
  • $ua->post($url, \%params, sub {})

つぶやく場合は

    $cv->begin;
    $ua->post('statuses/update', {status => 'いろはにほへと ちりぬるを'}, sub {
        my ($hdr, $res, $reason) = @_;

        say $res->{user}{screen_name};
        $cv->end;
    });

のように、より短くかけるようになりました。

その他

メソッド追加に伴ってテストを増やした(xt/05_get.t と xt/06_post.t あたり)


最新 CPAN モジュールをつぶやく Twitter BOT を書いた

Twitter

http://twitter.com/cpan_new ドキュメントへのリンクは FrePAN へ。

雑感

http://friendfeed.com/cpan からリアルタイムでデータを取ってくる。使ったモジュールはこれだけで、全体で60行弱。機能のわりに短くかけた。

use common::sense;
use Data::Dumper;
use AnyEvent::Twitter;
use AnyEvent::FriendFeed::Realtime;

repository

https://github.com/punytan/cpan_new

なんと見つかった既存のものは全部止まってた


« 6 »