svn commit report mail

Rubyのが流行っているらしいが書けないので、素直にPerlで。
当然のように日本語が通らないので修正して使う。


修正する前のは、これを使います。CentOS5.5くらいには入っているはず。
/usr/share/doc/subversion-1.4.2/tools/hook-scripts/commit-email.pl


このスクリプト、驚くべきことに
いわゆるmain()が634行あります。
処理の流れは追いかけやすいかもしれないけれど...


ファイル毎に文字コードが違っていても大丈夫です。


それから、svnlookを起動する際、
システムメッセージ部分は英語(まあ、日本語UTF-8でも良いのだが)で、
コミットログはUTF-8で、
という指示をつけているところがポイントといえばポイントでしょうか。

--- /usr/share/doc/subversion-1.4.2/tools/hook-scripts/commit-email.pl  2009-08-11 06:59:52.000000000 +0900
+++ commit-email.pl     2011-06-09 15:41:36.000000000 +0900
@@ -581,6 +581,16 @@
                                         '-r', $rev, @no_diff_deleted,
                                         @no_diff_added);
         @difflines = map { /[\r\n]+$/ ? $_ : "$_\n" } @difflines;
+
+               # start
+               s/\r\n/\n/ for(@difflines);
+               use Encode;
+               use Encode::Guess qw/cp932 shiftjis euc-jp utf8/;
+               @difflines = (join("\n=", map {
+                       my $decoder = Encode::Guess->guess($_);
+                       if(ref $decoder) { $_ = encode("utf8", $decoder->decode($_)); }
+               } split(/\n=/, join('', @difflines))));
+               # end
       }
 
     if (defined $sendmail and @email_addresses)
@@ -763,6 +773,11 @@
     {
       croak "$0: read_from_process passed no arguments.\n";
     }
+  
+  # start
+  @_ = ("/usr/bin/env", "LC_MESSAGES=C", "LC_CTYPE=ja_JP.UTF-8", @_);
+  # end
+  
   my ($status, @output) = &safe_read_from_pipe(@_);
   if ($status)
     {

フレームワークの使い方

cakePHPというフレームワークがあって、まあ、実にいろいろ複雑な仕掛けがあるわけです。

app/config/bootstrap.phpというファイルがあって、このファイルは、名前からして、相当初期にロードされるのだけれど、ここでうっかりあれこれ(*)やると、必要な設定が行われる前にフレームワークの基本的なところが不正確な設定で初期化されてしまって、妙なことになる、と。

それで誤動作すれば、まあ、何となくわかったりするものの、その症状は(バージョンによって違うかもしれないけれど)App::import()のキャッシュが無効になって、動作がとても遅くなる、というもの。

App::import()が遅い、というか、無駄すぎる、という話は、あちこちで出ていたりするものの、キャッシュが効けば(仕様として好き嫌いはあるでしょうが)遅くはないはず。

http://cakephp.jp/modules/newbb/viewtopic.php?topic_id=1458&forum=3&viewmode=flat&order=DESC&start=0

それにしても、仕掛けが多い道具を正しく使いこなすのは難しいです...

PHPでOAuth

PEARが嫌いというわけでもないけど、短く書けるものは短く書きたいということで。
いろいろ参考にしながら書いてみました。

PHP(5.3.xの機能は使ってません)のクライアントアプリです。
依存ライブラリとかはありません。
でも、エラー処理とかもありません。
ごちゃごちゃした仕様書よりも、
5.2.x以前では動くが5.3.xでは(たまに)動かないコードよりも、
ちゃんと動くコードを読むのが早いよね、っていう方、是非。
(なんでPHPなんだ、というのは、聞かないでください...)

実行するとURLが表示されるので、(ユーザに)このURLにアクセスしてもらいます。
「許可」ボタンを押すと数字が表示されるので、
Enter verifier code:の後に続けて数字を打ち込んでEnterを押します。

そうすると、user_id, screen_name, oauth_token, oauth_token_secretが表示されます。

今回はここまで。
次回はこれを使ってtwitter clientでも作ってみようと思います。

<?php

$api = new oauth_consumer();
$api->consumer_key = "*********************";
$api->consumer_secret = "****************************************";

$request_token = $api->get_request_token();

$confirm_url = "http://twitter.com/oauth/authenticate?oauth_token=".$request_token['oauth_token'];

print "Let's access to $confirm_url\n";
print "\n";

print "Enter verifier code: ";
$stdin = fopen('php://stdin', 'r');
$verifier = trim(fgets($stdin));

$access_token = $api->get_access_token($request_token, $verifier);

print $access_token['user_id']."\n";
print $access_token['screen_name']."\n";
print $access_token['oauth_token']."\n";
print $access_token['oauth_token_secret']."\n";

class oauth_consumer {
        function http_build_query($h) {
                return join('&', array_map(array($this, 'pair'), array_keys($h), array_values($h)));
        }

        function pair($k, $v) {
                return join('=', array_map(array($this, 'encode'), array($k, $v)));
        }

        function encode($str) {
                return preg_replace('/([^a-zA-Z0-9_\.~-])/e', '"%".strtoupper(join("", unpack("H2", \'$1\')))', $str);
        }

        function sign($method, $url, $param, $secret="") {
                $param['oauth_signature_method'] = 'HMAC-SHA1';
                $param['oauth_timestamp'] = time();
                $param['oauth_nonce'] = md5(microtime() . mt_rand());
                $param['oauth_version'] = '1.0';

                ksort($param, SORT_STRING);
                $qstring = $this->http_build_query($param);

                $txt = join('&', array_map(array($this, 'encode'), array($method, $url, $qstring)));
                $key = join('&', array_map(array($this, 'encode'), array($this->consumer_secret, $secret)));

                $param['oauth_signature'] = base64_encode(hash_hmac('sha1', $txt, $key, true));

                return $param;
        }

        function get_request_token() {
                $method = "GET";
                $url = 'http://twitter.com/oauth/request_token';
                $param = array(
                        'oauth_consumer_key' => $this->consumer_key,
                        'oauth_callback' => 'oob',
                );
                $param = $this->sign($method, $url, $param);
                $q = $this->http_build_query($param);

                $result = file_get_contents("$url?$q");
                parse_str($result, $result);

                return $result;
        }

        function get_access_token($request_token, $verifier) {
                $method = "GET";
                $url = 'http://twitter.com/oauth/access_token';
                $param = array(
                        'oauth_consumer_key' => $this->consumer_key,
                        'oauth_token' => $request_token['oauth_token'],
                        'oauth_verifier' => $verifier,
                );
                $param = $this->sign($method, $url, $param, $request_token['oauth_token_secret']);
                $q = $this->http_build_query($param);

                $result = file_get_contents("$url?$q");
                parse_str($result, $result);

                return $result;
        }
}

Apacheでワイルドカードのバーチャルホスト

*.example.comのようなDNSレコード(AまたはCNAME)を定義しておけば、いちいちDNSレコードを追加することなく、バーチャルホストが使えます。

*の部分を処理するのがWebアプリケーションという場合は、HTTP_HOST環境変数(Hostヘッダ)を見て、適当にやれば良いわけですが、別々のディレクトリにコンテンツを置いて、普通のWebサーバとして運用したい場合は、普通は、ApacheのVirtualHost設定を書き足す必要があります。

そのあたりをうまくやってくれるApacheモジュールがあったような気もしないでもないですが、うちではこんな感じです。実際には、例外的なディレクトリ配置を別ファイルに記述したり、ログをとったり、いろいろやっていますが、ここでは要点だけを。

ディレクトリの構成はこんな感じにします。

/var/www/example.com/test1/ ← test1.example.comのDocumentRoot
/var/www/example.com/test2/ ← test2.example.comのDocumentRoot
/var/www/example.net/test3/ ← test3.example.netのDocumentRoot
/var/www/example.net/test4/ ← test4.example.netのDocumentRoot

最初のRewriteRuleは、VirtualHost対象外にしたいものを書きます。以下の例ではserver-statusとiconsを指定しています。

なお、各DocumentRoot内でmod_rewrite(RewriteRule)を使ったり、プログラム中でHTTP環境変数を使う場合、素直にVirtualHost設定をしている時と取得できる値が異なる場合があるので、注意が必要です。

↓これはもちろんhttpd.confに書きます。.htaccessにはとか書けないので念のため。

<VirtualHost _default_:*>
DocumentRoot /var/www/default
RewriteEngine on
RewriteMap    lowercase    int:tolower
RewriteRule   ^/server-status - [L]
RewriteRule   ^/icons/ - [L]
RewriteCond   %{HTTP_HOST} ^(.+)\.(example\.com|example\.net)(:(80|443))?$
RewriteCond   /var/www/%2/%1 -d
RewriteRule   ^/(.*)$ /var/www/%2/%1/$1
</VirtualHost>

ISO-2022-JPエンコードのメールをPEAR::Mailで送る

基本的には、以下のコードでうまくいきます。(ファイルはもちろんUTF-8で保存してください。)

<?php
require_once "Mail.php";
require_once "Mail/mime.php";

$to = "to@example.com";

$mime = new Mail_Mime();
$mime->setTxtBody("ほんぶん");
$body = $mime->get(array(
	"head_charset" => 'UTF-8',
	"text_charset" => 'UTF-8',
));
$header = $mime->headers(array(
	"To" => $to,
	"From" => "ふろむ <from@example.com>",
	"Subject" => "さぶじぇくと",
));
$mail = Mail::factory();
$mail->send($to, $header, $body);

ただ、古いメーラを使っているユーザは、UTF-8なメールが扱えない。イマドキ、IE6並みに時代遅れなのではあるが、ユーザからのクレームにいちいち対応したくなければ、あと10年くらいはISO-2022-JPで送るのが無難かも。で、こうやってみる。

<?php
require_once "Mail.php";
require_once "Mail/mime.php";

$to = "to@example.com";

$mime = new Mail_Mime();
$mime->setTxtBody(mb_convert_encoding("ほんぶん", "ISO-2022-JP", "UTF-8"));
$body = $mime->get(array(
	"head_charset" => 'ISO-2022-JP',
	"text_charset" => 'ISO-2022-JP',
));
$header = $mime->headers(array(
	"To" => $to,
	"From" => mb_convert_encoding("ふろむ <from@example.com>", "ISO-2022-JP", "UTF-8"),
	"Subject" => mb_convert_encoding("さぶじぇくと", "ISO-2022-JP", "UTF-8"),
));
$mail = Mail::factory();
$mail->send($to, $header, $body);

しかし、Mail_Mimeの実装の都合上、上のコードでは、うまくいかない。

ソースを見てみると、0x80-0xffの文字だけをQエンコードしているのだが、ISO-2022-JPの文字列は0x00-0x7fだけでできていて、かつ、0x00-0x7fの範囲にある制御文字を含んでいる。Bエンコードを使うことの方が一般的な気もするが、とりあえずQエンコードを固定的に使う仕様を変えないとすると、こんな感じ。

<?php
class Mail_mime_JP extends Mail_mime {
	function _encodeHeaders($input) {
		foreach ($input as $hdr_name => $hdr_value) {
			$hdr_value = mb_convert_encoding($hdr_value, 'UTF-8', $this->_build_params['head_charset']);
			preg_match_all('/(\w*[\x80-\xFF]+\w*)/', $hdr_value, $matches);
			foreach ($matches[1] as $value) {
				$replacement = preg_replace('/(.)/e',
					'"=" .
					strtoupper(dechex(ord("\1")))',
					mb_convert_encoding($value, $this->_build_params["head_charset"], "UTF-8"));
				$hdr_value = str_replace($value, '=?' .
					$this->_build_params['head_charset'] .
					'?Q?' . $replacement . '?=',
					$hdr_value);
			}
			$input[$hdr_name] = $hdr_value;
		}
		return $input;
	}
}

使い方について、念のために書いておくと、

require_once 'Mail_mime.php';

の後で上記のコードをrequireして、

$mime = new Mail_Mime();

$mime = new Mail_Mime_JP();

に変える、というだけ。

mb_encode_mimeheader()を使ってMail_Mimeの外でエンコードを済ませてしまうやり方は、あちこちに転がってるけれど、美しくない気がします。

美しくないと言えば、そもそもMail_Mimeが美しくない! get()は、引数として渡しているhead_charsetとかをインスタンスのプロパティに上書きセットするという副作用があって、ここでhead_charsetをセットしているからその後のheaders()が正しく動くようになっています。そのため、get()を呼び出さずにheaders()を呼び出すと、うまくいきません。

プログラミングって何だろう?

そんなことを考える機会があったので、いろいろ書いてみる。

プログラミングは楽しいか?

  • a. 数学や創作が楽しいのと同様、プログラミングも楽しい
    • ネットワーク時代のプログラミングは、コラボレーションも楽しい
  • b. プログラミングという手段によって、目的が達せられるのが楽しい
    • ロボットが動くとか...

素質

何か不思議なものがあったとして(たとえば電子レンジ、とか?)、その仕組みを探ってみたいと思うか、思わないか。
使えればOK中身に興味は無いって言う人は、本格的な言語より、お手軽なものが向いているかもしれない。
EXCELのワークシート関数は便利だが、あのレベルで苦手だという人にプログラミングは勧められないかも。

キャリアパス

キャリアパスというのは表現が良くないかもしれないが、他の言い方を思いつかなかったので、暫定的に。

  • a. ちょっとしたWebのシステムを作るために(たとえば)PHPに挑戦し、同じところをうろうろ
  • b. きっかけはどうであれ、基本的なところからきちんと勉強して、様々なプログラミングを満喫

問題: 動けばいいと思っているプログラマ

自分の作業とか研究とかのために自分だけが使うのであれば、問題なし。
他人に使ってもらおう、お金をもらおう、というなら、それじゃ続かないだろう。

問題: 動けばいいと思っている発注者(たとえば経営者)

何かトラブルが起きて、たぶん問題に気づいて、それなりの対策をとるはず。
国益を損なう大きな問題かもしれないが、ここでは無視する。

問題: 困った仕様

PHPの仕様が変だとか、Webの仕様がセキュアで無いとか、いろいろあるが、うまくフレームワークを用意してやって、その上でできることだけやっている間は、特に困ることは無いはず。

何を学ぶべきか?

  • 1st
    • 短いサンプルコードをたくさん眺める
  • 2nd: 文法を学ぶ
    • サンプルコードを見ながらが学ぶ
    • 定数、変数、型、宣言、参照、制御構造、サブルーチン、データ構造
    • 正規表現、クラス、例外処理
  • 3rd: テクニック