2010年12月24日金曜日

C-u M-x shell とその省力化

Emacs の中で shell というコマンドをよく利用する。仕事で使うOSはWindowsだけど、安易にマウスに手を伸ばさないで、目を閉じて、GNUのコマンド群やPerlのワンライナーを頭の中で組み合わせて、打鍵して、実行する。

その shellを使うにあたり、ただ単にM-x shellと実行すると、*shell*バッファが1個だけしか使えないのだが、並行作業が多いので複数のバッファを使いたくなる(顧客ごと、プロジェクトごとのバッファ)。

複数の *shell* を開くには、いわゆる「前置引数(prefix argument)」を使って shellコマンドを実行する。具体的には次のようにキーを打つ。

C-u M-x shell[Enter][Enter]

この結果、*shell*, *shell*<2>, *shell*<3>,...という具合に番号付きのバッファが順次生成されてゆく。

仕事上の要求はこれで満足されるが、それにしてもC-u M-x shell[Enter][Enter]と毎回打っていると面倒になってくる。なので、この操作をもっと省略するにはどうすればいいかを考える。

最初、 (shell) をラップする関数を定義して、それにキーを割り当てるという方法を思い付いたが、どうも前置引数関連の関数/変数の仕様がよく理解できず挫折( (universal-argument), current-prefix-arg など )。

仕方が無いので、以下のようにキーボードマクロで実現することにした。

1. C-u M-x shellをマクロとして記録

次のようにキーを打つ。

C-x ( C-u M-x shell[Enter][Enter] C-x )
2. キーボーマクロに名前を付ける

名前はとりあえずたった一文字の「s」にすることにして、次の式を評価。

(name-last-kbd-macro 's)

この時点で、M-x s[Enter] を実行するとシェルが複数起動するようになるはず。

3. キーボードマクロの永続化
(insert-kbd-macro 's)

*scratch*バッファで上の式を評価すると、下のような出力が得られるはず。

(fset 's
   [?\C-u ?\M-x ?s ?h ?e ?l ?l return return])

これがキーボードマクロの定義。今後も使用するため、 .emacs, init.el 等に貼り付けておく。

2010年12月16日木曜日

IE5.5 で MochiKit のセレクタ(MochiKit.Selector)を使うときに

ほとんどの仕事でIE6以上がサポート対象となるのだが、まれにIE5.5もサポートしなければならないこともある。

IE5.5で何が困るかと言うと、JavaScriptライブラリである。普段はもっぱら jQuery を使ってJavaScriptを書くのだが、残念ながらIE5.5ではエラーが発生してしまう。

ちょっとしたスクリプトなら組み込みのJavaScript関数だけで対応するが、それが効率的でない場合は jQueryに代わるライブラリを導入する。JavaScriptのいわゆる「軽量ライブラリ」はたくさんあるのだが、一体何を使うか? 自分の場合は MochiKit を使う(IE5.5でエラーが発生しないのと、むかし使ったゆえの惰性もある)。

そのような状況で、つまり MochiKit と IE5.5 の組み合わせで頻繁に犯してしまうミスがあるので備忘のため書いておく。

Classセレクタ(Class Selector)は要素もセットで記述するべし

たとえばHTMLドキュメントに foo というclassをもつ div 要素があったとして、

NG
  MochiKit.DOM.addLoadEvent(function(){
    alert($$('.foo').length);
  });

このコードでは結果が 0 になってしまう。なぜか .fooだけでは目的の要素が取得できないのだな。

OK
  MochiKit.DOM.addLoadEvent(function(){
    alert($$('div.foo').length);
  });

セレクタに、要素 div も指定すると期待通りに 1 が得られる。

2010年12月15日水曜日

Redirectディレクティブのパラメータの形式

ある日サーバーの乗り換えを実施したところ、一部のURLで404エラーが発生するようになってしまった。

原因は、乗り換え前のサーバーとApacheのバージョンが変わってしまってリダイレクトが効かなくなったため。根本的には、 .htaccess 内のRedirectディレクティブで形式エラーが発生したためであった。

エラーのログ(apache の error_log)

[Fri Dec 10 20:18:06 2010] [alert] [client xxx.xxx.xxx.xxx] /foo/bar/baz/.htaccess: Redirect to non-URL, referer: http://example.com/index.html

.htaccess で Redirect to non-URL ですよ、と。

.htacces の修正

修正前
Redirect permanent /foo.html /foo/index.html

最後のパラメータ(リダイレクト先)がURLの形式になっていなかった。乗り換え前のサーバーではこれで問題なかったのだが…

修正後
Redirect permanent /foo.html http://example.com/foo/index.html

httpスキームとホスト名を追加してURLの形式にしたらOK。ホスト名を指定してしまうとWebサイトのテスト工程などで不便になるのだが、やむをえず。

Redirectディレクティブの仕様を調べてみる

ApacheのドキュメントからRedirectのSyntaxを引用:

Redirect [status] URL-path URL

リダイレクト元を表すURL-pathとリダイレクト先を表すURLの形式については、バージョン1.3, 2.0, 2.2 ごとに仕様記述が変化している(mod_alias は比較的素朴で安定したモジュールだと思われるが、それでも変化している)。仕様を読む限り、バージョン2.2からリダイレクト先の記述でスキームやホスト名を省略できることになったようだ。

Apache 1.3 の場合
パラメータ URL-path, URL の形式については特に記述が無い。
Apache 2.0 の場合
URL について、「スキームとホスト名で始まる完全なURLであるべし」と記述されている。
The new URL should be an absolute URL beginning with a scheme and hostname
この文で、"an absolute URL" という英語に「絶対URL」という用語をあてる人もいるが、混乱を招くようだ。"absolute"は"complete"でもあるから「完全なURL」としておいたほうが適切と思われる。
Apache 2.2 の場合
URL について、さらに「しかし、スラッシュで始まるURL-pathも使用できる。その場合は現在のサーバーのスキームとホスト名が付加される」と追記されている。
, but a URL-path beginning with a slash may also be used, in which case the scheme and hostname of the current server will be added.

この文で"URL-path"の書体はイタリックではない。よって一般的な用語としての"URL-path"であり、SyntaxにおけるURL-pathではないと思われる。

2010年12月12日日曜日

Windows7 に C/Migemo & migemo.el を導入

Emacsで、いわゆる日本語インクリメンタルサーチを実現する。

日本語インクリメンタルサーチつまり Migemo の辞書検索モジュールには Ruby/Migemo と C/Migemo の二つの実装がある。ここでは高速かつRuby不要という点から後者を利用する。

前提としている環境

  • Windows 7 Home Premium 64bit
  • Cygwin 1.7.7
  • GNU Emacs 23.1.1 (i386-mingw-nt6.1.7600)

※Cygwinのバージョンは uname -a コマンドを打つと確認できる。

導入作業

辞書ファイルをインストール

KaoriYa.net より「バイナリ(DLL) 1.3 64bit版 for Windows (約3.46MB)」をダウンロード。

解凍すると、/dict/utf-8 に辞書ファイルが計5個生成される。これ(UTF-8の辞書)を emacs向けのフォルダに置く。ここでは、 ~/.emacs.d/site-lisp/migemo/ に置くこととする。

$ cd ~/download  #ダウンロードしたファイルがある場所へ移動
$ unzip -q cmigemo-1.3-w64dll.zip
$ ls cmigemo-1.3-w64dll/dict/utf-8/
han2zen.dat
hira2kata.dat
migemo-dict
roma2hira.dat
zen2han.dat
$ mv cmigemo-1.3-w64dll/dict/utf-8 ~/.emacs.d/site-lisp/migemo/
C/Migemo の dllファイル および exeファイル を作る

さらに「ソースコード 1.3(予定) 開発版」をダウンロード。

Cygwinを起動して、以下のようにコンパイル&設置を行う。

$ cd ~/download  #ダウンロードしたファイルがある場所へ移動
$ tar xvjf cmigemo-1.3c.tar.bz2  #解凍
$ cd cmigemo-1.3
$ make cyg  #コンパイル
$ cp build/{cygmigemo1.dll,cmigemo.exe} "C:\cygwin\bin"  #dll と exe を cygwin/bin に設置
migemo.el のインストール

C/Migemo を emacs から実行するための elisp が "migemo.el".

オリジナルのソースコードは積極的にメンテナンスされていないようなので、どなたか分からないが個人的にElispを公開しているかたのものを使うことにする。

sakito / dot.emacs.d / source – Bitbucket よりダウンロードして、load-pathに置く(~/.emacs.d/site-lisp/など)。

emacs の*scratch*バッファを使うなどして下記elispコードを評価。

;; migemoの設定
(setq migemo-command "cmigemo")
(setq migemo-options '("-q" "--emacs" "-i" "\a"))
(setq migemo-dictionary (expand-file-name "~/.emacs.d/site-lisp/migemo/utf-8/migemo-dict"))
(setq migemo-user-dictionary nil)
(setq migemo-regex-dictionary nil)
(setq migemo-use-pattern-alist t)
(setq migemo-use-frequent-pattern-alist t)
(setq migemo-pattern-alist-length 1000)
(setq migemo-coding-system 'utf-8-unix)
(load-library "migemo")
(migemo-init)

動作のテスト&設定の永続化
"C-s"で日本語インクリメンタル検索が動作するか試す。問題がなければ上のelispコードを .emacs や init.el に保存しておく。

2010年12月10日金曜日

お客様のIPアドレスを教えてください、というときに

毎日仕事をしていると、顧客が利用しているPCあるいはそのネットワークのIPアドレスを調べなければならない時がある。たとえば、顧客自身によるサイトアクセスをGoogle Analytics で計上しないよう設定する仕事。この場合、顧客のIPアドレスを教えてもらって、GAの管理画面で設定を行う。

「IPアドレスを教えてください」の一言で済めばよいのだが、顧客によってはIPアドレスなんてまったく知らない。ネットワークについていろいろ説明するのが面倒だし、社内SEの人に聞いてもらうのも何だから、とりあえずアクセスしてもらってApacheのアクセスログから調べようか、なんて泥臭い方法も思い浮かぶのだが、やっぱり顧客自身に調べてもらいたい。

そこで、シンプルな無料サービスを使って調べてもらう。

顧客向けメールのテンプレ

次の内容をメールで送信して、調べてもらう。

IPアドレスを調べる手順です

1. http://www.iphiroba.jp/index.php を開いてください

2. [環境変数チェック] をクリックしてください

3. [簡易版] をクリックしてください

4. [リモートアドレス(IP) REMOTE_ADDR] の欄にある数字が「IPアドレス」になります。
   例:192.168.0.1

5. これをコピー&ペーストし、メールでご返信ください

2010年12月2日木曜日

SWFのメタデータ(幅や高さ等)を調べるPerlモジュール

以前書いた Technical Memorandum: FLVのメタデータ(動画の幅や高さ等)を調べるPerlモジュール と同じような内容…

SWFの場合は、CPANにある「SWF::Header」を使う。

プログラム例

#!/usr/bin/perl
# swfinfo.pl
# 引数で指定されたSWFファイルのメタデータを出力する
# 前提:SWF::Headerがインストールしてあること(CPANにある)
use SWF::Header;
use Data::Dumper;
my $h = SWF::Header->read_file($ARGV[0]);
print Dumper($h);

実行例

$ perl ./swfinfo.pl ./foo.swf
$VAR1 = {
          'width' => 940,
          'count' => 50,
          'version' => 9,
          'background' => '#000000',
          'duration' => '4.16666666666667',
          'height' => 200,
          'xmin' => 0,
          'rate' => 12,
          'ymax' => 4000,
          'signature' => 'CWS',
          'filelen' => 124058,
          'ymin' => 0,
          'xmax' => 18800
        };

2010年11月30日火曜日

Emacs文字コード判別の優先順位を変える

Emacsで日本語を含むファイルを開くと、まれにエンコードの自動判別に失敗することがある(仕事上の経験では、utf-8で書かれたcssファイルをsjisと勘違いすることが多い)。

そうした場合は、2つの関数 prefer-coding-system, find-alternate-file を使ってファイルを読み込み直す。

エンコードを変えてファイルを開き直すときのキー入力(例)

;utf-8-dos の優先順位を上げる
M-x prefer-coding-system [RET] utf-8-dos
;find-alternate-file関数を実行。現在のバッファを削除して読み直す
C-x C-v foo.txt

面倒だからこの2つをラップする関数を作ったほうがいいかも。

2010年11月29日月曜日

Lisp の every と some 風の関数を JavaScript で

Common Lisp や Emacs Lisp 等では every や some という高階関数が提供されており、複数の値に対するテストを簡潔に記述することができる(述語関数/predicateの、リストへの適用)。

Lispの例

  (every #'evenp '(1 2 3)) => nil        ; 偶数でない要素があるため偽(nil)
  (some #'evenp '(1 2 3)) => t           ; 偶数の要素が少なくとも1個あるため真(t)

ここで、evenp は引数が偶数の場合に t を返す組み込み関数

JavaScriptの例

JavaScriptで同様のことを実現したいという場合は、次のような形になるはず。

// 関数定義
function every(p, arr){
  for (i = 0, n=arr.length; i < n; i++) {
    if(!p(arr[i])) return false;
  }
  return true;
}
function some(p, arr){
  for (i = 0, n=arr.length; i < n; i++) {
    if(p(arr[i])) return true;
  }
  return false;
}
function evenp(x){
  return (x%2==0) ? true : false;
}
// 実行
every(evenp, [1, 2, 3]);   // => false
some(evenp, [1, 2, 3]);    // => true

こんな感じ。

実は every, some が組み込まれているブラウザもある

念のため調べたところ、Firefox(Gecko 1.8b2 以降)にはこれらの関数が配列のメソッドとして実装されているとのこと。

everyの利用例(Mozilla Developer Center より転載)

function isBigEnough(element, index, array) {
  return (element >= 10);
}
var passed = [12, 5, 8, 130, 44].every(isBigEnough);
// passed は false

mozilla.orgのページにある「互換性」のコードを利用すれば、Firefox以外のブラウザでもeveryが利用できるようになる。

2010年11月25日木曜日

VBAでeval(Evaluate関数)を使ってみる

2010年現在でも、中小零細企業では表計算から請求書や納品書等の書類にいたるあらゆる業務で Microsoft Excel が活躍する。したがって、Excel VBA でのプログラミングをすることも多い。

VBA の場合、セルにデータを入力しておくことでプログラムを制御するのが一つのマイベストプラクティス。たとえば一番最後のワークシートにプログラム上の定数を記載しておく。後日ユーザーがプログラムの挙動を変えたくなったらそのシートをユーザー自身に編集してもらうことで、自分の仕事を減らせる。

この考えをもう一歩進めると、データだけではなく「手続き」や「関数」までもセルに記述しておきたくなる。たとえばある金額の計算過程で現在は 5%増しの計算をしているのだが、来年になったら (8%増し + 100円) に変えたい、といった具合である。

以下ではこの例題をサンプルとして、変更を見越した関数を設計し、VBAの "Evaluate"関数と組み合わせることによって目的が達成できることを示す。

計算関数を定義する

引数は2つ(利率、上乗せ固定額)

Function f計算(利率、上乗せ固定額)
    f計算 = 金額 * (100 + 利率) / 100 + 上乗せ固定額
End Function

VBAでは日本語が利用可能なので、変数名や関数名になるべく日本語を使う。

グローバル変数を定義する

上記の関数内には、自由な変数、つまり仮引数として定義されていない変数「金額」がある。この値は実行時にワークシートのセルに入力されているデータであるのだが、残念ながら上記の関数内から参照することができない(Evaluateにより実行されるコードからはセルの値を"Cell(1, 1)"というような形で参照できない)。したがって関数実行前にセルから読み込み、グローバル変数に設定しておく。そのための入れ物を定義するのが次のコード。

Public 金額 As Long

このPublic文は標準モジュール等のトップレベルに書く。

計算を行うマクロを定義する

  1. グローバル変数に、名前付きセル「入力セル」を代入
  2. 「関数セル」に入力されているコードを適用
  3. 結果を「出力セル」に入力

Sub Macro1()
  金額 = Range("入力セル").value
  計算結果 = Application.Evaluate(Range("関数セル").value)
  Range("出力セル").value = 計算結果
End Sub

ワークシートのセルに必要なデータおよび関数呼出しコードを書く

  1. 任意のセルに「1000」と入力し、セルの名前を「入力セル」とする
  2. 〃 「f計算(5, 0)」と入力し、 〃 「関数セル」とする
  3. 任意のセルを選択して、セルの名前を「出力セル」とする

マクロを実行

  1. [f8]キーを押すなどしてマクロ「Macro1」を実行
  2. うまくいけば「出力セル」に 1050 と出力される

計算を変えてみる

  1. 「関数セル」の内容を「f計算(8, 100)」に変更
  2. マクロを実行
  3. うまくいけば「出力セル」に 1180 と出力される

まとめ

以上で、セル内のプログラムコード書き換えによりプログラムの挙動を変えることができるようになった。

「f計算」以外にもっと関数を定義してやることで、「関数セル」に書けるコードも自由度が増していくはず。

ただし、入力データを参照するためにグローバル変数が必要になるのであまりきれいとは言えない。

また、( )を多用したようなコードをユーザーに書き換えてもらうのは難しいから、変更はプログラム担当者の仕事になるだろう。

2010年11月22日月曜日

tableのtd要素にエクセルのセル番号のようなclassを設定するJavaScript

ここで「エクセルのセル番号」というのはつまり"A1"とか"AZ1"のような列と行を表す文字列のこと。

※「セル番号」よりももっと正式な名称があると思う。

Webサイト制作においては、tableを構成するすべてのセルに対してそれぞれCSSで柔軟にデザインを変えたいというケースが稀にある。そうした場合に手っ取り早いのは各セルにセル番号のような文字列をクラスとして設定してあげること。そして、css内で td.A1{ ...; } ... td.AZ1{ ...; } のようなスタイルを指定する。

小さいtableなら手動でやってもよいが、大きくなるとJavaScriptで動的に設定することになる。以下がそのためのJavaScriptの例。

<script type="text/javascript">
// jQuery等のライブラリ使わないコードです(仕事上、そういう前提だったので)
// 関数定義
function setClassToTd(){
  var alphas = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  var tbls = document.getElementsByTagName('table');

  // html内のすべてのtable要素を処理する
  for(var i=0, n=tbls.length; i<n; i++){
    var trs = tbls[i].getElementsByTagName('tr');
    for(var j=0, m=trs.length; j<m; j++){
      var tds = trs[j].getElementsByTagName('td');
      for(var k=0, l=tds.length, cls; k<l; k++){
        if(k < alphas.length){
          cls = alphas.charAt(k);
        }else{
          cls = charAt(Math.floor(k / alphas.length) - 1) + charAt(k % alphas.length);
        }
        cls += j + 1;
        // 既存のclass設定を消さないように
        var clsbuf = tds[k].getAttribute('class')||tds[k].getAttribute('className');
        if(clsbuf){
          cls += ' ' + clsbuf;
        }
        tds[k].setAttribute('class', cls);
        tds[k].setAttribute('className', cls); //IE
      }
    }
  }
}
// loadイベントで実行 
if (window.attachEvent) { //IE
  window.attachEvent("onload", setClassToTd);
}else{
  window.addEventListener('load', setClassToTd, false);
}
</script>

2010年11月9日火曜日

FLVのメタデータ(動画の幅や高さ等)を調べるPerlモジュール

仕事では、FLVファイルをサイトに掲載してください、という類の要求がしばしばある。

大抵の場合、FLVファイルだけポンと渡されるので、実際にFLVを掲載するために必要な「幅」「高」さといった情報はもらえないことが多い。そのくらいメールに付記して欲しいと思う反面、渡す側の人間にすればそんな細かい情報が要るとは見当がつかないだろうから、この現象は仕方がないということで自ら調べることになる。

Perlを利用する場合は、「FLV::Info」というモジュールで簡単に調べられる。なお、インストールはCPANシェルから特に問題なく行うことができた(CentOS5.3での話)。

メタ情報出力プログラム例

#!/usr/bin/perl
# flvinfo.pl
# 引数で指定されたFLVファイルのメタデータを出力する
# 前提:FLV::Infoがインストールしてあること(CPANにある)
use FLV::Info;
my $reader = FLV::Info->new();
$reader->parse($ARGV[0]);
my %info = $reader->get_info();
print "$info{video_count} video frames\n";
print $reader->report();

実行例

$ perl ./flvinfo.pl foo.flv
748 video frames
File name          foo.flv
File size          1082585 bytes
Duration           about 24.925 seconds
Video              748 frames
  codec            Sorenson H.263
  height           240
  type             interframe/keyframe
  width            320
Audio              954 packets
  format           MP3
  rate             44100 Hz
  size             16 bit
  type             stereo
Meta               1 event
  audiocodecid     2
  audiosamplerate  44100
  audiosamplesize  16
  duration         24.958
  filesize         1082585
  framerate        29.97002997003
  height           240
  stereo           1
  videocodecid     2
  width            320

2010年11月8日月曜日

wgetによるHTTPSダウンロードでありがちなエラー

今日、Cygwinでwget使ったときにエラーになった。Webブラウザと違ってwgetではCAの証明書がインストールされていないから、HTTPSサーバー側のSSL証明書を検証できない。検証できないとエラーになってダウンロードもできない、というのが仕様(割と厳格な仕様)。

このエラーを警告レベルに落として、ダウンロードを成功させるというオプションがあるので、これを有効にすればダウンロードが成功する。

$ wget --no-check-certificate https://example.com/foo.jpg

※あくまで急場しのぎ。理想的なのは、CA証明書をブラウザ等からエクスポートしてあげて `--ca-certificate=file' というオプションでそのファイルを読み込む方法だと思う。

2010年11月5日金曜日

htmlでルビを振る

漢字の読み仮名等を表示したいときに使うruby要素。

ruby, rb, rp, rtという4つもの要素を組み合わせて記述するのがスタンダードらしい(それぞれ、ruby, ruby base, ruby parenthesis, ruby text を表す)。

例:青空文庫の「夢十夜」(夏目漱石)

腕組をして枕元に<ruby><rb>坐</rb><rp>(</rp><rt>すわ</rt><rp>)</rp></ruby>っていると、<ruby><rb>仰向</rb><rp>(</rp><rt>あおむき</rt><rp>)</rp></ruby>に寝た女が、静かな声でもう死にますと云う。

確認したところ、Firefox3.6はルビ表示をサポートしておらず、rbの内容の直後にrp, rtの内容がそのまま表示される。IE6-IE8, Chrome7.0, Safari5.0はOK。

HTML5では、rb(ruby base)を記述しなくてもよいようだ。

2010年11月4日木曜日

Apache 利用可能なモジュールを確認するためのコマンド

Apacheがすでに運用されており、どのモジュールがどのように組み込まれているか知りたい、という状況で使うコマンド。

DSO (Dynamic Shared Object) として動的にロードされているモジュールを表示
# /usr/sbin/httpd -M
Loaded Modules:
 core_module (static)
 mpm_prefork_module (static)
(略)
 proxy_ajp_module (shared)
 ssl_module (shared)
Syntax OK
Apacheコンパイル時に静的に組み込まれたモジュールを表示
# /usr/sbin/httpd -l
Compiled in modules:
  core.c
  prefork.c
  http_core.c
  mod_so.c 

※上記コマンドを実行javascript:void(0)した環境は、ディストリビューション:CentOS-5、Apache:2.2.3(httpd-2.2.3)

Mercurialプロジェクトを作成してコミットするシェルスクリプト

テストを実行できる程度の成果物ができた時点で、プロジェクトを作成しコミットすることが多い。

そこで以下のようなスクリプトを使うことにした。些細なものだが、hg init、hg commit 等を記憶&打鍵する手間が省けるだろう。

$ cat hginit.sh
#!/bin/sh/ -x
# デバッグフラグ -x を付けている
# 引数無しならカレントディレクトリをプロジェクトのディレクトリとみなす
dir=.;
if [ $# -ge 2 ]; then
  echo 'Too much arguments; Usage: hginit.sh DIR';
  exit 1;
elif [ $# -eq 1 ]; then
  dir=$1;
fi

cd $dir
hg init
hg commit -A -m 'Initial commit'
exit 0;
$ ./hginit.sh myproject

2010年11月1日月曜日

Maildir形式のメールボックスと procmail の組み合わせ

postfix等を使う場合に、昨今はメールボックスの形式をMaildir形式にするのが一般的なのだが、procmail はデフォルト状態でmbox形式のメールボックスを想定してメールの配送を行う仕様になっている。

これを知らないでprocmailによる転送を行っていると、「転送先には届くのだが、元のメールボックス(Maildir形式)にメールが保存されない。調べたらmbox形式でspoolの中に保存されていた」という事態が起こる。

procmailで Maildir形式を使いたい場合は、.forward および .procmailrc の中にその旨を指示しておく必要がある。

~/.forward
"|exec /usr/bin/procmail -f- || exit 75 #~/Maildir/"
~/.procmailrc
MAILDIR=$HOME/Maildir/
DEFAULT=$MAILDIR
…レシピを書く部分…

.forward の中で # が付いている部分、つまりまるでコメントのように見える部分に意味がある情報を書くのがすごいところ。

2010年10月29日金曜日

applyメソッドの素朴な実用例

2010年現在、JavaScript関連の仕事ではほとんどの場合においてjQueryライブラリを使うわけだが、いわゆるレガシー案件というのだろうか、環境がそれを許さない場合もある。

そういった場合、DOMの書き換え処理を素朴な方法、つまりgetElementByIDなどDOMの組み込みメソッドを使った方法で実現することになる。本当に小さいプログラムなら気にならないかもしれないが、似たようなことを何度も書くことになるので自分でちょっとしたユーティリティ関数を書いておき、いろんな仕事で使い回すことになる。

そんな中にapplyメソッドを使った実用的なものがいくつかあったのでここに記録しておく。一昔前まではapplyの意味を理解できなかったけれどもう大丈夫ですよ、という記念をかねて。あとクロージャも。

DOMユーティリティ関数

// 指定されたidをもつ要素に関数fを適用
function getByIdAndApply(id, f){
  var elm = document.getElementById(id);
  if(elm) f.apply(elm);
}
// 指定されたタグをもつ要素群に関数fを適用
function getByTagNameAndApply(tag, f){
  var elms = document.getElementsByTagName(tag);
  for(var i=0, n=elms.length; i<n; i++) f.apply(elms[i]);
}

利用例

htmlを書きかえる。スタイルも変更(無駄にタイマーを使う)。

<html>
<head>
  <script type="text/javascript" charset="utf-8" src="./js/myutil.js"></script>
  <script type="text/javascript">
  window.onload = function(){

    // id=p1 なる要素のテキストを変更
    getByIdAndApply('p1', function(){
      this.innerHTML = "foo";
    });

    // 全p要素のスタイルを変更
    getByTagNameAndApply('p', function(){
      // クロージャを使う例(setTimeout内でthisを参照するため仮引数elmとして保持)
      (function(elm){ setTimeout(function(){elm.style.color='#f00';}, 2000) })(this);
    });
  }

  </script>
</head>
<body>
  <p id="p1">text</p>
  <p id="p2">text</p>
  <p id="p3">text</p>
</body>
</html>

2010年10月27日水曜日

IETesterのTips:コマンドライン起動

Webサイトを仕事で作る場合、たいていはIEの異なるバージョンで動作をテストする工程がある。その工程で役に立つのがIETesterというソフト。このソフト一つをインストールすればIE5.5以上の各バージョンでのレンダリング結果およびJavaScriptの挙動が確認できるので、色々なバージョンのIEをインストールして共存させる必要がない、開発用PCを仮想化する必要もない。

このIETesterを起動する際、GUIでボタンをクリックしてもよいのだが、何回もボタンをクリックして、同じURLを何回も入力 or コピペ する必要があるので面倒。そこで一度URLを入力したら全バージョンのIEを起動してくれるようなインターフェースが欲しくなる。以下、CUI(コマンドライン、コマンドプロンプト、DOSプロンプト)でそれを実現する方法。

1. 起動用のバッチファイルを作る

@echo on
@start "" /b "C:\Program Files (x86)\Core Services\IETester\IETester.exe" -all %1

ここで、startコマンドの第二引数"/b"は新しいウィンドウを開かないことを表す。よって第一引数(ウィンドウタイトル)も空文字列でOK。

これをファイル名 "ietest.bat" として、デフォルトのフォルダ(c:\Users\foo など)に保存しておく。

※OSは Windows 7 Home Premium。

2. コマンドプロンプトからバッチファイルを実行(引数は表示したいURL)

C:\Users\foo> ietest.bat http://host.domain/

(補足)BASIC認証をかけてるサイトなら id, password を引数に含めればOK

C:\Users\foo> ietest.bat http://id:password@host.domain/

2010年10月26日火曜日

mousemoveイベントのブラウザごとの違い(IE&ChromeとFirefox)

マウスポインタをちょっと愛らしくしたい、なんて仕事がたまにあると思う。画像を変えて、マウスに少し遅れて追従するような動きをさせる、とか。

例えば以下のサイトのように。

これらのサイトをいろいろなブラウザで閲覧して気づいたのだが、Firefoxはほかと挙動が違う。具体的には、ページを読み込んでからマウスを動かすまで、マウスポインタに画像が追従しない。試しにFirefoxでリロードしてからマウスを動かさずにただ眺めてみよう。

これがなぜなのか少し試行錯誤してみたところ、Firefoxだけは人が実際にマウスを動かすまでは有効なmousemoveイベントオブジェクト(= clientXなどの座標値が正しくセットされたmousemoveイベントオブジェクト)が発生しないからのようだ。反対に、Firefox以外の場合は何もしなくても mousemoveイベントが定期的に発生するため、適当なタイミングでその座標値を取得し、画像を設定した要素のスタイル(position:top, position:left)を書き換えることで画像をマウスのところまで移動させることができる。

実験として次のようなhtml+スクリプトを読み込んでみる(jQuery利用)。マウスを動かさずにじっとしていれば、ブラウザの違いが確認できる。

<html>
<head>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript">
$(function(){
  var cnt = 0;
  function f(e){
    $('#info').text(cnt + ' : ' + e.clientY);
    cnt++;
  };
  // mousemoveイベントに上の関数をバインド
  $('body').mousemove(f);
});
</script>
</head>
<body>
<p id="info"></p>
</body>
</html>
IE, Chrome の場合は、
ロードしただけでmousemoveが定期的に発生(カウンタ表示が1秒ごとくらいに増えていく。マウスを動かしていないのに…)
したがってマウス座標(mousemoveイベントオブジェクトのclientYプロパティ)が取得できる
Firefox(3.6)の場合は、
待っていても mousemove が発生しない。座標が取れない。

待ってもイベントが発生しないのであれば、強制的に発生させてみたらどうか? そこで、上のスクリプトを1行だけ変えてtrigger()を追加してみる。

(略)
  // mousemoveイベントに上の関数をバインド。さらにtrigger()で強制的にイベントを発生させる
  $('body').mousemove(f).trigger("mousemove");
});
</script>
(略)
この結果は、
0 : undefined と表示されて終了。triggerで発生させたイベントオブジェクトには座標値がセットされないらしい。

というわけでFirefoxの場合だけ、マウスが動くまでマウス用画像は座標(0, 0)の位置で待機するという仕様になりました…

2010年10月25日月曜日

無名関数(匿名関数)を再帰的に呼び出す

JavaScriptでは、無名関数の中で "arguments.callee()"を実行することにより、無名関数を再帰的に呼び出すことができる。

※Javaをやる人の間では匿名関数と呼ぶのが主流だが、JavaScriptをやる人は無名関数と呼ぶ場合が多い?

例:ID "foo" をもつDOM要素の、5階層以上の親に当たるTABLE要素を探し出してstyle属性をクリアする。

<script type="text/javascript">
(function(id){
  var elm = document.getElementById(id); // 指定されたidをもつ要素

  if(elm){
    var cnt=0;
    var target = (function(o){ // 再帰的な無名関数
      if(cnt >= 5 && o.tagName == 'TABLE') {
        return o; // 条件を満たす要素
      } else {
        cnt++;
        return arguments.callee(o.parentNode); // 親要素に対し無名関数を適用
      }
    })(elm);
    target.style.cssText = ''; // 目的の要素のスタイルを変更
  }

})('foo');
</script>

2010年10月21日木曜日

再帰によるべき乗(累乗)の計算をいくつか

再帰によりxのn乗を求める。すごく初歩的な問題だと思うが、あらためて。

n回再帰
(defun power (x n) "X to the Nth power"
  (if (<= n 0)
      1
    (* x (power x (- n 1)))))
power
(power 2 8)
=> 256
別解その1: 2乗を利用して再帰呼び出し回数を減らす
(defun power (x n)
  (cond
   ((<= n 1) x)
   ; nが偶数の場合にnを半減させる
   ((evenp n) (funcall (lambda (x) (* x x)) (power x (/ n 2))))
   (t (* x (power x (- n 1))) )))
別解その2: さらに再帰呼び出しを減らす(Perlで書いてるけど)
sub pow3{
    my ($x, $n) = @_;
    if($n < 1){
        1;
    }elsif($n % 2 == 0){
        (sub { my $x=shift; return $x*$x; })->( pow3($x, $n / 2) );2
    }else{
        # nが奇数の場合
        $x * (sub { my $x=shift; return $x*$x; })->( pow3($x, ($n - 1) / 2) );
    }
}

たいていの人は最初の解法で満足するだろう。が、"Paradigms of Artificial Intelligence Programming" という本には2番目の解法が載っていた。すごい人は些細な問題に対してもこだわりが違うのだな、と。

2010年10月18日月曜日

lftp コマンド省略機能

FTPクライアントの"lftp"では、いくつかのコマンドを省略することができる。

  • chmod => ch
  • lcd => lc
  • lpwd => lp
  • pwd => pw

たった2、3文字だけれど、、、キータイプの量が減る。

また、省略しすぎてコマンドを特定できない場合は、以下のようなエラー(ambiguous command)となる。

lftp foo@bar.baz.org:/html> p
Ambiguous command `p'.

2010年10月14日木曜日

sed ファイル置換+バックアップ

簡単な文字列置換には手軽さゆえ sed を使ってしまう。

ファイルの中身を置換する場合、リダイレクトでテンポラリファイルを作って mv するという手順を踏んでいたが、これは無駄。オプション -i を使うことによりファイルをsedだけで(in placeに)置換できるのだった。

$ sed --help
...
  -i[SUFFIX], --in-place[=SUFFIX]
                 edit files in place (makes backup if extension supplied)

ヘルプに記述されているように、"-i"の直後にextension(拡張子)を指定すればバックアップファイルも自動生成される。

バックアップ不要のとき
sed -i 's/foo/bar/g' /tmp/baz.txt
拡張子".orig"でバックアップをとる
sed -i.orig 's/foo/bar/g' /tmp/baz.txt
応用:findと組み合わせる
find . -type f -name '*.txt' -exec sed -i.orig 's/foo/bar/g' {} \;

2010年10月7日木曜日

Mercurial(hg)でどのファイルを変更したか調べる

hg diffコマンドに -c と --stat を指定すればOK。

$ hg diff -c 26 --stat
 foo/bar.pdf                     |    0 
 foo/index.php                   |    8 +++++++-
 foo/bar/baz/search.php          |    2 +-
 foo/bar/baz/user.php            |    2 +-
 4 files changed, 9 insertions(+), 3 deletions(-)
  • -c でリビジョン番号を指定。上の例では26。
  • --stat で"diffstat"形式のサマリー表示。ファイル名のほか、追加行数(insertions)、削除行数(deletions)もわかる。

2010年10月5日火曜日

モンテカルロ法による円周率の近似(Emacs Lisp)

こちらの記事「モンテカルロ法による円周率の近似」を Elisp で真似てみただけ(Haskellは今のところさっぱりわからない)。

;; 平面内の点の座標(1未満)
(defun x ()
  (mapcar #'(lambda (x) (/ x 10000.0))
          (list (random 10000) (random 10000)) ))
x
;; 点と座標中心との距離
(defun d (x)
  (sqrt (+ (* (elt x 0) (elt x 0)) (* (elt x 1) (elt x 1)))) )
d

;; n個の数列を生成して円内にある割合を計算(数列を全部リストで持つのでだいぶ富豪的)。
;; 整数での除算が丸められるので (* n 1.0) の様にしないといけない。
(defun my-calc-pi (n)
  (* 4 (/ (apply #'+
                 (mapcar #'(lambda (x) (if (< (d x) 1) 1 0))
                         (let ((seq) (i 0))
                           (while (< (length seq) n)
                             (setq seq (cons (x) seq))
                             (incf i) )
                           seq )))
          (* n 1.0) )))
my-calc-pi
;; 10,000個の点を生成した場合
(my-calc-pi 10000)
3.1524

「モンテカルロ法」という言葉を久しぶりに目にして何だか高揚した。学生気分を取り戻した感じ。

2010年9月29日水曜日

Mewで送信するメールのMessage-IDを変える

中小零細企業でインターネットにかかわる仕事をしていると、まったく面識もない人からメールが来る(よくわからない問合せだったり、クレームだったり、営業だったり)。

会社の仕事なのでスルーできないが、なるべくこちらの情報を提出したくない、という場合にメールヘッダを無意味なものに書き換える必要が生じる(自意識過剰気味)。

※SMTPサーバーが処理するエンベロープまでは手が出せないので、MUAでできる範囲。

  • Fromは編集時にバッファで書き換えればいい
  • Mewによって自動生成される「Message-ID」は、以下のように2つの変数の値を変更すればいい(マニュアルを読めば書いてある)
    (setq mew-smtp-msgid-user "no-reply")
    (setq mew-smtp-msgid-domain "no-reply.co.jp")
    

こうすると、

Message-Id: 01234567.012345.012345678.no-reply@no-reply.co.jp

という形式のMessage-IDが生成される。

2010年9月28日火曜日

JavascriptでUNIXタイムスタンプを得る

こちら↓の記事そのまんま。

Get a UNIX timestamp with Javascript

var ts = Math.round(new Date().getTime() / 1000);

new演算子とドット「.」の評価順序を陽に示したい場合は次のように書けばいい。

var ts = Math.round((new Date()).getTime() / 1000);

用途はいろいろあるだろうけど、今回はFlash(SWFファイル)をブラウザにキャッシュさせないために使用。クエリストリングにタイムスタンプを付加して、SWFファイルのURLを毎回変化させる。次のような感じで。

   // このコードは swfobject.js(http://code.google.com/p/swfobject/)に依存しています

   var serial = Math.round(new Date().getTime() / 1000); // タイムスタンプをシリアル番号として利用
   var flashvars = { };
   var params = { allowScriptAccess: 'sameDomain', allowFullScreen: 'false', scale: 'noscale', quality: 'autohigh', wmode: 'transparent', base: '/swf', menu: 'false', salign: 't' };
   var attributes = { id:'movie01', name:'movie01' };
   swfobject.embedSWF("/swf/movie.swf?s=" + serial, "myContent", "600", "300", "9.0.0",  "/swf/expressInstall.swf", flashvars, params, attributes );

もっとトリッキーな方法

Math.roundやMath.floorを使わずに、浮動小数点数から整数への暗黙の型変換(英語もついでに覚えよう:implicit type conversion)で切り捨てを実現することもできる。

補数演算(ビット反転)による切り捨て
var ts = ~~(new Date()/1000);    //~ が補数演算子。2回作用させれば元の値に戻る
論理和演算(OR演算)による切り捨て
var ts = new Date()/1000|0;    //| がOR演算子。ゼロとのORゆえ実質的には型変換のみ行われる

2010年9月20日月曜日

Perlのプロファイラ Devel::DProf

シンプルなプロファイラ。標準モジュールなので、すぐに使える。

$ perl -d:DProf ./p.pl  ;; あるいはシェバング行に追加してもよい(#!/usr/local/bin/perl -d:DProf)
$ dprofpp
Total Elapsed Time =   0.0096 Seconds
  User+System Time =        0 Seconds
Exclusive Times
%Time ExclSec CumulS #Calls sec/call Csec/c  Name
 0.00       - -0.000      1        -      -  strict::import
 0.00       - -0.000      1        -      -  strict::bits
 0.00       - -0.000      1        -      -  warnings::BEGIN
 0.00       - -0.000      1        -      -  warnings::import
 0.00       - -0.000      2        -      -  main::BEGIN
 0.00       - -0.000     11        -      -  main::__ANON__
 0.00       - -0.000      7        -      -  main::pow3
 0.00       - -0.000     12        -      -  main::pow2
 0.00       - -0.000     64        -      -  main::pow

参考

Devel::DProf

2010年9月1日水曜日

CakePHP paginateのカスタマイズ

URLをカスタマイズ

とりあえず、"Paginator"が生成するURLを文字列置換する方式。これを共通部品にするため、app/views/elements/ の中に"element"を作成する。

app/views/elements/pagination.ctp

<?php
$prev_link = str_replace('page:', '',
                         $paginator->prev('前のページ', null, null, array('class' => 'disabled')) );
$prev_link = preg_replace('/\/1"/', '"', $prev_link);

$numbers_link = str_replace('page:', '', $paginator->numbers());
$numbers_link = preg_replace('/\/1"/', '"', $numbers_link);

$next_link = str_replace('page:', '',
                         $paginator->next('次のページ', null, null, array('class' => 'disabled')));

echo $prev_link;
echo $numbers_link;
echo $next_link;
?>

このelementを呼び出すためのコード(Viewに記述)。

  <?php echo $this->element('pagination'); ?>

Routesの変更

paginateのURLに対応するため、Routing情報を追加。

app/config/routes.php

/* pagination */
  Router::connect('/:controller/:action/:page',
                  array('action' => 'index', 'page' => null),
                  array('action' => 'index', 'page' => '[0-9]+'));

ここでconnect()の引数は、

  • URLは controller名/action名/page名 の形式になっている
  • controller名に対応するControllerが呼び出される(デフォルトのRouting)
  • action名に対応するActionではなく、強制的にindexを呼び出す(デフォルトのRoutingを変更)
  • page名はnull可(オプショナルな「カスタムRoute要素」)
  • action名は"index"という文字列にマッチしないといけない
  • page名は半角数字列にマッチしないといけない

というような意味。

2010年8月31日火曜日

PHPアップロードファイルサイズ

XOOPSの仕事。

アップロードできるファイルサイズの上限値を変えたいという話。

XOOPSの管理画面で変更するほか、PHPのオプションも変えないといけません。

.htaccess

php_value upload_max_filesize 10M
php_value post_max_size 10M

マニュアルでオプションの説明に"PHP_INI_ALL"、"PHP_INI_USER"、"PHP_INI_PERDIR"のいずれかが書いてあれば .htaccessで変更可能。"PHP_INI_SYSTEM"だと不可能(php.ini, httpd.confレベル)。

前提として、Apacheのhttpd.confで "AllowOverride Options" が有効になっていること。

2010年8月30日月曜日

XML::Parserが動かない問題でハマる

PerlでXMLをあつかう場合、XML::Simpleを仲介者としてXML::Parserを利用していた。しかしサーバーによってはXML::Parserがインストールされていない&インストールできないので、エラーになってしまう。

  # xmlを読み込む
  $XML::Simple::PREFERRED_PARSER = 'XML::Parser';  # この行をコメントアウトすると動いたが、UTF8フラグ関係の動きが変わってしまって文字化け。具体的にどのモジュールがParseを行っているのか不明なのでどこをどうすればいいのやら(プロファイルをとればわかるだろう)
  my $xmlobj = XMLin($res->content);

しかたなく binmode STDOUT, ":utf8"; を取ったら日本語も化けなくなった(今のところ謎)。

2010年8月17日火曜日

アコーディオンメニュー

L社のWebサイト案件にて。

jQuery UI を使わないで自作(プラグイン化)。

HTMLの構造は違うのだけど、下記サイトを参考にした。

参考
jQueryでアコーディオンメニューを作成する | TRIPLEXXX

HTML構造が変&要素が巨大なのでマウスオーバーイベント駆動かつアニメーションにすると振動が発生してしまった。よってクリックイベント駆動にしておいた。

2010年8月15日日曜日

ielm: Inferior Emacs Lisp Mode

EmacsでREPLみたいなことができる、という話。

M-x ielm で起動すると *ielm* というバッファが生成される。*ielm* の中で対話的に式を評価できる。

*scratch*バッファや M-:(eval-expression) ばかり使っているから、これは知らなかった。

2010年8月10日火曜日

You can't test "EntryExcerpt" with "MTIfNonEmpty"

MovableTypeの話。

ブログ記事の「概要」(MTEntryExcerpt)の有無を判断するため、MTIfNonEmptyタグを使ったのだが、期待通りに動作せずはまってしまった。

「概要」は参照時に本文(MTEntryBody)から自動生成されるため、常に「有」となってしまうらしい。

NG
<MTIfNonEmpty tag="EntryExcerpt">
  <mt:EntryExcerpt convert_breaks="0">
</MTIfNonEmpty>
これだと、常に真と判断されてしまう。
OK
<MTIf no_generate="1" tag="EntryExcerpt">
  <mt:EntryExcerpt convert_breaks="0">
</MTIf>
MTIfを使ってno_generateという修飾子をつける。

2010年8月5日木曜日

emacs-fu: some handy key bindings

emacs-fu: some handy key bindings で紹介されていたキーバインディングのうち、今後活用したいものだけ抜粋。

  • "M-m jumps to the first non-whitespace character on the current line;"
  • "M-^ joins two lines into one – like vi(m)'s :join, except that point must be on the second line, not the first;"
  • "C-x 8 RET in a recent emacs version gives you an auto-completable list of special characters to insert."

2010年8月4日水曜日

2010年7月29日木曜日

"はなまるメールフォーム"の拡張

メールフォームが必要な場合に使っているライブラリ。PHP4で動く(5でも動くかは知らない)

チェックボックスのような複数選択項目を処理する場合は、下記のようなコードを追加すればOK。


hnmailform.php:

function initData($posts){//入力データを初期化する
...
  $keys = array_keys($posts);
  foreach ($keys as $k){
    if(is_array($posts[$k])){
      $posts[$k] = implode(", ", $posts[$k]);
    }
  }
...

html:

htmlのコード:

<input type="checkbox" id="item1" name="チェック項目[]" value="あ" checked="checked" /><label for="inqtype1">あ</label>
<input type="checkbox" id="item2" name="チェック項目[]" value="い" checked="checked" /><label for="inqtype2">い</label>
<input type="checkbox" id="item3" name="チェック項目[]" value="う" /><label for="inqtype3">う</label>

perl: warning: Setting locale failed.

Perlコマンド実行で警告が出る場合、以下のように環境変数を設定する。

$ export PERL_BADLANG=0

SSLとIEの組み合わせでファイルダウンロードができない問題

よくある問題。

XOOPSの掲示板モジュールでも発生したから、キャッシュを有効にするコードをいれた。

xoops_trust_path/modules/attachfile/include/attach_functions.php:
        header( "Cache-Control: public" );
        header( "Pragma: cache" );

2010年7月25日日曜日

EmacsキーボードマクロのTips

以下の関数を活用して、キーボードマクロの再利用を促進しよう。

  • (name-last-kbd-macro symbol): 名前をつけてM-xで呼び出せるようにする。
  • (insert-kbd-macro macroname &optional keys): この関数の出力を.emacsに貼っておけば、Emacsを終了しても大丈夫。

2010年7月20日火曜日

CakePHPでCSVをデータソースにする

参考


なお、例示されてるコードにはPHP4で動かない箇所がある。
  • "private"修飾子
  • foreach文での参照記法("&$l")

DBに接続できないというWarningが出たので"app/config/datagbase.php"を一部コメントアウトした。今回のシステムではDBを使わないので問題ないはず。
/*
 var $default = array(
  'driver' =>'mysql',
  'persistent' => false,
  'host' => 'localhost',
  'login' => 'user',
  'password' => 'password',
  'database' => 'database_name',
  'prefix' => '',
 );

 var $test = array(
  'driver' => 'mysql',
  'persistent' => false,
  'host' => 'localhost',
  'login' => 'user',
  'password' => 'password',
  'database' => 'test_database_name',
  'prefix' => '',
 );
    */

2010年7月16日金曜日

JavaScript文字列検索のバリエーション

indexOfメソッドか正規表現(RegExpオブジェクト)を使う。

RegExpオブジェクトはnewせずにリテラルをスラッシュで囲む方法もある(静的な正規表現)。

for (var i = 0; i < 3000; i++) {
    //var m = str.indexOf('item:9999');           // (1) indexOf
    //var m = /item:9999/.exec(str);             // (2) 静的な正規表現
    //var m = new RegExp('item:9999').exec(str); // (3) 動的生成の正規表現
    //var m = /item:9999$/.exec(str);             // (4) 正規表現 (3/31 追加)
}

4番目は、わざと正規表現のメタキャラクタを入れて最適化を無効にしている。

RgeExpに関しては、存在確認だけならexecよりtestを使うほうが行儀がいい。

GoogleAdwords経由かどうか判定

クッキーの「__utmz」を利用して、どうにか判定できる。"organic"が含まれていたら普通のリンク、そうでなければAdwordsのリンク(たぶん)

        var __utmz = $.myGetCookie('__utmz');
        if(__utmz && __utmz.indexOf('organic') >= 0){
          return 'Google-Organic';
        }else{
          return 'Google-Adwords';
        }

2010年7月15日木曜日

jQueryユーティリティ関数を追加するプラグイン例

extend関数を使って追加する。
実行は jQuery.f() の形式。

プラグインのコード

(function($){
  $.extend({
    // get cookie
    myGetCookie: function(name){
      var regexp = new RegExp('; ' + name + '=([^;]*);');
     var match  = ('; ' + document.cookie + ';').match(regexp);
     if(match){
      return match[1];
     }
    },
    // set cookie
    mySetCookie: function(key, val, validFunc){
      var flg = true;
      if(validFunc){
        if(!validFunc(val)){
          flg = false;
        }
      }
      if(flg){
        document.cookie= key + '=' + encodeURIComponent(val) + ';path=/;';
      }
    },
    // referrer -> key
    classifyReferrer: function(ref){
      if('affiliate-b.com/'.indexOf(ref) >= 0){
        return 'アフィリエイト';
      }else if('google.co.jp/'.indexOf(ref) >= 0){
        return 'Google-Organic';
      }else if('google.com/aclk'.indexOf(ref) >= 0){
        return 'Google-Adwords';
      }else if('yahoo'.indexOf(ref) >= 0){
        return 'Yahoo-Organic';
      }else if('overture.com'.indexOf(ref) >= 0){
        return 'Yahoo-Overture';
      }else{
        return 'その他';
      }
    }
  });
})(jQuery);

呼び出し元

/* conversion tracking */
  var CONVERSION_COOKIE_NAME = 'xx_ref';
  var CONVERSION_INPUT_NAME  = 'xx_ref';
  // exec.
  if($('body#form-idx').length){
    var c;
    if(c = jQuery.myGetCookie(CONVERSION_COOKIE_NAME)){
      $('form').append('');
    }
  } else {
    if(document.referrer) {
      jQuery.mySetCookie(CONVERSION_COOKIE_NAME,
                         jQuery.classifyReferrer(document.referrer),
                         function(val){
                           return (location.hostname.indexOf(val, 0) < 0);
                         });
    }
  }

2010年7月14日水曜日

JavaScriptでCookie処理

ポイント

  • リファラーの取得
  • クッキーの取得(パースするのが少し煩わしい)
  • encodeURIComponent.encode()
  • 高階関数(値に応じて保存するかどうかを判断する)

/* conversion tracking */
  var CONVERSION_COOKIE_NAME = 'ones_style_ref';
  // get cookie
  function myGetCookie(name){
    var regexp = new RegExp('; ' + name + '=([^;]*);');
   var match  = ('; ' + document.cookie + ';').match(regexp);
   if(match){
    return match[1];
   }
  }
  // set cookie
  function mySetCookie(key, val, validFunc){
    var flg = true;
    if(validFunc){
      if(!validFunc(val)){
        flg = false;
      }
    }
    if(flg){
      document.cookie= key + '=' + encodeURIComponent(val) + ';path=/;';
    }
  }
  // exec.
  if($('body#form-idx').length){
    var c;
    if(c = myGetCookie(CONVERSION_COOKIE_NAME)){
      $('form').append('');
    }
  } else {
    if(document.referrer) {
      mySetCookie(CONVERSION_COOKIE_NAME,
                  document.referrer,
                  function(val){
                    return (location.hostname.indexOf(val, 0) < 0);
                  });
    }
  }

参考

日時の形式

インターネットで日常的に見かけるのはたったの3種類。暗記してしまいたい。

1. ISO8601 - 日付と時刻の表記に関する国際規格 -
1-1. UTCの場合 - Zを付ける
例: 2010-07-14T15:27:46Z
1-1. UTC以外の場合
例: 2010-07-14T15:27:46+01:00
2. RFC822
電子メールに関するRFC(よって日時以外のデータに関する決まりも含む)。
現在はRFC2822が発行されたためRFC822は破棄されたが、日付の形式は現在でも使われている。
例: Wed, 14 Jul 2010 09:53:48 +0900

2010年7月9日金曜日

印刷&印刷プレビューでJavaScript

IEに限って、onbeforeprintとonafterprintイベントが使える。

たとえば

flashなどのobjectを含むhtmlを印刷した場合にレイアウトが乱れることがあるので、印刷前にhtmlからobjectを削除して解決(代替画像に差し替える、といったこともできると思う)。

    // このコードはjQueryに依存します

    // object要素を除去した印刷用html
    //(dhtml的に変化するページなので初めに印刷用htmlを記録しておく)
    var htmlStripped = $('body').html().replace(/<object.*<\/object>|<script.*<\/script>/ig, '');

    // 関数定義
    function fbef(){
      // 印刷するにあたって、記録しておいた印刷用htmlを復活
      $('body').html(htmlStripped);
    };
    function faft(){
      // キャッシュからリロード(これが無いと、
      // サーバーからリロードしたhtmlが印刷されて
      // しまって"fbef"の処理が無意味になる)
      location.reload(false);
    };

    // イベントハンドラを設定
    window.onbeforeprint = fbef;
    window.onafterprint  = faft;

2010年7月1日木曜日

Perlメールの文字化け

Encode::encode()でjis変換すると化ける文字がある。

そういうときはtrで強引にやる。

for my $key (keys %values) {
    #YEN SIGN, WAVE DASH, MINUS SIGN
    $values{$key} =~ tr/\x{00a5}\x{ff5e}\x{ff0d}/\x{005c}\x{301c}\x{2212}/;
}
...
$body = makemailbody($template, \%values);
$body = Encode::encode('jis', $body);

2010年6月29日火曜日

jQueryプラグイン:ロールショウ?

指定された要素の子供を、順番にshow&hideするだけのもの。
今日自作。拡張性ほとんどなし。

jquery.myslideshow.js:
(function($){

  $.fn.mySlideShow = function(options){
    var c = $.extend(
      {
        //opt1 : true
        duration: 3000
      },
      options
    );
    var o = this;
    var n = $(o).children().length;
    var curr = 0;
    var timerID;

    var f = function() {
      var tgt = $(o).children().get(curr);
      $(tgt).fadeIn('normal');
      $(tgt).siblings().hide();
      curr++;
      if(curr >= n) {
        curr = 0;
      }
    }

    f();
    if(timerID){
      clearInterval(timerID);
    }
    timerID = setInterval( f, c.duration );

    return o;
  };
})(jQuery);

MTにPerlモジュールを追加したいとき

参考:mtプラグインにおける Perl モジュール の配置 について - 左脳Script

CPANを使えない環境での話。
/インストールディレクトリ/lib/ に置くと動作するらしい。
Digest::SHA::PurePerl なら、/インストールディレクトリ/lib/Digest/SHA/

今回はとりあえず、バックアップ/復元に使う、
* Archive::Tar
* Archive::Zip
* IO::Compress::Gzip
* IO::Uncompress::Gunzip
が使えればOK.

2010年6月28日月曜日

[HOP] partition problemのコード

単独のreturnが参考になった。

use strict;
use warnings;
use Data::Dumper;

sub partition{
    my ($target, $treasures) = @_;

    return [] if $target == 0;     #anon. array ref
    return () if $target < 0 || @$treasures <= 0; # empty list
    # ここはPerlの作法に詳しくないと謎。
    # 一般的に、エラーケースではempty listを返すのが普通。
    # 空リストを明示的に書かずに、単独のreturnでよいかもしれない
    # (呼び出し元がリストコンテキストなら空リストになるから)

    my ($first, @rest) = @$treasures;
    my @solutions = partition($target - $first, \@rest);
    return ((map {[$first, @$_]} @solutions), partition($target, \@rest));
    # この文も実は難しい。mapの結果は、@solutionsがarray refならリスト、
    # empty listなら空リストになる性質がある。
}

print Dumper(partition(5, [1,2,3,4]));

2010年6月25日金曜日

クローラーの邪魔をしたい場合

.htaccess

BrowserMatchNoCase Googlebot     robot
BrowserMatchNoCase Slurp         robot
BrowserMatchNoCase msnbot        robot
BrowserMatchNoCase proodleBot    robot
BrowserMatchNoCase psbot         robot
BrowserMatchNoCase ScSpider      robot
BrowserMatchNoCase TutorGigBot   robot
BrowserMatchNoCase YottaShopping robot
BrowserMatchNoCase Faxobot       robot
BrowserMatchNoCase Gigabot       robot
BrowserMatchNoCase MJ12bot       robot
BrowserMatchNoCase Baidu         robot
deny from env=robot

確認(Perlモジュールを使う)

$ lwp-request -mHEAD -H 'User-Agent: slurp' http://sample.com/img/1.jpg
$ lwp-request -mHEAD -H 'User-Agent: slurp' http://user:password@testserver.com/img/1.jpg

2010年6月24日木曜日

PHP5のグローバル変数

グローバル変数は、globalキーワードを付けて宣言することになったらしい。
※以前のようにブロック内でglobal宣言しただけでは、参照できない。
茶々ラボ - Chacha Lab.: PHPでグローバル変数

しかも、global宣言と同時に初期化できないのが面倒。
※2つ文を書く必要がある。

global $category_tbl;
$category_tbl = array(
    '広告[  ]+総合広告' => array(1),
);
...
function(){
    global $category_tbl;
    print_r($category_tbl);
}

PHPでTSV処理

エクセルで出力されたTSVを想定。

CSVのほうが知名度はあるが、TSVが好ましい(区切りには制御文字のほうが適している、それこそ制御文字の本分でしょう、という気がする)。

エクセルでデータを作る人、つまり非プログラマー、非SEの人は普通TSVのことを知らないが、そこは事前に説明してTSVで出力するようお願いしておく。

TSVに関する大まかな仕様

  • 改行を含む列は"で始まり"で終わる
  • タブ、二重引用符を含んでもいい
  • SJIS(PHPプログラム内ではUTF8に変換して処理する)

TSVファイルを読み込んで連想配列の配列に変換(PHP)

/*
 * TSVファイルを読み込んで連想配列の配列に変換
 * 引数:処理結果(参照渡し)、TSVファイルのパス
 */
function parseTSV(&$records, $path){
    $fp = fopen($path, 'r');
    if($fp===FALSE){
        return FALSE;
    }

    $records = array();
    $errors = array();
    //$cnt = 0; // これはデバッグ専用のリミット(後で削除)
    //    while(!feof($fp) && $cnt < 10){
    while(!feof($fp)){

        $line = fgets($fp);
        $line = mb_convert_encoding($line, 'UTF-8', 'SJIS-win');
        $line = str_replace(array("\r\n","\r"), "\n", $line);
        $len = mb_strlen($line, 'UTF-8');

        $state = 'START';
        $flg = 0;
        $buf = '';
        $fields = array();
        //        $cnt++;

        for($i=0; $i<$len; $i++){
            $char = mb_substr($line, $i, 1, 'UTF-8');
            if($state=='START'){
                if($char === '"' && $flg == 0) {
                    $flg = 1;
                    $state = 'MID';
                } elseif($char === "\n" && $flg == 0) {
                    break;
                } elseif($char === "\t") {
                    $state = 'END';
                } else {
                    $buf .= $char;
                    $state = 'MID';
                }
            } elseif($state == 'MID') {
                if($char === "\t") {
                    if($flg){
                        $buflen = mb_strlen($buf, 'UTF-8');
                        if($buflen && mb_substr($buf, $buflen - 1, 1, 'UTF-8') == '"') {
                            $state = 'END';
                        } else {
                            $char = ' ';
                        }
                    } else {
                        $state = 'END';
                    }
                } elseif($char === "\n"){
                    if($flg){
                        $buf .= $char;
                        // データが次の行に続いている場合
                        $line = fgets($fp);
                        $line = mb_convert_encoding($line, 'UTF-8', 'SJIS-win');
                        $line = str_replace(array("\r\n","\r"), "\n", $line);
                        $len = mb_strlen($line, 'UTF-8');
                        $i = -1; // ここは強引
                    } else {
                        $state = 'END';
                    }
                } else {
                    $buf .= $char;
                }
            }
            if($state == 'END') {
                if($flg){
                    $buf = mb_substr($buf, 0, mb_strlen($buf, 'UTF-8') - 1, 'UTF-8');
                }
                $buf = mb_ereg_replace('""', '"', $buf); // 連続したダブルクォートは1つに

                // 連想配列に追加して、初期化
                $fields[] = $buf;
                $state = 'START';
                $flg = 0;
                $buf = '';
            }
        }
        // 1レコード読み込み終了
        if(count($fields) > 0) {
            $records[] = $fields;
        }
    }
    fclose($fp);

    return TRUE;
}

2010年6月23日水曜日

CSSでsubmitの装飾(IE6以外)

buttonでもinput(submit)でもOK。hover, focusで画像ロールオーバーさせる。

WebTecNote - [css] フォームのボタンをスタイルシートで画像ボタンに変更する

しかし、IE6以下の場合は動作しない(hover疑似クラスが機能しないため)。必要なら JavaScriptを加える必要がある。

grep(シェルコマンド)での論理和

egrep にして"|"を使えばOK。

postfixのログを検索している。

$ egrep "726D9220181|DDDEC220181|A9A91220181|8F6FF22071E|96A0522071D|D85A622074C|81E5822074B|8A89422074C|34A27220750|3883C22074E|AD2FD220750|B1A9F22074E|2288A22074F|2B57122074E|98C5322074E" /var/log/maillog.*

参考

3.6 Basic vs Extended Regular Expressions

In basic regular expressions the meta-characters ‘?’, ‘+’, ‘{’, ‘|’, ‘(’, and ‘)’ lose their special meaning; instead use the backslashed versions ‘\?’, ‘\+’, ‘\{’, ‘\|’, ‘\(’, and ‘\)’.

"grep"ではメタキャラクタの前に"\"が必要だが、"egrep"つまり "extended grep" なら不要、と。

2010年6月18日金曜日

autopair, paredit

職場のWindowsに導入してみた。

ついでに、選択範囲を上書きする (delete-selection-mode 1) も。

参考

  • http://trey-jackson.blogspot.com/2009/09/emacs-tip-33-paredit.html
  • http://emacs-fu.blogspot.com/2010/06/automatic-pairing-of-brackets-and.html
  • http://www.emacswiki.org/emacs/AutoPairs

課題

  • skkでの入力中は無効にしたい(カッコ/コッカが入力できないため) ← adviceで対応
(defadvice skk-mode (around disable-autopairs-around (arg))
  "Disable autopairs mode when skk-mode is on"
  ad-do-it
  (if skk-mode
      (autopair-mode 0)
    (autopair-mode 1)
    ))
(ad-activate 'skk-mode)

2010年6月7日月曜日

BOM の存在を消去する(Perlの場合)

BOM存在のチェックと、その除去を行うプログラム(Perl)

一口にBOMと言っても、エンコードに応じて実体は様々あるらしい(utf8なら 0xEF 0xBB 0xBF)

1. BOMチェックスクリプト

#!/usr/bin/perl
@bom = (
  chr(0x00).chr(0x00).chr(0xFE).chr(0xFF),
  chr(0xFF).chr(0xFE).chr(0x00).chr(0x00),
  chr(0x00).chr(0x00).chr(0xFF).chr(0xFE),
  chr(0xFE).chr(0xFF).chr(0x00).chr(0x00),
  chr(0xFE).chr(0xFF),
  chr(0xFF).chr(0xFE),
  chr(0xEF).chr(0xBB).chr(0xBF)
);

if(-f $ARGV[0]){
  open(FILE, $ARGV[0]) or die;
  $dat = <FILE>;
  for(my $i = 0; $i <= $#bom; $i++){
    if(index($dat, $bom[$i]) == 0){
      print "$ARGV[0]\n";
      last;
    }
  }
  close(FILE);
}

2. BOM除去スクリプト(いちおうバックアップを取る仕様)

#!/usr/bin/perl
if(-f $ARGV[0]){
    my $f = $ARGV[0];
    my $new = $f . '.new';
    my $flg = 1;
    open(OLD, $f) or die;
    open(NEW, "> $new")         or die "can't open $new: $!";
    while (<OLD>) {
        if($flg) {
            print NEW substr($_, 3) or die "can't write $new: $!";
            $flg = 0;
        } else {
            print NEW $_            or die "can't write $new: $!";
        }
    }
    close(OLD)                  or die "can't close $old: $!";
    close(NEW)                  or die "can't close $new: $!";
    rename($f, "$f.orig")       or die "can't rename $old to $old.orig: $!";
    rename($new, $f)            or die "can't rename $new to $old: $!";
} else {
    die 'no argument';
}

PHPのレアな関数

* error_get_last()
(PHP 5 >= 5.2.0)
error_get_last — 最後に発生したエラーを取得する。

* simplexml_load_file()
(PHP 5)
simplexml_load_file — XMLファイルをパースし、オブジェクトに代入する

* PHPバージョン5以上だとini_set()、.htaccessでallow_url_fopenを有効にできない
管理者にphp.iniを変更してもらわなければ file_get_contents() や simplexml_load_file() が使えない。
↓こんな感じのエラーが発生(failed to open steream

Array ( [type] => 2 [message] => file_get_contents(http://twitter.com/statuses/user_timeline/138673716.rss) [function.file-get-contents]: failed to open stream: no suitable wrapper could be found [file] => /virtual/www/2012/lib/twjson.php [line] => 12 )

2010年6月6日日曜日

Caching and Memoization

Higher Order Perl.

inline caching
memoization
marshalling
orcish maneuver ( || and cache 戦略、||= 演算子)
semipredicate problem

memoizationではsymbol tableも書き換えないといけない(再帰関数の場合に、内部で古い関数定義が呼び出されてしまうから)

"Partition problem" - 面白い例題。Chapter1, 3, 4 で徐々に改善されていく。NP-complete problem らしい(Nondeterministic Polynomial)

2010年6月3日木曜日

ul要素内のliをランダムに並び替えるJavaScript

  // random image
  if($('body#home').length){
    var ul = $('div.category > ul');
    if(!ul) return;

    var chi = ul.children();
    for(var i = 0, n = chi.length; i < n; i++){
      var r = Math.floor( i + Math.random() * (n - i) );
      var tmp = chi[i];
      chi[i] = chi[r];
      chi[r] = tmp;
    }

    ul.html(chi);
  }

htmlタグを除いてマルチバイト150文字にtruncateする関数

htmlタグを除き、さらにマルチバイト文字150文字を取り出す。


/********************************************************************************************
myTruncate() : 
- 用途 : truncate string considering HTML tag.
- 引数 : html string, maxlength, url
- 戻値 : truncated string
********************************************************************************************/
function myTruncate($html, $maxLength, $url) {
    $printedLength = 0;
    $position = 0;
    $tags = array();
    $printstr = '';

    mb_internal_encoding("UTF-8");

    // while ($printedLength < $maxLength && preg_match('{</?([a-z]+)[^>]*>|&#?[a-zA-Z0-9]+;}', $html, $match, PREG_OFFSET_CAPTURE, $position))
    // ここの preg_match を等価なマルチバイト処理に変更すればうまくいく(たぶん)
    while ($printedLength < $maxLength && $this->mb_preg_match('{</?([^>]+)>|&#?[a-zA-Z0-9]+;}', $html, $match, PREG_OFFSET_CAPTURE, $position))
        {
            list($tag, $tagPosition) = $match[0];

            // Print text leading up to the tag.
            $str = mb_substr($html, $position, $tagPosition - $position);
            if ($printedLength + mb_strlen($str) > $maxLength)
                {
                    //print(mb_substr($str, 0, $maxLength - $printedLength));
                    $printstr .= mb_substr($str, 0, $maxLength - $printedLength);
                    $printedLength = $maxLength;
                    break;
                }

            //print($str);
            $printstr .= $str;
            $printedLength += mb_strlen($str);

            if ($tag[0] == '&')
                {
                    // Handle the entity.
                    //print($tag);
                    $printstr .= $tag;
                    $printedLength++;
                }
            else
                {
                    // Handle the tag.
                    $tagName = $match[1][0];
                    $tagName = mb_ereg_replace(' .*', '', $tagName);
                    if ($tag[1] == '/')
                        {
                            // This is a closing tag.

                            $openingTag = array_pop($tags);
                            if($openingTag != $tagName) die;
                            assert($openingTag == $tagName); // check that tags are properly nested.

                            //print($tag);
                            $printstr .= $tag;
                        }
                    else if ($tag[mb_strlen($tag) - 2] == '/')
                        {
                            // Self-closing tag.
                            //print($tag);
                            $printstr .= $tag;
                        }
                    else
                        {
                            // Opening tag.
                            //print($tag);
                            $printstr .= $tag;
                            $tags[] = $tagName;
                        }
                }

            // Continue after the tag.
            $position = $tagPosition + mb_strlen($tag);
        }

    // Print any remaining text.
    if ($printedLength < $maxLength && $position < mb_strlen($html))
        //print(mb_substr($html, $position, $maxLength - $printedLength));
        $printstr .= mb_substr($html, $position, $maxLength - $printedLength);

    // Close any open tags.
    while (!empty($tags)) //printf('</%s>', array_pop($tags));
        $printstr .= sprintf('</%s>', array_pop($tags));

    if(mb_strlen($html) > mb_strlen($printstr)){
        $readmore = '<p class="nav"><a href="' . $url . '" title="続きを読む">続きを読む</a></p>';
    } else {
        $readmore = '';
    }

    return '<p>' . $printstr . '</p>' . $readmore;
}

2010年6月2日水曜日

Smarty反復構文での制御変数参照方法

for文のような、制御変数を明示的に利用する反復構文がない。

1. sectionを使う

{section name=cnt start=0 loop=10}
{$smarty.section.cnt.index}
{/section}

2. foreachのプロパティを使う

{foreach from=emps item=emp name=emploop}
{$smarty.foreach.emploop.index}
{/section}

どちらの場合も、breakとcontinueに相当するものはない。

2010年5月31日月曜日

Smartyのassignと反復に関して

assign

valueに2つ変数を使用する例。ダブルクォートの内側にバッククォートを入れる。

{assign var=myvar value="`$var1`の中央部にある`$var2`で`$var3`を見つけました"}

参考:http://xfield.info/modules/tautech/content0107.html

反復のためのforはない。代わりは"section"

{section name=cnt start=0 loop=10}
{$smarty.section.cnt.index}
{/section}

※startを設定することで任意の値から開始できる(デフォルト値は0)。