2010年 11月 08日

gerry++

irssi、ハイライトさせたいワードを含むがハイライトさせたくないワードを指定

LimeChat の Highlight words と Exclude words の関係みたいなもの、irssi でどうやってやるかサッパリわからないので書きました……

といっても、MSGLEVEL_HILIGHT のフラグを落とすだけなのでハイライト自体はされてしまいます。

僕は MSGLEVEL_HILIGHT が立っているメッセージを特定ウィンドウに転送する別のスクリプトと、全てメール通知+IM 通知するスクリプトを使っていて、ハイライト自体よりは後半のアクションを止めたいだけなのでとりあえず十分です。

2010年 11月 06日

自分用 IRC の Web インターフェイス

  • rpc.pl を ~/.irssi/scripts/auto に
    • /script load auto/rpc しとく
  • perl script/server.pl -p 9876 で HTTPD を起動

irssi のプラグイン + UI用 HTTPD の構成になっている

  • irssi で直接 UI 用の HTTP サーバをたてない
    • UI 用のウェブサーバは再起動を頻繁にすることが多いので、irssi 用のプラグイン部分は最小限構成にし、滅多に再起動したりしないように
  • UI 用の HTTP サーバと irssi は RPC で通信することにする
    • 現状は MessagePack RPC

機能的には以下程度

  • 端末ごとの自動ふりわけ
    • タッチデバイス (Android, iPhone, iPad) への対応
    • PC 向けの簡易ビュー (どうしてもSSHを使えない環境とかで使う用)
  • JS による画面制御
    • 体感速度向上
  • 省メモリ
    • HT-03A 程度の端末でもホーム画面が殺されたりしない
  • irssi と連動した未読管理
    • window を移動した際、irssw 側の未読もクリアする

これまで mobirc をずっと使っていたのですが、Android 用にもうちょいマシな新しくUI作りなおそうと思ったときなんか面倒だったので、最小限セットのものを作りなおした。

Tiarra に RPC 機能をつけたらいいかとも思ったが、Tiarra に手を入れたくなかったのでやめた

2010年 11月 04日

gerry++

2010年 11月 01日

gerry++

2010年 10月 31日

gerry++

2010年 10月 28日

gerry++

2010年 10月 17日

DSi Browser にも console.log が欲しい

DSi Browser 上で動く JavaScript を書く必要があることがしばしばあると思いますが、今のご時世でもデバッグが面倒で、alert デバッグに頼ったりすることがあります。これは大変今時としてはありえないので、console.log が欲しいところです。とはいえ、DSi Browser の表示領域というのは非常に限らており、console.log を作るにしても表示する場所がありません。

なので以下のようなサーバを立ててやることがあります。

#!/usr/bin/env plackup -s AnyEvent 
# vim:set ft=perl:
use strict;
use warnings;
use lib 'lib/perl/lib/perl5', 'lib/perl/lib/perl5/x86_64-linux-thread-multi';

use Plack::Request;
use AnyEvent;
use JSON::XS;

my $template = <<'EOS';
<!DOCTYPE html>
<title>tail</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.4.2.min.js"></script>
<script type="text/javascript">$(function () {
var log   = document.getElementById('log');
var $style = $(document.createElement('style')).appendTo(document.body);

$('.host').live('click', function () {
	var host = $(this).text();
	$style.text( '.line { display: none } ' + '.line.' + classOfHost(host) + ' { display: block }');
});

$(document.body).dblclick(function () {
	$style.text('.line { display: block }');
});

function classOfHost (host) {
	return 'h-' + host.replace(/\./g, '-');
}


function read () {
	$.ajax({
		url: '/api/read',
		dataType : 'json',
		success : function (data) {
			var messages = data.messages;
			for (var i = 0, len = messages.length; i < len; i++) {
				var message = messages[i];
				$('<div class="line"></div>').
					append($('<span class="host"></span>').text(message.host)).
					append($('<span class="body"></span>').text(message.body)).
					addClass(classOfHost(message.host)).
					prependTo(log);
			}
			while (log.childNodes.length > 100) log.removeChild(log.lastChild);
		},

		complete : function () {
			setTimeout(function () {
				read();
			}, 1000);
		}
	});
}

read();

})</script>
<style type="text/css">
pre {
	font-size: 14px;
	line-height: 1.33;
}

.host {
	color: #666;
	margin: 0 1em 0 0;
	cursor: pointer;
}

.body {
}
</style>
<pre id="log"></pre>
EOS

my $i   = 0;
my $sessions = {};

my $app = sub {
	my $env = shift;
	$env->{'psgi.streaming'} or die;
	sub {
		my $callback = shift;

		my $req = Plack::Request->new($env);
		my $res = $req->new_response(200);
		eval {
			+{
				'/' => sub {
					$res->content_type('text/html');
					$res->content($template);
					$callback->($res->finalize);
				},
				'/api/read' => sub {
					my $sid = $req->cookies->{sid} || $i++;
					my $session = $sessions->{$sid} || +{
						sid      => $sid,
						messages => [],
						callback => undef,
						expire   => 0,
						ua       => $req->header('User-Agent'),
					};
					$session->{expire} = time() + 60 * 60;
					$sessions->{$sid} = $session;
					$res->cookies->{sid} = $sid;

					if (@{ $session->{messages} }) {
						$res->content(encode_json +{
							sid => $sid,
							messages => $session->{messages},
						});
						$session->{messages} = [];
						$callback->($res->finalize);
					} else {
						$session->{callback} = sub {
							my $message = shift;
							delete $session->{callback};
							$res->content(encode_json +{
								sid => $sid,
								messages => [ $message ],
							});
							$res->content_type('application/json');
							$callback->($res->finalize);
						};
					}
				},
				'/p' => sub {
					my $message = {
						host => $req->address,
						body => $req->param('m'),
					};
					for my $sid (keys %$sessions) {
						my $session = $sessions->{$sid};
						if ($session->{expire} < time()) {
							delete $sessions->{$sid};
							next;
						}

						if ($session->{callback}) {
							$session->{callback}->($message);
						} else {
							push @{ $session->{messages} }, $message;
						}
					}

					$res->content_type('application/json');
					$res->content(encode_json +{ status => 'ok' });
					$callback->($res->finalize);
				},
			}->{ $req->path }->();
		};

		if ($@) {
			$res->code(200);
			$res->content($@);
			$callback->($res->finalize);
		}
	};
};

use Plack::Builder;

builder {
	enable "Plack::Middleware::ReverseProxy";
	$app;
};

このサーバを立てたうえで、JS で以下のようなコードをつくっておきます。

function toTail (msg) {
	var img = new Image();
	img.src = 'http://tail.psgi/p?m=' + encodeURIComponent(msg);
}

if (typeof console == 'undefined') {
	console = {
		log : toTail
	};
}

どや

複数ブラウザで同時に JS を実行するシェルをつくった

plackup -s AnyEvent psgi.psgi

して、http://localhost:5000/ とかを各ブラウザで開いておく (ここは手動)

でもって ./shell.pl するとプロンプトがでてくるので適当に入力すると各ブラウザからレスポンスがかえってくる。

仕組み

  • 各ブラウザで / をひらくと、それぞれ long poll してスクリプトの実行要求を待つ。
    • 親の iframe がずっと long poll する
  • 実行要求は適当に HTTP で送る (結果がかえってくるまで long poll する)
  • 各ブラウザはそれを受け取って子 iframe で実行する
  • 各ブラウザは結果を HTTP 経由で送る
  • 各ブラウザからの結果が集ったら実行要求の応答をする

二重の long poll になっている。

ブラウザたち → 中継ウェブサーバ ← 実行させたいクライアント

中継サーバは Plack, AnyEvent

どや

2010年 10月 16日

YAPC::Asia 2010 で「映画にでてくるハッカーになりたい」というタイトルで発表してきました

大変恐縮ながら投票でエンターテナー賞を頂きました。聞き間違えかと思うぐらいびびりました。ありがとうございます。ちょうど iPad の Safari でお絵描きツールを作りたいけど、継続的に使える実機がなくて困っていたので嬉しいです。

以下補足

realtime*

realtime* 系は動かすのにちょいと設定がいるので説明しておきますと (この説明でわかるかわかりませんが……)

~/.rrggc に以下のような感じのことを書く

Format::Apache::LogFormat->define_logformats(q[
	LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D %{X-DCMGUID}i %{X-UP-SUBNO}i %{X-JPHONE-UID}i %{X-SID}i" mylog
]);

これは realtime* が実行されるときに do されるファイルで、define_logformats の中は Apache のログフォーマットをそのままコピペするとたぶん動きます (動かないかも) responsegraph をとりたい場合には %D が必須で、accesstrack をみたいときは X-JPHONE-UID とか、ユーザど同定するセクションが必須です。

それで

tail ... | realtimeresponsegraph.pl --format mylog

とかやるとでてくる気がします。ほか、オプションがいくかありますが --format だけ必須です。

Devel::KYTProf

  • http://github.com/onishi/perl5-devel-kytprof
    • 僕がフォークしてるやつは前パッチ書いたときにやったやつなので、onishi さんのが一番新しいはずです (今はコラボレータに入れてもらったので直接コミットしてます)。

use するだけで

  • DBI
  • LWP::UserAgent
  • Cache::Memcached::Fast
  • MogileFS::Client

のメソッドコールが表示されるようになりますが、任意のメソッドの対して設定できるようになっているので自力で設定することもできるようになっているようです

あと、いくつか正規表現を設定できて、例えば

Devel::KYTProf->namespace_regex(qr/Hatena/);

とかやると、フックしたメソッドが呼ばれたとき、表示されるパッケージ名を、指定した regexp にマッチするパッケージまでたどるので、「自分のアプリ内で、どこからよばれているか」がわかるようになって便利です。